Add plaintiff registration to the Court of Humanity app - #320
Conversation
Plaintiff registration was ported into apps/warondisease first. The case belongs to the court, not to a party in it, so /plaintiffs and /plaintiffs/manage land in apps/courtofhumanity instead. New pages: - /plaintiffs — register a plaintiff, browse the public wall of faces. - /plaintiffs/manage — edit the plaintiffs you registered, with the square photo cropper. New API routes under apps/courtofhumanity/app/api: represented-people, people/[id], conditions/conflicts/jurisdictions search, and the two upload routes that put plaintiff photos in Cloudflare R2. Photo upload needs R2_ENDPOINT, R2_BUCKET, R2_PUBLIC_URL, R2_ACCESS_KEY_ID and R2_SECRET_ACCESS_KEY on the courtofhumanity Vercel project; the block is documented in apps/courtofhumanity/.env.example. Shared server and client code moves into packages/site-kit/src/lib (represented-people, efficacy-lag matcher, person name/href, image upload, object storage, privacy and life-status helpers) plus the WelfareClaim component. The Court app keeps its own local humanity-v-government-case.server.ts and subject.server.ts, so the copied site-kit code reads HUMANITY_V_GOVERNMENT_CASE_SLUG from @optimitron/db instead. apps/courtofhumanity/lib/format-count.ts is deleted because it shadowed the site-kit version and hid the new formatSignificantFigures helper. What stays on optimitron.com: the people directory. Person tiles link there through optimitronUrl(). The 1% Treaty stays on warondisease.org. /humanity-v-government and the court dashboard now link to the local /plaintiffs instead of warondisease.org/plaintiffs. Nav gains a "Register a Plaintiff" item in THE CASE, in both the sidebar and the footer of courtofhumanity.org only. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
4 Skipped Deployments
|
|
Warning Review limit reachedNext included review available in 16 minutes. View limit detailsLimit details: You’ve used the included review currently available. You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Review configuration: ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Team Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (50)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
🔵 Needs a closer look
It introduces a large new end-to-end flow (UI + uploads + multiple APIs + shared lib extraction), and there are scalability/performance concerns that should be resolved and re-validated by a human reviewer before approval.
Pull request overview
This PR relocates plaintiff registration from apps/warondisease into apps/courtofhumanity (where the case record conceptually belongs), while extracting shared plaintiff/gallery/upload logic into packages/site-kit so campaign surfaces can reuse it consistently.
Changes:
- Adds
/plaintiffsand/plaintiffs/managepages to the Court of Humanity app, including registration, public “wall of faces”, filtering, pagination, and plaintiff management (photo crop + evidence). - Introduces new Court API routes for represented-people creation/editing plus search endpoints and upload endpoints (presign + normalized image uploads).
- Adds shared plaintiff-related utilities and components into
packages/site-kit, plus navigation/copy-snapshot wiring for the court variant.
File summaries
| File | Description |
|---|---|
| scripts/site-app-visual-routes.mjs | Adds visual-route coverage for /plaintiffs and authenticated /plaintiffs/manage. |
| pnpm-lock.yaml | Lockfile updates for new dependencies (AWS SDK, react-easy-crop, sharp) and transitive bumps. |
| packages/site-kit/src/lib/storage.ts | Adds localStorage types + helpers for pending represented-person drafts and a sync lock. |
| packages/site-kit/src/lib/slugify.ts | Adds slugify helper used for persisted relationship/role slugs. |
| packages/site-kit/src/lib/site-config.ts | Adds courtPlaintiffs nav item to court variant “case” section. |
| packages/site-kit/src/lib/routes.ts | Adds plaintiffs route constants and a getSignInPath() helper. |
| packages/site-kit/src/lib/represented-person-sync.ts | Client-side sync to POST locally-staged drafts after verification. |
| packages/site-kit/src/lib/represented-person-privacy.ts | Centralizes rules for publishing condition info + evidence-kind restrictions. |
| packages/site-kit/src/lib/represented-people.server.ts | Server query for public plaintiff gallery, filters, counts, and sorting. |
| packages/site-kit/src/lib/represented-life-status.ts | UI label helper for life-status messaging. |
| packages/site-kit/src/lib/plaintiffs-flow.ts | Navigation helper for post-save redirect (direct edit vs list). |
| packages/site-kit/src/lib/person.server.ts | Allows ensurePersonForUser to run on an injected transaction client. |
| packages/site-kit/src/lib/person-name.ts | Adds displayName ⇄ name-parts conversion utilities for forms. |
| packages/site-kit/src/lib/person-href.ts | Single source of truth for /people/{handle-or-id} links. |
| packages/site-kit/src/lib/object-storage.server.ts | Adds R2/S3-compatible public-bucket upload + presign support. |
| packages/site-kit/src/lib/jurisdiction-search.server.ts | Adds jurisdiction typeahead query helper. |
| packages/site-kit/src/lib/image-upload.server.ts | Adds server-side image validation/normalization (sharp) + key generation. |
| packages/site-kit/src/lib/image-upload.client.ts | Adds client helper for posting images to the backend upload route. |
| packages/site-kit/src/lib/image-upload-types.ts | Defines allowed upload “kinds” for campaign/court surfaces. |
| packages/site-kit/src/lib/global-variable-lookup.server.ts | Adds canonical condition GV lookup for condition normalization/linking. |
| packages/site-kit/src/lib/format-count.ts | Adds formatSignificantFigures() helper alongside formatCount(). |
| packages/site-kit/src/lib/efficacy-lag-matcher.server.ts | Adds efficacy-lag matching logic + evidence upsert for memorials. |
| packages/site-kit/src/components/shared/WelfareClaim.tsx | Extracts the campaign welfare-claim component for reuse in court pages. |
| packages/site-kit/package.json | Adds AWS SDK + sharp dependencies needed for shared upload/storage code. |
| apps/courtofhumanity/package.json | Adds react-easy-crop and moves sharp to dependencies for runtime usage. |
| apps/courtofhumanity/lib/format-count.ts | Deletes local duplicate so site-kit formatting helpers are used. |
| apps/courtofhumanity/app/terms/page.logged-out.md | Updates footer/sidebar snapshot to include “Register a Plaintiff”. |
| apps/courtofhumanity/app/privacy/page.logged-out.md | Updates footer/sidebar snapshot to include “Register a Plaintiff”. |
| apps/courtofhumanity/app/plaintiffs/register-plaintiff-form.tsx | Adds the client registration form with unauthenticated local draft staging + sync. |
| apps/courtofhumanity/app/plaintiffs/person-face-tile.tsx | Adds the public gallery tile component (links to optimitron people directory). |
| apps/courtofhumanity/app/plaintiffs/people-filter-bar.tsx | Adds client filter UI + option loading via search APIs. |
| apps/courtofhumanity/app/plaintiffs/page.tsx | Adds /plaintiffs page: registration, copy, public wall-of-faces, filters, pagination. |
| apps/courtofhumanity/app/plaintiffs/page.logged-out.md | Adds logged-out snapshot for /plaintiffs. |
| apps/courtofhumanity/app/plaintiffs/manage/square-photo-cropper.tsx | Adds modal cropper UI for square photos (react-easy-crop + canvas output). |
| apps/courtofhumanity/app/plaintiffs/manage/page.tsx | Adds /plaintiffs/manage server page: auth gate, fetch user plaintiffs, pass to client editor. |
| apps/courtofhumanity/app/page.logged-out.md | Updates home snapshot nav to include “Register a Plaintiff”. |
| apps/courtofhumanity/app/humanity-v-government/page.tsx | Updates CTA link to local /plaintiffs route. |
| apps/courtofhumanity/app/humanity-v-government/page.logged-out.md | Updates CTA snapshot link to /plaintiffs. |
| apps/courtofhumanity/app/dashboard/page.tsx | Updates dashboard CTA to local /plaintiffs route. |
| apps/courtofhumanity/app/contact/page.logged-out.md | Updates snapshot nav to include “Register a Plaintiff”. |
| apps/courtofhumanity/app/api/uploads/presign/route.ts | Adds authenticated presign endpoint for direct uploads to R2. |
| apps/courtofhumanity/app/api/uploads/images/route.ts | Adds authenticated backend image-upload endpoint with normalization + R2 upload. |
| apps/courtofhumanity/app/api/referendums/[slug]/represented-people/route.ts | Adds represented-person creation endpoint with validation, memorial/evidence handling, and case party creation. |
| apps/courtofhumanity/app/api/people/[id]/route.ts | Adds represented-person update/delete endpoints with validation + visibility rules. |
| apps/courtofhumanity/app/api/jurisdictions/search/route.ts | Adds jurisdictions search endpoint for typeahead/filter surfaces. |
| apps/courtofhumanity/app/api/conflicts/search/route.ts | Adds conflicts search endpoint for filter/typeahead. |
| apps/courtofhumanity/app/api/conditions/search/route.ts | Adds conditions search endpoint for filter/typeahead. |
| apps/courtofhumanity/.env.example | Documents required R2 environment variables for photo uploads. |
Review details
Files not reviewed (1)
- pnpm-lock.yaml: Generated file
- Files reviewed: 50/51 changed files
- Comments generated: 2
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 619826479d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
PR review packetStart here
Agent preflightThe agent checks this only after rerunning the review until every diff is intentional. It resets for each commit.
Human review checklistAgents leave these boxes unchecked. Mike checks them after reviewing the linked evidence.
Changed files considered
Updated automatically when this PR's preview or visual review reruns. |
Fast registration posts lifeStatus UNKNOWN with no memorial, so the efficacy-lag matcher in the creation route never runs for those plaintiffs: the death date and condition it needs only arrive later through the PATCH path, which never called it. Those plaintiffs got no efficacy-lag records and so were missing from both the efficacyLag=1 filter and the "died closest to cure" ordering this PR ships. Run the matcher after the memorial upsert, best-effort and inside the same transaction, exactly as the creation route does. The wall of faces also rendered every <img> eagerly, so a full page of plaintiff photos was fetched up front on mobile. Added loading="lazy" and decoding="async". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TgYULvPW89bMAV3C6mXmtG
The Vercel projects are configured now; this only re-runs the previews and the checks that waited on them. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Code reviewSix issues confirmed via independent verification (two bug-scan passes + a dedicated validation pass per finding). No CLAUDE.md/AGENTS.md compliance issues found — two independent audits against the root CLAUDE.md and AGENTS.md came back clean. 1. Country/condition filter dropdowns can only ever show the first 50 results — most values are unreachableoptimitron/apps/courtofhumanity/app/plaintiffs/people-filter-bar.tsx Lines 84 to 88 in cee6341 The filter bar requests
Both 2. Pixel-limit upload rejection returns 500 instead of 400optimitron/packages/site-kit/src/lib/image-upload.server.ts Lines 114 to 116 in cee6341 Throws optimitron/apps/courtofhumanity/app/api/uploads/images/route.ts Lines 63 to 70 in cee6341
3. "Best-effort" efficacy-lag matcher can't actually be best-effort — it runs inside the transaction it's supposed to not breakoptimitron/apps/courtofhumanity/app/api/people/[id]/route.ts Lines 919 to 930 in cee6341 The comment says "a matcher failure must not fail the edit," and the call is wrapped in try/catch — but that try/catch sits inside 4. A failed-then-retried registration can leave a stale draft that becomes a duplicate plaintiffoptimitron/apps/courtofhumanity/app/plaintiffs/register-plaintiff-form.tsx Lines 173 to 176 in cee6341
optimitron/apps/courtofhumanity/app/plaintiffs/register-plaintiff-form.tsx Lines 216 to 218 in cee6341 On a failed direct POST, the draft (with its 5. Dead branch — the "saved" confirmation screen can never renderoptimitron/apps/courtofhumanity/app/plaintiffs/register-plaintiff-form.tsx Lines 143 to 154 in cee6341 if (result.syncedDrafts.length > 0) {
const destination = getRepresentedPersonDetailsHref(result.syncedPeople)
router.push(destination)
router.refresh()
return
}
setMode(result.syncedDrafts.length > 0 ? "saved" : "idle")The early return above already exits whenever 6. A deceased plaintiff's health condition can be published even though the UI just cleared that flagoptimitron/packages/site-kit/src/lib/represented-person-privacy.ts Lines 12 to 18 in cee6341 if (!input.isPublic) return false
if (input.lifeStatus === PersonLifeStatus.DECEASED) {
return input.publicDisplayAcknowledged
}
return input.showConditionPublicly && input.healthDisclosureConfirmedFor deceased people, publication of the health condition depends only on optimitron/apps/courtofhumanity/app/api/people/[id]/route.ts Lines 706 to 708 in cee6341 PersonCondition.isPublic ends up true for every public deceased plaintiff regardless of the explicit showConditionPublicly: false the client just sent. Realistic sequence: user has a public plaintiff with a private condition, marks them deceased, saves (must tick the required public-display acknowledgment) — the cause of death becomes public even though the UI implied it would stay private, and the toggle to undo it is hidden for deceased people. This is health/death data about real people; recommend treating "show condition publicly" as required, explicit consent for deceased people too, not implied by the general display acknowledgment.
|
Summary
Plaintiff registration was first ported into
apps/warondisease. The case record belongs to the court rather than to a party in the case, so/plaintiffsand/plaintiffs/manageland inapps/courtofhumanityinstead. This PR is that re-home; nothing was added to the campaign app.What changed
New pages (
apps/courtofhumanity/app/plaintiffs/)page.tsx— register a plaintiff, then browse the public wall of faces with sort/cause/condition/conflict/country filters and pagination.register-plaintiff-form.tsx— the name form. Anonymous visitors get their draft staged inlocalStorageand synced after they verify.people-filter-bar.tsx,person-face-tile.tsx.manage/page.tsx,manage/manage-plaintiffs-client.tsx,manage/square-photo-cropper.tsx— edit the plaintiffs you registered: photo, condition, cause of death, relationship, public note, evidence.New API routes (
apps/courtofhumanity/app/api/)referendums/[slug]/represented-people,people/[id],conditions/search,conflicts/search,jurisdictions/search,uploads/presign,uploads/images.Shared code into
packages/site-kitrepresented-people.server.ts,efficacy-lag-matcher.server.ts,represented-person-sync.ts,jurisdiction-search.server.ts,person-name.ts,person-href.ts,global-variable-lookup.server.ts,represented-person-privacy.ts,slugify.ts,plaintiffs-flow.ts,represented-life-status.ts,image-upload.{server,client}.ts,image-upload-types.ts,object-storage.server.ts, andcomponents/shared/WelfareClaim.tsx. Existingstorage.ts,constants.ts,person.server.ts,format-count.tsandroutes.tsgained the additions the pages need.Deliberately not carried over
site-kit/lib/subject.server.ts(a re-export of@optimitron/tracking) and the@optimitron/trackingdependency. The Court app's locallib/subject.server.tswins the@/lib/*lookup and is the better implementation — it clearsdeletedAton the upsert.site-kit/lib/humanity-v-government-case.server.ts. The Court app's local file is a superset. The copied site-kitrepresented-people.server.tsnow readsHUMANITY_V_GOVERNMENT_CASE_SLUGfrom@optimitron/db.apps/courtofhumanity/lib/format-count.tsis deleted. It was byte-equivalent to site-kit'sformatCountand shadowed it, hiding the newformatSignificantFigureshelper.Cross-domain links
optimitronUrl()— that directory stays on optimitron.com.https://warondisease.org/treaty; the court does not serve/treaty./humanity-v-governmentand the court dashboard previously deep-linkedhttps://warondisease.org/plaintiffs. They now link to the local/plaintiffs.Nav
courtPlaintiffs("Register a Plaintiff") is added tonav-items.tsand to the sidebar and footercasesections ofcourtofhumanity.orgonly. No other variant's nav is touched.Deployment note
The
courtofhumanityVercel project needs five new environment variables for plaintiff photo upload:R2_ENDPOINT,R2_BUCKET,R2_PUBLIC_URL,R2_ACCESS_KEY_ID,R2_SECRET_ACCESS_KEY(documented inapps/courtofhumanity/.env.example). It also needsDATABASE_URL,NEXTAUTH_SECRETandNEXTAUTH_URLif they are not already set. Without the R2 variables the pages render and registration works; only photo upload fails.Pages to review
/plaintiffs?logout=1/plaintiffs?login=demo/plaintiffs/manage?login=demo/?logout=1/humanity-v-government?logout=1Verification
pnpm --filter @apps/courtofhumanity typecheck— passpnpm --filter @apps/warondisease typecheck— pass (shared site-kit edits)pnpm typecheck:apps— pass (all seven site apps)pnpm --filter @apps/courtofhumanity lint— passpnpm --filter @apps/courtofhumanity run test:unit— 5 files, 15 tests, passpnpm test:site-app-navigation— 19 tests, passpnpm --filter @apps/courtofhumanity build— pass;/plaintiffs,/plaintiffs/manageand all seven API routes compilepnpm copy courtofhumanity— regenerated;/plaintiffssnapshot added, sidebar/footer entry picked up on the other court pagesLive pass against a local dev server with
NEXT_PUBLIC_SITE_VARIANT=courtofhumanity.org:/plaintiffsrenders logged out and signed in,/plaintiffs/managebounces an anonymous visitor to sign-in and lists your plaintiffs when signed in, registration saves end to end and lands on the plaintiff editor, the filter query params round-trip, all three search endpoints answer 200, andPOST /api/uploads/imagesstored a photo in R2 and returned its public URL. Screenshots captured and inspected locally at 1280 and 390 wide; no layout problems found.🤖 Generated with Claude Code