Skip to content

refactor: migrate task assignment and comment notification emails to …#79

Open
mikepsinn wants to merge 7 commits into
mainfrom
feature/copy-preview-hyperlinks-tables-emails
Open

refactor: migrate task assignment and comment notification emails to …#79
mikepsinn wants to merge 7 commits into
mainfrom
feature/copy-preview-hyperlinks-tables-emails

Conversation

@mikepsinn
Copy link
Copy Markdown
Owner

@mikepsinn mikepsinn commented May 13, 2026

…React components

  • Updated task assignment notification email to use React for rendering.
  • Simplified email content by removing redundant CTAs and feedback notes.
  • Enhanced test cases for task assignment email to reflect changes in content.
  • Migrated task comment notification email to React, improving structure and readability.
  • Updated tests for task comment notification email to accommodate new rendering method.
  • Removed legacy HTML rendering functions and replaced with React components.
  • Cleaned up email markdown documentation for both task assignment and comment notifications.
  • Adjusted URL handling to streamline base URL retrieval.

Summary by CodeRabbit

  • New Features

    • Auto-generated markdown previews for outbound emails and a registry-driven dev email preview; many emails migrated to shared React-based templates, including a new monthly "Humanity Management" status report.
  • Improvements

    • Cleaner, more consistent email designs, stronger CTAs, forward/share wording, and stable preview composition.
    • Multiple public pages refreshed with richer copy and linked sources.
    • Pre-commit checks now provide full per-file validation output during commit attempts.
  • Bug Fixes

    • More reliable base-URL/link resolution and unsubscribe URL handling.
  • Chores

    • CI: added PR-managed-data preview sync job and tightened managed-data apply safety.

Review Change Stack

…React components

- Updated task assignment notification email to use React for rendering.
- Simplified email content by removing redundant CTAs and feedback notes.
- Enhanced test cases for task assignment email to reflect changes in content.
- Migrated task comment notification email to React, improving structure and readability.
- Updated tests for task comment notification email to accommodate new rendering method.
- Removed legacy HTML rendering functions and replaced with React components.
- Cleaned up email markdown documentation for both task assignment and comment notifications.
- Adjusted URL handling to streamline base URL retrieval.
@vercel
Copy link
Copy Markdown

vercel Bot commented May 13, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
optimitron-web Ready Ready Preview, Comment May 13, 2026 6:07am

Copilot AI review requested due to automatic review settings May 13, 2026 04:54
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 13, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Converts many email templates to React-based renderers, adds a preview registry and Playwright snapshot script, refactors email send/dedupe to accept React payloads with safety checks, rewrites unsubscribe handling, tightens CI managed-data sync and remote-apply guards, normalizes DB trigger equality, updates URL/env resolution, and refreshes numerous page contents and tests.

Changes

Email Preview & Rendering

Layer / File(s) Summary
Preview registry & envelope
packages/web/src/lib/email/preview-registry.ts, packages/web/src/lib/email/preview-envelope.ts
Adds EMAIL_PREVIEWS, EmailPreview/EmailBody contracts and functions to render full preview HTML and compose outbound bodies with unsubscribe/signature handling.
React email primitives & components
packages/web/src/lib/email/react-email-components.tsx
New campaign primitives and building blocks (shell, eyebrow, heading, button, metric table, share footer) used by email React templates.
React email templates & previews
packages/web/src/lib/email/*-react-email.tsx, packages/web/src/lib/email/*.email.md
New React-based email templates (magic-link, monthly-chain-digest, post-vote-share, referral-first-conversion, task assignment/comment) and autogenerated .email.md previews with Envelope metadata.
Render & send pipeline
packages/web/src/lib/email/render-react-email.ts, packages/web/src/lib/email/resend.ts, packages/web/src/lib/email/send-deduped-email.server.ts
Adds renderReactEmailBody, centralizes composeOutboundEmailBody, introduces assertEmailSafe, and makes send/dedupe accept discriminated React or html/text payloads.

Preview Snapshoting

Layer / File(s) Summary
Playwright snapshot script
packages/web/scripts/render-emails-to-markdown.ts, packages/web/package.json
Renders /dev/email/<slug> previews to .email.md files including an Envelope table; email:preview-md script added.

Unsubscribe API

Layer / File(s) Summary
Unsubscribe route & tests
packages/web/src/app/api/email/unsubscribe/route.ts, packages/web/src/app/api/email/unsubscribe/route.test.ts
Unified GET/POST handler, RFC 8058 one-click parsing, DB recipient-email confirmation, prompt/confirmation renderers, and expanded test coverage with Prisma mocks.

CI & Managed-Data Safety

Layer / File(s) Summary
CI job & workflow
.github/workflows/ci.yml
Adds sync-preview-managed-data job for PRs, pulls Vercel preview env, runs migrations, and conditionally applies managed-data sync; adds MANAGED_DATA_ALLOW_REMOTE_APPLY for production apply step.
Remote-apply guards
packages/db/prisma/seed.ts, packages/db/scripts/sync-managed-data.ts
Removed CI shortcut; require local host or MANAGED_DATA_ALLOW_REMOTE_APPLY=1 for non-local DB hosts.

DB Normalization & Tests

Layer / File(s) Summary
Trigger normalization & tests
packages/db/src/managed-data/managed-task-triggers.ts, packages/db/src/managed-data/managed-task-triggers.test.ts
Normalize and sort spawn/communication spec inputs for order-insensitive equality; add idempotency test.

URL/env/site defaults, tests & content

Layer / File(s) Summary
URL & env changes
packages/web/src/lib/url.ts, packages/web/src/lib/env.ts, packages/web/src/lib/site.ts, tests
Remove NEXT_PUBLIC_BASE_URL from client env; prefer NEXTAUTH_URL/VERCEL_URL and getBaseUrl() for absolute links; update tests accordingly.
UI/content edits
packages/web/src/app/*, packages/web/src/components/*
Multiple markdown/page copy updates, DashboardShareCard now uses HumanityManagerPromotion, PersonFaceTile life-status conditional rendering, RepresentedPersonConversionForm copy tweak, and assorted generated email markdown files.

Estimated code review effort:
🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs:

"A little rabbit hops and types away,
Rendering previews by night and day,
React bits stitched, snapshots penned,
CI tightened, tests to mend,
I nibble bugs — then shout Hooray!"

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/copy-preview-hyperlinks-tables-emails

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR migrates several outbound notification emails (task assignment, task comment notifications, post-vote share, referral-first-conversion, monthly chain digest, magic link) from legacy string-built HTML/text to React Email rendering, and introduces a unified preview/markdown snapshot pipeline for reviewing rendered email output. It also consolidates base-URL resolution around NEXTAUTH_URL/VERCEL_URL, tightens send-time safety checks, and updates CI/managed-data tooling accordingly.

Changes:

  • Refactored multiple email templates to React components, plus a shared React-email renderer and a central preview registry/envelope composer.
  • Added send-time safety guardrails for localhost/unsub placeholders; extended deduped sending to support React templates.
  • Updated page/email markdown snapshot tooling and CI workflows (including preview managed-data sync) to match the new pipelines.

Reviewed changes

Copilot reviewed 89 out of 90 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
packages/web/src/lib/url.ts Removes NEXT_PUBLIC_BASE_URL client override from base URL resolution.
packages/web/src/lib/tasks/task-comment-notifications.server.ts Awaits async React-rendered task comment notification email builder.
packages/web/src/lib/tasks/task-comment-notification.email.md Adds generated markdown snapshot for task comment notification email.
packages/web/src/lib/tasks/task-comment-notification-react-email.tsx New React Email template for task comment notifications.
packages/web/src/lib/tasks/task-comment-notification-email.server.ts Migrates comment notification email builder to React Email + preview metadata.
packages/web/src/lib/tasks/task-assignment.email.md Adds generated markdown snapshot for task assignment email.
packages/web/src/lib/tasks/task-assignment-react-email.tsx New React Email template for task assignment emails.
packages/web/src/lib/tasks/task-assignment-notifications.server.ts Awaits async React-rendered task assignment email builder.
packages/web/src/lib/tasks/task-assignment-notification-email.server.ts Migrates assignment email builder to React Email + preview metadata.
packages/web/src/lib/tasks/tests/task-comment-notification-email.test.ts Updates tests for async builder and React-rendered output.
packages/web/src/lib/tasks/tests/task-assignment-notification-email.test.ts Updates tests for simplified CTA behavior and async builder.
packages/web/src/lib/site.ts Base origin resolution now prefers NEXTAUTH_URL, then VERCEL_URL, and changes canonical fallback.
packages/web/src/lib/referral-url-helpers.server.ts Updates docstring to reflect share-footer wording change.
packages/web/src/lib/referral-invitations.server.ts Formatting update; continues building invite URL with getBaseUrl().
packages/web/src/lib/humanity-manager-promotion.tsx Introduces shared promotion component for dashboard + email rendering.
packages/web/src/lib/env.ts Removes NEXT_PUBLIC_BASE_URL from client env schema.
packages/web/src/lib/email/wishonia-signature.ts Switches signature URL base from getEmailBaseUrl() to getBaseUrl(); markup tweaks.
packages/web/src/lib/email/unsub-url.ts Switches unsubscribe URL base from getEmailBaseUrl() to getBaseUrl().
packages/web/src/lib/email/task-notification.ts Replaces custom base URL logic with getBaseUrl().
packages/web/src/lib/email/share-footer.ts Updates share footer eyebrow/copy (“Forward this”) and adds forward line.
packages/web/src/lib/email/send-deduped-email.server.ts Adds React-email send path alongside HTML/text for deduped sends.
packages/web/src/lib/email/resend.ts Adds assertEmailSafe, unifies composing via composeOutboundEmailBody, and adds React send composition path.
packages/web/src/lib/email/render-react-email.ts New helper to render React Email to {html,text}.
packages/web/src/lib/email/referral-first-conversion.email.md Adds generated markdown snapshot for referral-first-conversion email.
packages/web/src/lib/email/referral-first-conversion-react-email.tsx New React Email template for referral-first-conversion.
packages/web/src/lib/email/referral-first-conversion-email.ts Migrates referral-first-conversion send to React + preview metadata.
packages/web/src/lib/email/react-email-components.tsx Adds shared React Email primitives (shell, buttons, text, share footer, etc.).
packages/web/src/lib/email/preview-registry.ts New central registry for email previews.
packages/web/src/lib/email/preview-envelope.ts Adds shared compose pipeline + full-preview HTML generation for /dev/email.
packages/web/src/lib/email/post-vote-share.email.md Adds generated markdown snapshot for post-vote-share email.
packages/web/src/lib/email/post-vote-share-react-email.tsx New React Email template for post-vote-share.
packages/web/src/lib/email/post-vote-share-email.ts Migrates post-vote-share send to React + preview metadata.
packages/web/src/lib/email/monthly-chain-digest.server.ts Migrates monthly digest send to React; expands digest data and queries.
packages/web/src/lib/email/monthly-chain-digest.email.md Adds generated markdown snapshot for monthly chain digest email.
packages/web/src/lib/email/monthly-chain-digest-react-email.tsx New React Email template for monthly chain digest (positive/zero variants).
packages/web/src/lib/email/monthly-chain-digest-email.ts Refactors digest model/subject + adds preview metadata/fixture.
packages/web/src/lib/email/magic-link.email.md Adds generated markdown snapshot for magic link email.
packages/web/src/lib/email/magic-link-render.ts Exposes copy helper, adds template id + preview metadata; adds React email preview rendering.
packages/web/src/lib/email/magic-link-react-email.tsx New React Email template for magic link.
packages/web/src/lib/email/magic-link-email.ts Sends magic link email via sendReactEmail using React template.
packages/web/src/lib/email/email-urls.ts Removes getEmailBaseUrl; defaults email URL prefixing to getBaseUrl().
packages/web/src/lib/email/tests/wishonia-signature.test.ts Updates mocks to match signature base URL source change.
packages/web/src/lib/email/tests/task-notification.test.ts Updates base URL env tests to NEXTAUTH_URL/VERCEL_URL behavior.
packages/web/src/lib/email/tests/referral-first-conversion-email.test.ts Updates tests to render React template via renderReactEmailBody.
packages/web/src/lib/email/tests/post-vote-share-email.test.ts Updates tests to render React template via renderReactEmailBody.
packages/web/src/lib/email/tests/monthly-chain-digest-email.test.ts Updates tests to render React template and new subject/copy.
packages/web/src/lib/email/tests/magic-link-email.test.ts Updates mocks/expectations for sendReactEmail path.
packages/web/src/lib/tests/resend.test.ts Adapts tests for new send-time safety checks and base URL mocking.
packages/web/src/lib/tests/mcp-server.task-email.integration.test.ts Removes NEXT_PUBLIC_BASE_URL usage from env mock.
packages/web/src/lib/tests/email-urls.test.ts Removes getEmailBaseUrl tests; keeps prefixEmailImage tests.
packages/web/src/lib/tests/daily-activity-digest.server.test.ts Removes NEXT_PUBLIC_BASE_URL from test env setup.
packages/web/src/components/ui/latex.tsx Dynamically imports KaTeX CSS on client to avoid Node-side CSS parse issues.
packages/web/src/components/dashboard/DashboardShareCard.tsx Replaces inline promotion copy with shared HumanityManagerPromotion.
packages/web/src/app/terms/page.logged-out.md Updates logged-out snapshot with fixed Privacy Policy link.
packages/web/src/app/tasks/page.logged-out.md Updates logged-out snapshot (includes new link formatting/uppercase extraction).
packages/web/src/app/signatories/page.logged-out.md Updates logged-out snapshot with link formatting (and current extraction behavior).
packages/web/src/app/privacy/page.logged-out.md Updates logged-out snapshot with mailto + Terms link.
packages/web/src/app/plaintiffs/page.tsx Refactors plaintiff page content/layout and reorders sections.
packages/web/src/app/plaintiffs/page.logged-out.md Updates logged-out snapshot to match new plaintiff page + extraction changes.
packages/web/src/app/people/page.logged-out.md Updates logged-out snapshot with linkified rows and navigation.
packages/web/src/app/humanity-v-government/page.logged-out.md Updates logged-out snapshot with added internal/external links and formatting.
packages/web/src/app/feedback/page.logged-out.md Updates logged-out snapshot with a root “Back” link.
packages/web/src/app/endorse/page.logged-out.md Expands logged-out snapshot with legal notes + treaty excerpt links.
packages/web/src/app/employees/page.logged-out.md Updates logged-out snapshot with linkified task rows and formatting.
packages/web/src/app/dev/email/[template]/route.ts Replaces hardcoded render map with preview registry + full/raw rendering options.
packages/web/src/app/dashboard/page.logged-out.md Adds logged-out snapshot for /dashboard redirect target.
packages/web/src/app/court/page.logged-out.md Updates logged-out snapshot with evidence links and formatting adjustments.
packages/web/src/app/api/email/unsubscribe/route.test.ts Expands unsubscribe route tests to cover GET confirmation and POST behaviors.
packages/web/scripts/render-pages-to-markdown.ts Improves snapshot extraction (links, tables, transforms), adds retry and redirect marker.
packages/web/scripts/render-emails-to-markdown.ts Adds new script to generate .email.md snapshots via /dev/email?....
packages/web/scripts/build-visual-review.mjs Switches base URL sourcing from NEXT_PUBLIC_BASE_URL to NEXTAUTH_URL.
packages/web/package.json Adds email:preview-md script entry.
packages/web/e2e/email-screenshots.spec.ts Consolidates monthly digest screenshots to new monthly-chain-digest template id.
packages/db/src/managed-data/managed-task-triggers.ts Normalizes spawn spec input ordering to avoid unnecessary updates.
packages/db/src/managed-data/managed-task-triggers.test.ts Adds coverage for order-insensitive child spec comparison.
packages/db/scripts/sync-managed-data.ts Tightens remote apply guard (requires explicit override).
packages/db/prisma/seed.ts Tightens remote seed guard (requires explicit override).
.husky/pre-commit Replaces auto snapshot regen with manual reminder guidance.
.gitignore Ignores Codex CLI scratch files under .codex/.
.github/workflows/ci.yml Removes NEXT_PUBLIC_BASE_URL, adds preview managed-data sync job, and enables remote apply in deploy.
.env.example Removes NEXT_PUBLIC_BASE_URL example variable.
.codex/config.toml Adds repo-level Codex configuration.
.claude/settings.json Removes Stop hook for UI verification.
.claude/hooks/verify-ui-changes.mjs Refactors violation reporting; adds concise Stop-mode summary and full commit-attempt output.
.claude/hooks/pre-commit-checklist.mjs Improves git commit detection and pipes hookData through to verifier.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/web/src/lib/humanity-manager-promotion.tsx Outdated
Comment thread packages/web/scripts/render-pages-to-markdown.ts
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ef0d55f133

ℹ️ 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".

Comment thread packages/web/src/app/api/email/unsubscribe/route.ts
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/web/src/lib/email/email-urls.ts (1)

41-48: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Normalize the base URL before composing fixed email links.

Line 41 uses raw getBaseUrl(), so a trailing slash can produce //dashboard-style links.

💡 Suggested fix
 export function getEmailUrls() {
-  const base = getBaseUrl();
+  const base = getBaseUrl().replace(/\/+$/, "");
   return {
     dashboardLink: `${base}/dashboard`,
     settingsLink: `${base}/settings#email-preferences`,
     prizeLink: `${base}/prize`,
     wishocracyLink: `${base}/wishocracy`,
     alignmentLink: `${base}/alignment`,
   };
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/web/src/lib/email/email-urls.ts` around lines 41 - 48, The code is
using getBaseUrl() raw which can include a trailing slash and produce
double-slash paths for dashboardLink, settingsLink, prizeLink, wishocracyLink,
and alignmentLink; normalize the base by trimming any trailing slashes (e.g.,
remove trailing "/" characters from the value returned by getBaseUrl()) before
concatenating the fixed paths so each template uses a clean base (refer to
getBaseUrl and the
dashboardLink/settingsLink/prizeLink/wishocracyLink/alignmentLink properties).
packages/web/src/lib/site.ts (1)

1273-1279: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Parse env URL to a real origin before returning it.

Line 1274 accepts raw env text, and Line 1278 returns it after trim-only normalization. If NEXTAUTH_URL ever includes a path, URL builders will append paths onto that path and generate incorrect links.

Suggested fix
 export function getConfiguredSiteOrigin(options?: {
   allowLocalFallback?: boolean;
 }) {
   const envOrigin =
     process.env.NEXTAUTH_URL?.trim() ||
     (process.env.VERCEL_URL ? `https://${process.env.VERCEL_URL}` : "");

   if (envOrigin) {
-    return normalizeOrigin(envOrigin);
+    try {
+      return new URL(envOrigin).origin;
+    } catch {
+      return normalizeOrigin(envOrigin);
+    }
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/web/src/lib/site.ts` around lines 1273 - 1279, The code currently
returns the raw trimmed envOrigin, which may include a path; update the block
that builds envOrigin (using NEXTAUTH_URL and VERCEL_URL) to parse it as a URL
and extract its origin before calling normalizeOrigin: attempt new
URL(envOrigin) (if parsing fails and envOrigin lacks a scheme, prepend
"https://" first), then pass url.origin into normalizeOrigin; ensure you catch
parsing errors and fall back to normalizeOrigin(envOrigin) as a safe default.
Use the existing envOrigin variable and normalizeOrigin function in this change.
🧹 Nitpick comments (3)
.github/workflows/ci.yml (1)

616-659: ⚡ Quick win

Consider adding database connection verification before migrations.

The script extracts DATABASE_URL from Vercel's preview env file and immediately runs migrations. If the extracted URL is malformed or the database is unreachable, the migration step will fail without a clear error message.

Consider adding a quick connection test after extracting the URL:

 const target = new URL(parsed.DATABASE_URL);
 console.log(`Loaded Vercel preview database env for ${target.hostname}.`);
+// Quick connection test
+const testCmd = `psql "${parsed.DATABASE_URL}" -c "SELECT 1" > /dev/null 2>&1`;
+require('child_process').execSync(testCmd, { stdio: 'ignore' });
+console.log('Database connection verified.');
 NODE

This provides early failure detection and clearer error messages when preview DB setup issues occur.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 616 - 659, After parsing DATABASE_URL
in the node block (see parseEnv and parsed variables) add a quick connection
verification step before appending env and running migrations: require a
lightweight DB client (e.g., node:pg), construct a client from
parsed.DATABASE_URL, attempt a short connect + simple test query (e.g., SELECT
1) with a short timeout, and throw or console.error a descriptive message if
connection fails; ensure you still mask the URL with ::add-mask:: and only
append to GITHUB_ENV after the verification succeeds.
packages/web/src/lib/email/monthly-chain-digest-react-email.tsx (1)

66-69: ⚡ Quick win

Consider citing the viral growth calculation.

The "32 doubling rounds x 2 referrals each = 4,300,000,000 humans" claim is a major numeric assertion without citation. Other email templates in this PR (see post-vote-share.email.md lines 25-29) link similar calculations to documentation. Consider adding a citation or link to support this claim, especially since it's central to the management report's framing.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/web/src/lib/email/monthly-chain-digest-react-email.tsx` around lines
66 - 69, The sentence in monthly-chain-digest-react-email.tsx inside the
CampaignText block claims "32 doubling rounds x 2 referrals each = 4,300,000,000
humans" without a source; update the copy to include a citation or hyperlink to
the calculation (for example the same docs used by post-vote-share.email.md) by
adding a parenthetical link or footnote-style reference next to the numeric
claim, or replace the raw number with a short "see calculation" link that points
to the authoritative doc/markdown explaining the doubling math; ensure the
change is applied in the CampaignText component string in
monthly-chain-digest-react-email.tsx.
packages/web/src/lib/tasks/task-assignment-notification-email.server.ts (1)

82-90: ⚡ Quick win

Use a deterministic host in preview rendering.

Line 87 pulls taskUrl from environment-aware URL resolution, which makes generated preview markdown drift between localhost and deployed hosts.

Suggested fix
 export const TASK_ASSIGNMENT_PREVIEW: EmailPreview = {
@@
   renderReact: () =>
     React.createElement(TaskAssignmentReactEmail, {
@@
-      taskUrl: getTaskUrl(SAMPLE_ASSIGNMENT_INPUT.id),
+      taskUrl: `https://warondisease.org/tasks/${SAMPLE_ASSIGNMENT_INPUT.id}`,
       title: SAMPLE_ASSIGNMENT_INPUT.title,
       recipientReferralUrl: SAMPLE_ASSIGNMENT_INPUT.recipientReferralUrl,
     }),
 };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/web/src/lib/tasks/task-assignment-notification-email.server.ts`
around lines 82 - 90, The preview render uses environment-dependent
getTaskUrl(SAMPLE_ASSIGNMENT_INPUT.id) inside renderReact which causes
non-deterministic hosts; change the preview to supply a fixed host when building
the sample URL (e.g., call getTaskUrl with a preview/host override or construct
the URL using a constant preview host) so TaskAssignmentReactEmail always
receives a deterministic taskUrl for previewing; update the renderReact
invocation that creates TaskAssignmentReactEmail (and adjust
SAMPLE_ASSIGNMENT_INPUT usage if needed) to pass a known host like "example.com"
or a dedicated PREVIEW_HOST constant.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/web/src/app/api/email/unsubscribe/route.ts`:
- Around line 279-281: The catch block is logging the entire parsed object
(which contains the sensitive unsubscribe token); update the error logging in
the unsubscribe route so the token is not included — e.g., create a redacted
copy of parsed (omit or replace parsed.token) and log that redacted object
instead, or log only non-sensitive fields (such as email or id) in the
console.error call inside the catch in route.ts; ensure the return behavior
(htmlResponse(renderErrorHtml(...), 500)) remains unchanged.

In `@packages/web/src/app/donate/page.logged-out.md`:
- Around line 83-94: The numbered lines in the markdown lost a space after the
numeral (e.g., "1Only", "9150,000") making the numbered list render as broken
text; fix by inserting a space after each leading list numeral so each item uses
a proper marker like "1. " or "9 " (e.g., change "1Only" → "1. Only", "9150,000"
→ "9. 150,000"), and scan the donate page content for any other occurrences of
/^\d+/ immediately followed by text and normalize them to a standard
numbered-list format in the donate/page.logged-out.md content.

In `@packages/web/src/app/endorse/page.logged-out.md`:
- Line 104: The in-page link "[JOIN AS AN ORGANIZATION]" points to the
non-existent fragment "#organization-endorsement-form"; fix by either updating
the link target to match the actual heading id (e.g., change the href to the
existing heading fragment such as "#organization" or "#endorsement-organization"
if that exists) or add/rename the corresponding heading to include the id
"organization-endorsement-form" so the anchor resolves; locate the link text
"[JOIN AS AN ORGANIZATION]" and the intended heading in page.logged-out.md and
make the link and heading ids consistent.

In `@packages/web/src/app/humanity-v-government/page.logged-out.md`:
- Line 25: The internal link uses the fragment "#verdict" but the document's
heading is "VOTE ON THE FINDING" which auto-generates the fragment
"#vote-on-the-finding"; update the link target in the `[VOTE ON THE
FINDING](`#verdict`)` anchor to `[VOTE ON THE FINDING](`#vote-on-the-finding`)` (or
alternatively add an explicit id attribute to the "VOTE ON THE FINDING" heading)
so the link resolves correctly to the existing heading.
- Around line 53-55: The three count lines ("COUNT 1 — DEATH BY WAR310 million
deaths", "COUNT 2 — DEATH BY REGULATORY DELAY102 million deaths", "COUNT 3 —
DEATH BY MISALLOCATION37,778 trial-years") are missing a space between the label
and the numeric value; update each to insert a space after the label (e.g.,
"COUNT 1 — DEATH BY WAR 310 million deaths", "COUNT 2 — DEATH BY REGULATORY
DELAY 102 million deaths", "COUNT 3 — DEATH BY MISALLOCATION 37,778
trial-years") so the labels and values are readable.

In `@packages/web/src/app/signatories/page.logged-out.md`:
- Line 30: Update the text containing "messageI" in page.logged-out content to
insert a space so it reads "message I"; specifically locate the sentence that
begins "Share messageI love you..." and change "messageI" to "message I" to
restore proper spacing and readability.
- Around line 32-36: The share links labelled TEXT, WHATSAPP, EMAIL, POST, and
FACEBOOK in page.logged-out.md use the local URL http://127.0.0.1:3001/vote
which will break in production; update each link to use the production URL
https://warondisease.org/vote or (preferably) replace the hardcoded host with a
dynamic base URL variable (e.g., NEXT_PUBLIC_BASE_URL or siteUrl) and
interpolate that into each href so the TEXT, WHATSAPP, EMAIL, POST, and FACEBOOK
links resolve correctly in production.
- Around line 22-24: The signatory lines for [`#1Mike` Sinn], [`#2Nikhil` Yadala],
and [`#3Wishonia`] have missing spaces between labels and values (e.g.,
"KILLS26.0", "PREVENTED4.68", "RECRUITED9"); update each entry in
page.logged-out.md so labels "KILLS", "PREVENTED", and "VOTERS RECRUITED" are
followed by a single space before their numeric values (e.g., "KILLS 26.0",
"PREVENTED 4.68", "RECRUITED 9") preserving existing order and punctuation.

In `@packages/web/src/lib/email/react-email-components.tsx`:
- Around line 271-273: The hardcoded growth claim in the React email copy (the
string containing "32 doubling rounds x 2 referrals each = 4,300,000,000 humans
reached") must be replaced with parameterized values using the existing
<ParameterValue> component; locate the text in react-email-components.tsx and
swap the inline literals ("32", "2", and "4,300,000,000") for the appropriate
<ParameterValue name="..."> entries (pick matching parameter keys from
packages/data/src/parameters/parameters-calculations-citations.ts), compose the
sentence from those components so the copy renders the numbers and citation
metadata, and keep the original wording/spacing and ARIA/readability
considerations intact.

In `@packages/web/src/lib/email/referral-first-conversion-react-email.tsx`:
- Around line 27-28: The hardcoded user-facing number in
referral-first-conversion-react-email.tsx must be replaced with the canonical
parameter via the <ParameterValue> component; find the appropriate parameter key
in packages/data/src/parameters/parameters-calculations-citations.ts (grep for
the doubling/referral calculation or "doubling rounds" to locate the correct
parameter), then update the JSX in the component that renders the sentence (the
email rendering function in referral-first-conversion-react-email.tsx) to render
<ParameterValue name="..."> instead of the literal "4,300,000,000" so the email
always uses the canonical value.

In
`@packages/web/src/lib/tasks/__tests__/task-comment-notification-email.test.ts`:
- Around line 104-111: The test "suppresses the CTA when explicitly null"
currently asserts email.text does not contain "Open the task:" (with a colon)
which can miss regressions because the actual CTA text has no colon; update the
assertion in that test (the it block calling buildTaskCommentNotificationEmail
with cta: null and using baseInput) to assert email.text does not contain the
exact CTA string "Open the task" (no colon) so the suppression check correctly
fails if the CTA reappears.

---

Outside diff comments:
In `@packages/web/src/lib/email/email-urls.ts`:
- Around line 41-48: The code is using getBaseUrl() raw which can include a
trailing slash and produce double-slash paths for dashboardLink, settingsLink,
prizeLink, wishocracyLink, and alignmentLink; normalize the base by trimming any
trailing slashes (e.g., remove trailing "/" characters from the value returned
by getBaseUrl()) before concatenating the fixed paths so each template uses a
clean base (refer to getBaseUrl and the
dashboardLink/settingsLink/prizeLink/wishocracyLink/alignmentLink properties).

In `@packages/web/src/lib/site.ts`:
- Around line 1273-1279: The code currently returns the raw trimmed envOrigin,
which may include a path; update the block that builds envOrigin (using
NEXTAUTH_URL and VERCEL_URL) to parse it as a URL and extract its origin before
calling normalizeOrigin: attempt new URL(envOrigin) (if parsing fails and
envOrigin lacks a scheme, prepend "https://" first), then pass url.origin into
normalizeOrigin; ensure you catch parsing errors and fall back to
normalizeOrigin(envOrigin) as a safe default. Use the existing envOrigin
variable and normalizeOrigin function in this change.

---

Nitpick comments:
In @.github/workflows/ci.yml:
- Around line 616-659: After parsing DATABASE_URL in the node block (see
parseEnv and parsed variables) add a quick connection verification step before
appending env and running migrations: require a lightweight DB client (e.g.,
node:pg), construct a client from parsed.DATABASE_URL, attempt a short connect +
simple test query (e.g., SELECT 1) with a short timeout, and throw or
console.error a descriptive message if connection fails; ensure you still mask
the URL with ::add-mask:: and only append to GITHUB_ENV after the verification
succeeds.

In `@packages/web/src/lib/email/monthly-chain-digest-react-email.tsx`:
- Around line 66-69: The sentence in monthly-chain-digest-react-email.tsx inside
the CampaignText block claims "32 doubling rounds x 2 referrals each =
4,300,000,000 humans" without a source; update the copy to include a citation or
hyperlink to the calculation (for example the same docs used by
post-vote-share.email.md) by adding a parenthetical link or footnote-style
reference next to the numeric claim, or replace the raw number with a short "see
calculation" link that points to the authoritative doc/markdown explaining the
doubling math; ensure the change is applied in the CampaignText component string
in monthly-chain-digest-react-email.tsx.

In `@packages/web/src/lib/tasks/task-assignment-notification-email.server.ts`:
- Around line 82-90: The preview render uses environment-dependent
getTaskUrl(SAMPLE_ASSIGNMENT_INPUT.id) inside renderReact which causes
non-deterministic hosts; change the preview to supply a fixed host when building
the sample URL (e.g., call getTaskUrl with a preview/host override or construct
the URL using a constant preview host) so TaskAssignmentReactEmail always
receives a deterministic taskUrl for previewing; update the renderReact
invocation that creates TaskAssignmentReactEmail (and adjust
SAMPLE_ASSIGNMENT_INPUT usage if needed) to pass a known host like "example.com"
or a dedicated PREVIEW_HOST constant.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c7f90b8d-18d0-412c-8a66-ddd4fee71f57

📥 Commits

Reviewing files that changed from the base of the PR and between f5f002f and ef0d55f.

📒 Files selected for processing (90)
  • .claude/hooks/pre-commit-checklist.mjs
  • .claude/hooks/verify-ui-changes.mjs
  • .claude/settings.json
  • .codex/config.toml
  • .env.example
  • .github/workflows/ci.yml
  • .gitignore
  • .husky/pre-commit
  • TODO.md
  • packages/data/src/parameters/parameters-calculations-citations.ts
  • packages/db/prisma/seed.ts
  • packages/db/scripts/sync-managed-data.ts
  • packages/db/src/managed-data/managed-task-triggers.test.ts
  • packages/db/src/managed-data/managed-task-triggers.ts
  • packages/web/e2e/email-screenshots.spec.ts
  • packages/web/package.json
  • packages/web/scripts/build-visual-review.mjs
  • packages/web/scripts/render-emails-to-markdown.ts
  • packages/web/scripts/render-pages-to-markdown.ts
  • packages/web/src/app/api/email/unsubscribe/route.test.ts
  • packages/web/src/app/api/email/unsubscribe/route.ts
  • packages/web/src/app/court/page.logged-out.md
  • packages/web/src/app/dashboard/page.logged-out.md
  • packages/web/src/app/dev/email/[template]/route.ts
  • packages/web/src/app/donate/page.logged-out.md
  • packages/web/src/app/employees/page.logged-out.md
  • packages/web/src/app/endorse/page.logged-out.md
  • packages/web/src/app/feedback/page.logged-out.md
  • packages/web/src/app/humanity-v-government/page.logged-out.md
  • packages/web/src/app/people/page.logged-out.md
  • packages/web/src/app/plaintiffs/page.logged-out.md
  • packages/web/src/app/plaintiffs/page.tsx
  • packages/web/src/app/privacy/page.logged-out.md
  • packages/web/src/app/signatories/page.logged-out.md
  • packages/web/src/app/tasks/page.logged-out.md
  • packages/web/src/app/terms/page.logged-out.md
  • packages/web/src/app/treaty/page.logged-out.md
  • packages/web/src/components/dashboard/DashboardShareCard.tsx
  • packages/web/src/components/ui/latex.tsx
  • packages/web/src/lib/__tests__/daily-activity-digest.server.test.ts
  • packages/web/src/lib/__tests__/email-urls.test.ts
  • packages/web/src/lib/__tests__/mcp-server.task-email.integration.test.ts
  • packages/web/src/lib/__tests__/resend.test.ts
  • packages/web/src/lib/email/__tests__/magic-link-email.test.ts
  • packages/web/src/lib/email/__tests__/monthly-chain-digest-email.test.ts
  • packages/web/src/lib/email/__tests__/post-vote-share-email.test.ts
  • packages/web/src/lib/email/__tests__/referral-first-conversion-email.test.ts
  • packages/web/src/lib/email/__tests__/task-notification.test.ts
  • packages/web/src/lib/email/__tests__/wishonia-signature.test.ts
  • packages/web/src/lib/email/email-urls.ts
  • packages/web/src/lib/email/magic-link-email.ts
  • packages/web/src/lib/email/magic-link-react-email.tsx
  • packages/web/src/lib/email/magic-link-render.ts
  • packages/web/src/lib/email/magic-link.email.md
  • packages/web/src/lib/email/monthly-chain-digest-email.ts
  • packages/web/src/lib/email/monthly-chain-digest-react-email.tsx
  • packages/web/src/lib/email/monthly-chain-digest.email.md
  • packages/web/src/lib/email/monthly-chain-digest.server.ts
  • packages/web/src/lib/email/post-vote-share-email.ts
  • packages/web/src/lib/email/post-vote-share-react-email.tsx
  • packages/web/src/lib/email/post-vote-share.email.md
  • packages/web/src/lib/email/preview-envelope.ts
  • packages/web/src/lib/email/preview-registry.ts
  • packages/web/src/lib/email/react-email-components.tsx
  • packages/web/src/lib/email/referral-first-conversion-email.ts
  • packages/web/src/lib/email/referral-first-conversion-react-email.tsx
  • packages/web/src/lib/email/referral-first-conversion.email.md
  • packages/web/src/lib/email/render-react-email.ts
  • packages/web/src/lib/email/resend.ts
  • packages/web/src/lib/email/send-deduped-email.server.ts
  • packages/web/src/lib/email/share-footer.ts
  • packages/web/src/lib/email/task-notification.ts
  • packages/web/src/lib/email/unsub-url.ts
  • packages/web/src/lib/email/wishonia-signature.ts
  • packages/web/src/lib/env.ts
  • packages/web/src/lib/humanity-manager-promotion.tsx
  • packages/web/src/lib/referral-invitations.server.ts
  • packages/web/src/lib/referral-url-helpers.server.ts
  • packages/web/src/lib/site.ts
  • packages/web/src/lib/tasks/__tests__/task-assignment-notification-email.test.ts
  • packages/web/src/lib/tasks/__tests__/task-comment-notification-email.test.ts
  • packages/web/src/lib/tasks/task-assignment-notification-email.server.ts
  • packages/web/src/lib/tasks/task-assignment-notifications.server.ts
  • packages/web/src/lib/tasks/task-assignment-react-email.tsx
  • packages/web/src/lib/tasks/task-assignment.email.md
  • packages/web/src/lib/tasks/task-comment-notification-email.server.ts
  • packages/web/src/lib/tasks/task-comment-notification-react-email.tsx
  • packages/web/src/lib/tasks/task-comment-notification.email.md
  • packages/web/src/lib/tasks/task-comment-notifications.server.ts
  • packages/web/src/lib/url.ts
💤 Files with no reviewable changes (7)
  • packages/db/scripts/sync-managed-data.ts
  • packages/web/src/lib/tests/daily-activity-digest.server.test.ts
  • packages/db/prisma/seed.ts
  • .claude/settings.json
  • packages/web/src/lib/env.ts
  • .env.example
  • packages/web/src/lib/url.ts

Comment thread packages/web/src/app/api/email/unsubscribe/route.ts
Comment thread packages/web/src/app/donate/page.logged-out.md Outdated
Comment thread packages/web/src/app/endorse/page.logged-out.md
Comment thread packages/web/src/app/humanity-v-government/page.logged-out.md
Comment thread packages/web/src/app/humanity-v-government/page.logged-out.md Outdated
Comment thread packages/web/src/app/signatories/page.logged-out.md Outdated
Comment thread packages/web/src/app/signatories/page.logged-out.md
Comment thread packages/web/src/lib/email/react-email-components.tsx
@argos-ci
Copy link
Copy Markdown

argos-ci Bot commented May 13, 2026

The latest updates on your projects. Learn more about Argos notifications ↗︎

Build Status Details Updated (UTC)
route-visuals (Inspect) ⚠️ Changes detected (Review) 14 changed May 13, 2026, 6:14 AM

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 13, 2026

Preview deploy — one-click review links

Latest preview: https://optimitron-3whskjppn-mike-p-sinns-projects.vercel.app

Page State What changed
/dashboard demo logged-in components/dashboard/DashboardShareCard.tsx
/plaintiffs logged-out app/plaintiffs/page.tsx

?login=demo signs you in as the demo user; ?logout=1 clears the session. Updated automatically when this PR's preview deploys.

@claude
Copy link
Copy Markdown

claude Bot commented May 13, 2026

Code review: 1 CLAUDE.md visual style violation in humanity-manager-promotion.tsx. Lines 128 and 188 use opacity modifiers (text-[var(--treaty-ink)]/60 and /70) in browser render paths. Per CLAUDE.md, use text-muted-foreground instead. No bugs or security issues found.

1. Copilot — humanity-manager-promotion.tsx coupling:
   The shared module imported `Text` from @react-email/components,
   which got bundled into the client when DashboardShareCard ("use
   client") imported the module. Replaced with plain `<p>` + inline
   styles (Text was just a styled <p> anyway). Email renders
   identically; client bundle no longer drags in React Email.

2. Copilot — markdown walker concatenation:
   toMarkdown joined adjacent element fragments without whitespace,
   merging words like `1% TREATYRedirect ...`. Added an appendFragment
   helper that inserts a single space when the buffer ends with a word
   character AND the new fragment begins with one. Downstream
   `\s+`->` ` collapse handles duplicate spaces. Applied to both
   render-pages-to-markdown.ts and render-emails-to-markdown.ts.
   Verified on /signatories: `[#1Mike Sinn](/people/mike)26.0...`
   became `[#1 Mike Sinn](/people/mike)26.0 INVERSE KILLS 26.0 ...`.

3. ChatGPT Codex bot (P1) — RFC 8058 multipart unsubscribe:
   The unsubscribe route only accepted application/x-www-form-urlencoded
   POST bodies. RFC 8058 §3 lists multipart/form-data as equally valid
   for one-click unsubscribe. Mailbox providers using multipart got a
   400 instead of unsubscribing — a deliverability regression.
   parseFormBody now branches on content-type and uses request.formData()
   for multipart, request.text() + URLSearchParams for urlencoded.

Tests: 22 email/task test files pass. Typecheck clean.

Co-Authored-By: Codex CLI (gpt-5-codex) <noreply@openai.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/web/scripts/render-emails-to-markdown.ts`:
- Around line 50-54: The code calls
page.goto(`${BASE}/dev/email/${slug}?raw=1&full=1`, ...) and then immediately
runs page.evaluate(), which can snapshot HTTP 4xx/5xx error pages because
page.goto may resolve without throwing; modify the flow to capture the Response
returned by page.goto (assign it to a variable), check response.status() and
reject or throw if status >= 400 (or return early) before calling
page.evaluate(), e.g., use the Response to log or surface the error for the
given slug and avoid writing an invalid snapshot; update references around
page.goto, BASE, slug, and page.evaluate accordingly.

In `@packages/web/scripts/render-pages-to-markdown.ts`:
- Around line 297-317: hasContainingTag currently treats table cells (td/th) as
containing tags even when they belong to a layout table, causing nested blocks
inside role="presentation" tables to be flattened; update hasContainingTag to
ignore td/th (and similar cell elements) when their nearest ancestor table is a
presentation table by checking the cell's closest table ancestor with
isLayoutTable and treating those cells as non-containing so their child blocks
are still visited; keep references to isLayoutTable, hasContainingTag, root and
the querySelectorAll(tags) loop when making the change.
- Around line 220-328: The current logic collapses whitespace for all tags
because `pre` is in `tags` but falls through to the generic path that does
`.replace(/\s+/g, " ")`; modify the main walker (the loop over
`root.querySelectorAll(tags)` and the branch logic around `toMarkdown`) to
special-case tag === "pre" (and/or `<pre><code>` variants) and render a fenced
code block that preserves the original whitespace and line breaks (use the
element's textContent or innerText for content), optionally derive a language
from a child `<code class="language-...">`, and ensure any triple-backticks
inside the content are escaped or the fence length increased so the markdown
remains valid; keep other tags unchanged (retain `toMarkdown`,
`tableToMarkdown`, `applyTransform`, `hasContainingTag`).
- Around line 176-179: The current expression for redirectedFromStatus awaits
neither the redirected request nor its response, so the ternary checks a Promise
(always truthy) and may call .status() on null; to fix, first await
response.request().redirectedFrom() into a variable (e.g., redirectedReq), then
if redirectedReq is non-null await redirectedReq.response() into redirectedResp,
and finally set redirectedFromStatus to redirectedResp ? redirectedResp.status()
: null; update references around redirectedFromStatus,
response.request().redirectedFrom(), .response(), and .status().
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0e48862f-5728-4e2f-8bb0-15bd9b3a34e1

📥 Commits

Reviewing files that changed from the base of the PR and between ef0d55f and 5e7c830.

📒 Files selected for processing (6)
  • .claude/hooks/verify-ui-changes.mjs
  • packages/web/scripts/render-emails-to-markdown.ts
  • packages/web/scripts/render-pages-to-markdown.ts
  • packages/web/src/app/api/email/unsubscribe/route.ts
  • packages/web/src/app/signatories/page.logged-out.md
  • packages/web/src/lib/humanity-manager-promotion.tsx
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/web/src/app/signatories/page.logged-out.md
  • packages/web/src/app/api/email/unsubscribe/route.ts
  • packages/web/src/lib/humanity-manager-promotion.tsx
  • .claude/hooks/verify-ui-changes.mjs

Comment thread packages/web/scripts/render-emails-to-markdown.ts Outdated
Comment thread packages/web/scripts/render-pages-to-markdown.ts Outdated
Comment thread packages/web/scripts/render-pages-to-markdown.ts
Comment thread packages/web/scripts/render-pages-to-markdown.ts
mikepsinn and others added 2 commits May 13, 2026 00:40
User flagged repetitive copy on /plaintiffs:
- "30 humans already named. Their names are evidence in
  Humanity v. Government." duplicates the heading right below
  it (PUBLIC PLAINTIFFS → PLAINTIFFS IN HUMANITY V. GOVERNMENT)
  and the face grid IS the evidence. Removed the paragraph.
- "Use the name that belongs on the court record." under
  WHO SHOULD BE A PLAINTIFF was load-bearing on no decision;
  removed.
- PersonFaceTile hover overlay rendered the lifeStatus eyebrow
  on every tile ("PLAINTIFF IN THE CASE" / "PLAINTIFF
  REPRESENTED BY ANOTHER HUMAN"). Tautological on /plaintiffs.
  Now only shown for DECEASED (the high-signal "PLAINTIFF WHO
  CAN NO LONGER SIGN").
- represented-people.server filtered the gallery to exclude
  parties whose createdBy is the demo account. Screenshot
  tooling logs in as demo@thinkbynumbers.org and leaves rows
  like "CROP VIEWPORT HUMAN 1778...ADDED BY DEMO USER" that
  were polluting the public list with 20+ junk tiles.

CodeRabbit triage on PR #79:
- Token redaction in unsubscribe error log (id 3231615739):
  VALID. console.error logged `parsed`, which contains the
  signed unsubscribe token. Destructured `token` out before
  logging.
- task-comment-notification-email.test.ts "Open the task:"
  assertion (id 3231615786): VALID. Test guarded against a
  string variant the code never produced (colon-suffixed),
  so a regression to plain "Open the task" would pass. Dropped
  the colon.

Spacing nits 3231615747 / 3231615764 / 3231615767 / 3231615774:
already fixed by the appendFragment work in 5e7c830 — the
regenerated .md snapshots (donate, humanity-v-government,
signatories, plaintiffs, tasks, treaty) ship in this commit
and reflect the fix.

Comments resolved without code change (see thread replies):
- 3231615750 (endorse #organization-endorsement-form):
  INVALID. Anchor exists at page.tsx:181; markdown walker
  doesn't emit divs.
- 3231615757 (humanity-v-government #verdict): INVALID.
  Anchor exists at page.tsx:165.
- 3231615777 (signatories localhost URLs): INVALID. Snapshot
  was rendered against http://127.0.0.1:3001; live app uses
  getBaseUrl() which returns https://warondisease.org in
  production (verified against email snapshots).
- 3231615781 / 3231615783 (hardcoded "32 / 2 / 4,300,000,000"
  in email templates): SKIPPED. These are derived arithmetic
  identities in prose (2^32 ≈ 4.3B), not cited statistics.
  CLAUDE.md's <ParameterValue> rule targets sourceable
  numbers; wrapping arithmetic in parameters adds indirection
  without improving auditability.

Tests: 141 web tests pass. Typecheck clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1. emails renderer: 4xx/5xx slipped through. page.goto resolves on
   HTTP errors without throwing, so a broken /dev/email/<slug>
   route used to write a junk snapshot. Now throws with the
   status code so the failure surfaces. (3231738933)

2. pages renderer: redirected-from-status check was wrong. The
   code branched on the truthiness of `request.response()` —
   which returns a Promise (always truthy) — instead of the
   awaited Response (may be null). The `!` non-null assertions
   masked the type-level mistake. Restructured to await once,
   then read status. (3231738940)

3. pages renderer: <pre> blocks fell through to the generic
   `.replace(/\s+/g, " ")` path and collapsed all whitespace.
   Now emits a fenced code block, matching the emails renderer
   already does. (3231738964)

4. pages renderer: presentation-table cells (`<td>`/`<th>` under
   `role="presentation"`) were treated as containing tags, so
   nested <p>/<a> children got flattened into a single bullet
   and lost their structure. Now skipped explicitly so the
   walker descends through them, matching emails renderer.
   (3231738972)

Smoke test: re-rendered /plaintiffs snapshot is unchanged after
the refactor.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
page.goto resolves on HTTP errors without throwing, so a broken
/dev/email/<slug> route used to silently write a junk snapshot.
Now throws with the status code so the failure surfaces.

(CodeRabbit 3231738933 — second half of the renderer-bugs commit
that was missed because of the error-swallow hook block-retry.)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/web/src/app/donate/page.logged-out.md`:
- Around line 93-94: Replace the unresolved placeholder tokens in the donate
copy: find the string "To pass the treaty: reach humans at $ each." and replace
"$ each" with a concrete default value or an explicit variable label (e.g., "$X
each" or "{{cost_per_person}} each"); similarly find "At % success and 1.00x
treaty scale" and replace "% success" with a concrete default percentage or an
explicit variable label (e.g., "Y% success" or "{{success_rate}}"). Update the
surrounding sentences in page.logged-out.md so the text no longer shows raw
placeholders and reads correctly for users while preserving the original context
and formatting.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 26172520-2ec0-473b-93cf-2de67de89aca

📥 Commits

Reviewing files that changed from the base of the PR and between 5e7c830 and 8dfcc8b.

📒 Files selected for processing (11)
  • packages/web/src/app/api/email/unsubscribe/route.ts
  • packages/web/src/app/donate/page.logged-out.md
  • packages/web/src/app/humanity-v-government/page.logged-out.md
  • packages/web/src/app/plaintiffs/page.logged-out.md
  • packages/web/src/app/plaintiffs/page.tsx
  • packages/web/src/app/tasks/page.logged-out.md
  • packages/web/src/app/treaty/page.logged-out.md
  • packages/web/src/components/people/PersonFaceTile.tsx
  • packages/web/src/components/people/RepresentedPersonConversionForm.tsx
  • packages/web/src/lib/represented-people.server.ts
  • packages/web/src/lib/tasks/__tests__/task-comment-notification-email.test.ts
💤 Files with no reviewable changes (1)
  • packages/web/src/components/people/RepresentedPersonConversionForm.tsx
✅ Files skipped from review due to trivial changes (1)
  • packages/web/src/components/people/PersonFaceTile.tsx
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/web/src/app/tasks/page.logged-out.md
  • packages/web/src/app/api/email/unsubscribe/route.ts
  • packages/web/src/lib/tasks/tests/task-comment-notification-email.test.ts
  • packages/web/src/app/plaintiffs/page.tsx

The existing VONNEGUT/blather gate said 'read the rendered copy,
goal: a 5th grader follows it' — too vague. I shipped the
plaintiffs page anyway with PLAINTIFF IN THE CASE × 24,
30 humans already named above the matching heading + grid, and
'Use the name that belongs on the court record' under
WHO SHOULD BE A PLAINTIFF?

Replaced the vague goal with a numbered checklist that names the
specific failure modes I missed. Advisory (non-blocking) — the
discipline is mine; the hook is a reminder, not a regex.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two false-positive sources caught when comparing /people, /employees,
and /donate snapshots against the rendered page:

1. Responsive duplicates. Tailwind `lg:hidden` paired with
   `hidden lg:block` renders both copies in the DOM but only one is
   visible at the current viewport. The walker emitted both, so
   /people had 'Public official / LY / 1 task' (mobile) AND
   'Public official / LY' + '1 task' (desktop) per row × 189 rows.
   Fix: filter `display: none` and `visibility: hidden` from
   computed style, both for the toMarkdown descent and the outer
   block walker (including any hidden ancestor in the chain).

2. sr-only labels. DonationImpactCalculator emits every slider
   label twice — visible <label> + <span class="sr-only"> paired
   with the input — to give screen readers an accessible name for
   the input. Tailwind's sr-only uses 1px clipping, NOT display:none,
   so the previous filter didn't catch it. Added a class-name check
   for sr-only and its responsive variants.

Caveat: task-row's overlay Link puts the leader name ONLY in an
sr-only span (the visible name lives in a plain <div>, which isn't
in the walker's tags list). Blindly filtering sr-only made
/employees lose every leader name. Added a fallback: if a Link's
inner text is empty after filtering, re-descend with the filter off
so the accessible name still ships.

Net deltas after the fix (vs HEAD):
  donate    -3 lines (label duplicates gone)
  employees -1 line  (one mobile-only entry)
  people    -10 lines (responsive metadata duplicate × first 10 rows shown)
  plaintiffs no change (sanity check)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
packages/web/src/app/donate/page.logged-out.md (1)

90-91: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Replace unresolved runtime placeholders in published copy.

Line 90 and Line 91 still render $ each and % success, which reads as broken content in a user-facing donation section. Use explicit defaults or concrete variable labels in the snapshot source.

Suggested fix
-- 11 To pass the treaty: reach humans at $ each.That makes the campaign cost $1,000,000,000. Everyone thinks this is crazy because everyone else thinks this is crazy. Right now every human who wants less war and disease assumes they are the weird one. The referendum is the moment they find out they are everyone.
-- 12 At % success and 1.00x treaty scale, the model gives $0.18 per healthy life-year.That is 503x the cost-effectiveness of bed nets at the live assumptions. Your calculator will display an error, emit a tiny electronic scream, and attempt to leave the desk. This is correct. The published skeptical case assumes a 99% chance humanity fumbles this and still comes out 503x better than bed nets, where bed nets cost $89/daly. This model suggests the treaty campaign may be the most cost-effective way to reduce human suffering per dollar spent. If that sounds insane, good. Change the assumptions or attack the citations.
+- 11 To pass the treaty: reach humans at $2.00 each.That makes the campaign cost $1,000,000,000. Everyone thinks this is crazy because everyone else thinks this is crazy. Right now every human who wants less war and disease assumes they are the weird one. The referendum is the moment they find out they are everyone.
+- 12 At 1% success and 1.00x treaty scale, the model gives $0.18 per healthy life-year.That is 503x the cost-effectiveness of bed nets at the live assumptions. Your calculator will display an error, emit a tiny electronic scream, and attempt to leave the desk. This is correct. The published skeptical case assumes a 99% chance humanity fumbles this and still comes out 503x better than bed nets, where bed nets cost $89/DALY. This model suggests the treaty campaign may be the most cost-effective way to reduce human suffering per dollar spent. If that sounds insane, good. Change the assumptions or attack the citations.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/web/src/app/donate/page.logged-out.md` around lines 90 - 91, The
markdown contains unresolved runtime placeholders "$ each" and "% success" in
the user-facing donation copy; update the snapshot source for page.logged-out.md
to replace those placeholders with concrete defaults or explicit variable labels
(e.g. "$X each" or "{{amount}} each" and "Y% success" or "{{successRate}}%") so
the rendered text never shows raw placeholders, and re-generate any
snapshot/tests that depend on this content; ensure the template or interpolation
code that produces this file (if present) uses fallback values for amount and
successRate to avoid runtime leakage.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/web/src/app/donate/page.logged-out.md`:
- Line 91: Replace the lowercase unit "daly" with the standardized uppercase
"DALY" in the user-facing copy: find the string "$89/daly" in the donate page
content and change it to "$89/DALY" so the metric matches the rest of the page
that uses "DALY".

---

Duplicate comments:
In `@packages/web/src/app/donate/page.logged-out.md`:
- Around line 90-91: The markdown contains unresolved runtime placeholders "$
each" and "% success" in the user-facing donation copy; update the snapshot
source for page.logged-out.md to replace those placeholders with concrete
defaults or explicit variable labels (e.g. "$X each" or "{{amount}} each" and
"Y% success" or "{{successRate}}%") so the rendered text never shows raw
placeholders, and re-generate any snapshot/tests that depend on this content;
ensure the template or interpolation code that produces this file (if present)
uses fallback values for amount and successRate to avoid runtime leakage.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 18ae81ad-70ba-458b-a631-a80f265c252b

📥 Commits

Reviewing files that changed from the base of the PR and between bed22b3 and 8aa60da.

📒 Files selected for processing (5)
  • packages/web/scripts/render-emails-to-markdown.ts
  • packages/web/scripts/render-pages-to-markdown.ts
  • packages/web/src/app/donate/page.logged-out.md
  • packages/web/src/app/employees/page.logged-out.md
  • packages/web/src/app/people/page.logged-out.md
✅ Files skipped from review due to trivial changes (1)
  • packages/web/src/app/employees/page.logged-out.md
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/web/src/app/people/page.logged-out.md
  • packages/web/scripts/render-pages-to-markdown.ts
  • packages/web/scripts/render-emails-to-markdown.ts

- 9 150,000 people die from disease every day.That is 104 every minute. Every minute of delay, 104 humans permanently stop. With the current live assumptions, the model estimates 107,455,177 expected deaths prevented and 2,204,450,326 years of suffering and disability prevented.
- 10 Humans spend $2.72 trillion every year on stuff designed specifically to make humans stop being alive. Move % to high-efficiency pragmatic clinical trials.Government spending on clinical trials is 604 times less than military spending. Your chance of dying from disease is 100%. Your current budget does not reflect this. Earth owns 12,200 nuclear warheads. 100 is enough for nuclear winter. We have 122 apocalypses' worth of weapons. Keep the deterrent. Spend one slice curing every disease.
- 11 To pass the treaty: reach humans at $ each.That makes the campaign cost $1,000,000,000. Everyone thinks this is crazy because everyone else thinks this is crazy. Right now every human who wants less war and disease assumes they are the weird one. The referendum is the moment they find out they are everyone.
- 12 At % success and 1.00x treaty scale, the model gives $0.18 per healthy life-year.That is 503x the cost-effectiveness of bed nets at the live assumptions. Your calculator will display an error, emit a tiny electronic scream, and attempt to leave the desk. This is correct. The published skeptical case assumes a 99% chance humanity fumbles this and still comes out 503x better than bed nets, where bed nets cost $89/daly. This model suggests the treaty campaign may be the most cost-effective way to reduce human suffering per dollar spent. If that sounds insane, good. Change the assumptions or attack the citations.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Normalize daly to DALY for metric consistency.

Line 91 uses $89/daly while the rest of the page uses DALY; this should be standardized in user-facing copy.

🧰 Tools
🪛 LanguageTool

[grammar] ~91-~91: Ensure spelling is correct
Context: ... than bed nets, where bed nets cost $89/daly. This model suggests the treaty campaig...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/web/src/app/donate/page.logged-out.md` at line 91, Replace the
lowercase unit "daly" with the standardized uppercase "DALY" in the user-facing
copy: find the string "$89/daly" in the donate page content and change it to
"$89/DALY" so the metric matches the rest of the page that uses "DALY".

@claude
Copy link
Copy Markdown

claude Bot commented May 13, 2026

test

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants