Skip to content

Add public profile task assignment#85

Merged
mikepsinn merged 22 commits into
mainfrom
feature/public-profile-task-assignment
May 19, 2026
Merged

Add public profile task assignment#85
mikepsinn merged 22 commits into
mainfrom
feature/public-profile-task-assignment

Conversation

@mikepsinn
Copy link
Copy Markdown
Owner

@mikepsinn mikepsinn commented May 17, 2026

Summary

  • add owner controls to public profile pages with profile visibility and public-profile sharing
  • add task assignment from public profile pages, including login redirect for signed-out users
  • extract the task creation dialog so the FAB and profile assignment flow share one implementation
  • keep private profiles hidden from non-owners while still letting owners view their own profile and tasks

Review URLs

Preview deployment: https://optimitron-web-git-feature-public-9f8c82-mike-p-sinns-projects.vercel.app

Changed states to review:

Visual review: https://mikepsinn.github.io/optimitron/pr-85/76f177c47d32/latest.html

Validation

  • git diff --check
  • pnpm --filter @optimitron/web typecheck:fast
  • pnpm --filter @optimitron/web test -- src/lib/__tests__/tasks.server.test.ts src/app/api/tasks/route.test.ts
  • pnpm --filter @optimitron/web copy:preview -- --routes=/profile,/people

Summary by CodeRabbit

  • New Features

    • Public profile owner controls: view/edit, copy public profile URL, and toggle profile visibility (disabled state supported).
    • Assign Task UI: in-profile "Assign Task" action and a reusable Create Task dialog (supports auto-open via URL).
    • Dashboard: new Humanity Manager status panel with K‑factor and downstream conversion surfaced.
  • Improvements

    • Navbar: authenticated menu shows Edit Profile and, when available, Public Profile link.
    • Magic-link email and treaty slider/post‑vote copy updated for clearer CTAs and messaging.

Review Change Stack

Copilot AI review requested due to automatic review settings May 17, 2026 17:51
@vercel
Copy link
Copy Markdown

vercel Bot commented May 17, 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 19, 2026 2:59am

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 17, 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

Adds owner-facing public-profile controls and privacy toggle, extracts a reusable CreateTaskDialog and PersonTaskAssignmentAction (with assignTask=1 auto-open), enforces person-profile visibility server-side with tests, updates Navbar/route links, refactors CampaignActionFab, revises visual-review tooling and PR preview packet + CI, updates magic-link email/messaging copy, adds Sentry preview auditing, and tweaks treaty slider UI and E2E expectations.

Changes

Public profiles and task assignment

Layer / File(s) Summary
People page owner controls & links
packages/web/src/app/people/[id]/page.tsx
Computes ownership and public-profile/assign-task URLs, conditionally renders PublicProfileOwnerControls, and wires PersonTaskAssignmentAction into referral and treaty UI.
Task assignment UI and dialog
packages/web/src/components/people/PersonTaskAssignmentAction.tsx, packages/web/src/components/tasks/CreateTaskDialog.tsx, packages/web/src/components/site/CampaignActionFab.tsx
Adds PersonTaskAssignmentAction with sign-in or dialog paths and auto-open via assignTask=1. Extracts CreateTaskDialog and delegates from CampaignActionFab.
Public profile owner controls
packages/web/src/components/profile/PublicProfileOwnerControls.tsx, packages/web/src/components/dashboard/PrivacyToggle.tsx
New owner controls section showing public profile URL, view/edit links, and PrivacyToggle (now a button with optional disabled) that PATCHes /api/dashboard/profile and refreshes on success.
Navbar & route link catalog
packages/web/src/components/Navbar.tsx, packages/web/src/lib/routes.ts, packages/web/src/components/Navbar.test.ts
Splits profileLink into editProfileLink and publicProfileLink, adds getAuthenticatedProfileLinks, updates navbar rendering and tests.
Server-side visibility enforcement & tests
packages/web/src/lib/tasks.server.ts, packages/web/src/lib/__tests__/tasks.server.test.ts
Includes isPublic in person select; getPersonTaskProfileData returns null for private profiles viewed by non-owners; tests cover owner vs non-owner cases.
Task-impact idempotent upserts
packages/web/src/lib/tasks/per-verified-voter-impact.server.ts, packages/web/src/lib/tasks/__tests__/per-verified-voter-impact.server.test.ts
Reworks impact sync to use Prisma upsert/updateMany for estimate sets, frames, and metrics; updates client type expectations and tests to assert upsert/update behavior.
Visual review build & PR preview packet tooling
packages/web/scripts/build-visual-review.mjs, .github/scripts/generate-pr-preview-links.mjs, .github/scripts/generate-pr-preview-links.test.mjs, .github/workflows/ci.yml
build-visual-review emits manifest.json and per-route authState/URL/context; generate-pr-preview-links renders a sticky PR review packet with preserved checklist state and auth-state preview links; tests and CI updated to run and consume these artifacts.
Sentry preview audit
.github/scripts/audit-sentry-preview.mjs, .github/scripts/audit-sentry-preview.test.mjs, .github/workflows/smoke-deploy.yml
Adds a script to query Sentry for preview-matching events, rendering a markdown report and CI step summary; tests validate matching logic; workflow runs the audit and posts PR comments.
E2E & screenshot updates
packages/web/e2e/utils/visual-routes.ts, packages/web/e2e/visual-regression.spec.ts, packages/web/e2e/smoke.spec.ts, packages/web/e2e/email-screenshots.spec.ts, packages/web/e2e/new-user-flow-screenshots.spec.ts
Adds authenticated /people/demo visual routes, adjusts visual-regression to wait for load, adds smoke test asserting demo login mints NextAuth cookie, and updates magic-link expected copy in screenshots.
Magic-link email & messaging
packages/web/src/lib/email/*, packages/web/src/lib/messaging.ts
Updates magic-link copy/subject to voting-focused text, sets MAGIC_LINK_PREVIEW.skipWishoniaSignature = true, passes skipWishoniaSignature in sendMagicLinkEmail, updates templates and tests, and revises VOTE_SECTION copy.
Humanity Manager status / k-factor
packages/web/src/lib/humanity-manager-status.server.ts, packages/web/src/lib/humanity-manager-status-content.tsx, packages/web/src/app/dashboard/page.tsx, packages/web/src/components/site/TreatyTaskDashboardClient.tsx
Adds getKFactorForUser, computes kFactor30d, threads humanityManagerStatus into the treaty dashboard client and UI, and updates types/tests to include kFactor30d.
Profile identity / dashboard payload
packages/web/src/lib/profile-identity.server.ts, packages/web/src/lib/dashboard.server.ts, packages/web/src/types/dashboard.ts
Includes downstreamConversionCount in profile/dashboard payloads and updates the DashboardUser type.
Treaty slider copy/UI tweaks
packages/web/src/components/landing/TreatyVoteFlow.tsx
Updates slider headline/prompt, drag tooltip wording/position, submit button label, and post-vote overlay email/save strings wired into AuthForm.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I polished links and toggles late at night,
A dialog popped to make task-assignments right,
Tests hopped in to guard private profiles tight,
Visual packets glimmered in CI’s light,
The rabbit shipped—hop!—the review is bright.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/public-profile-task-assignment

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 adds owner controls and task assignment to public profile pages. It introduces a privacy gate on getPersonTaskProfileData so private profiles are hidden from non-owners while still letting the owner view their own. The task creation form is extracted into a shared CreateTaskDialog component used by both the existing FAB and the new "Assign Task" action on a person's profile, with a signed-out flow that redirects to sign-in and auto-opens the dialog via ?assignTask=1 after login.

Changes:

  • Add isPublic selection and owner-only gate to getPersonTaskProfileData, with new unit tests for the private/owner cases.
  • Extract CreateTaskDialog so it can be reused by the FAB and a new PersonTaskAssignmentAction on profile pages (with sign-in redirect + auto-open).
  • Add PublicProfileOwnerControls (visibility toggle, share URL, view/edit links), wire it into /people/[id], add a public-profile link in the navbar side menu, and adjust PrivacyToggle styling/disabled state.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated no comments.

Show a summary per file
File Description
packages/web/src/lib/tasks.server.ts Select isPublic and gate private profiles from non-owners.
packages/web/src/lib/tests/tasks.server.test.ts Tests for private/owner visibility behavior.
packages/web/src/components/tasks/CreateTaskDialog.tsx New shared task creation dialog with optional fixed assignee and 401 sign-in redirect.
packages/web/src/components/site/CampaignActionFab.tsx Refactored to use shared CreateTaskDialog.
packages/web/src/components/profile/PublicProfileOwnerControls.tsx New owner-only profile controls (visibility, share URL, links).
packages/web/src/components/people/PersonTaskAssignmentAction.tsx New action with sign-in fallback and auto-open via assignTask=1.
packages/web/src/components/Navbar.tsx Adds public-profile link in the authenticated side menu.
packages/web/src/components/dashboard/PrivacyToggle.tsx Becomes a button with disabled state; restyled.
packages/web/src/app/people/[id]/page.tsx Renders owner controls and the assign-task action.
packages/web/e2e/visual-regression.spec.ts Adds a load wait before screenshotting.
packages/web/e2e/utils/visual-routes.ts New visual routes for owner view and assign dialog.

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

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

🤖 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/components/Navbar.tsx`:
- Around line 80-82: Replace the manual people URL construction for
publicProfileHref with the shared helper: call getPersonHref(user) (from
`@/lib/person-href`) when user and user.personId exist instead of building
`${ROUTES.people}/${user.handle ?? user.personId}`; ensure getPersonHref is
imported and used in the publicProfileHref assignment (and remove or stop using
ROUTES.people for this case).

In `@packages/web/src/components/profile/PublicProfileOwnerControls.tsx`:
- Line 113: In PublicProfileOwnerControls replace the hardcoded Tailwind color
class on the error paragraph (the <p> rendering {error}) with the project
semantic error/destructive token class; locate the p with className "mt-2
text-sm font-bold text-red-700" and change "text-red-700" to the app’s semantic
error token (e.g., the project's destructive/error text utility) so the
component uses theme tokens instead of a hardcoded palette value.

In `@packages/web/src/components/tasks/CreateTaskDialog.tsx`:
- Around line 233-237: Cancel currently only calls onOpenChange(false) so the
form's local state persists; update the Button onClick handler to also reset the
dialog form state (e.g., call the form reset function) before closing.
Specifically, call the form reset helper used in this component (for example
form.reset() or reset()) and any error/field-clear helpers as needed, then call
onOpenChange(false) so the dialog closes with fresh values and no stale errors.
🪄 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: 15e48bd8-8393-441d-a24e-c38f5a4f1120

📥 Commits

Reviewing files that changed from the base of the PR and between 1b63670 and 28a28ab.

📒 Files selected for processing (11)
  • packages/web/e2e/utils/visual-routes.ts
  • packages/web/e2e/visual-regression.spec.ts
  • packages/web/src/app/people/[id]/page.tsx
  • packages/web/src/components/Navbar.tsx
  • packages/web/src/components/dashboard/PrivacyToggle.tsx
  • packages/web/src/components/people/PersonTaskAssignmentAction.tsx
  • packages/web/src/components/profile/PublicProfileOwnerControls.tsx
  • packages/web/src/components/site/CampaignActionFab.tsx
  • packages/web/src/components/tasks/CreateTaskDialog.tsx
  • packages/web/src/lib/__tests__/tasks.server.test.ts
  • packages/web/src/lib/tasks.server.ts

Comment thread packages/web/src/components/Navbar.tsx
Comment thread packages/web/src/components/profile/PublicProfileOwnerControls.tsx Outdated
Comment thread packages/web/src/components/tasks/CreateTaskDialog.tsx
@claude
Copy link
Copy Markdown

claude Bot commented May 17, 2026

Code review

Four CLAUDE.md compliance issues found. No bugs or security issues.


1. Navbar.tsx — manual URL construction violates Display Identity rules

File: packages/web/src/components/Navbar.tsx

+  const publicProfileHref = user?.personId
+    ? `${ROUTES.people}/${user.handle ?? user.personId}`
+    : null;

CLAUDE.md rule (Display Identity: Person owns it):

URLs: getPersonHref(person) from @/lib/person-href. Never /people/${id}.

The Navbar re-implements the handle-vs-id fallback inline rather than calling the canonical helper. The same PR correctly uses getPersonHref(person) in people/[id]/page.tsx. The Navbar should call getUserPersonHref(user) (exported from @/lib/person-href) or receive the pre-resolved href as a prop.

  const publicProfileHref = user?.personId
    ? getUserPersonHref(user)
    : null;

2. PublicProfileOwnerControls.tsxtext-red-700 on a public treaty surface

File: packages/web/src/components/profile/PublicProfileOwnerControls.tsx

<p className="mt-2 text-sm font-bold text-red-700">{error}</p>

CLAUDE.md rule (Visual Style Rules):

Never use: Tailwind color scales (bg-emerald-100, text-gray-500) → semantic or treaty tokens

CreateTaskDialog (also introduced in this PR) correctly uses text-destructive for its error paragraph. This should match.

<p className="mt-2 text-sm font-bold text-destructive">{error}</p>

3. PublicProfileOwnerControls.tsx — voice/copy violations

File: packages/web/src/components/profile/PublicProfileOwnerControls.tsx

This is how other humans help you figure out the most valuable action you can take to maximize humanity's median income and healthy life expectancy. Make it public to get help from your network and the world, or keep it private if you prefer. You can change this anytime.

CLAUDE.md rules (Wishonia: Voice of the Site):

Short sentences — punchy. Declarative.
Data-first — lead with specific numbers, costs, percentages, or ROI ratios.
No blather. State the fact, state the action, stop.

Three compound sentences of reassurance copy with no numbers. "Get help from your network and the world" reads like LinkedIn onboarding. A compliant version would open with a specific number and drop the soft opt-in framing, e.g. "8 billion people could see your task list. Zero are helping you yet."

Also: CLAUDE.md requires calling mcp__optimitron-tasks__searchManual before writing user-facing copy. The manual may have canonical phrasing for this toggle.


4. page.logged-out.md snapshot not regenerated

The PR adds new UI to /people/[id] (owner controls, task assignment button, restructured CTA layout) but the generated packages/web/src/app/people/[id]/page.logged-out.md snapshot was not updated.

CLAUDE.md rule:

Never hand-edit page.logged-out.md / *.email.md snapshots. They're generated. After UI/copy changes run pnpm --filter @optimitron/web copy:preview.

Run pnpm --filter @optimitron/web copy:preview and commit the updated snapshot.


Reviewed by Claude — checked for bugs and CLAUDE.md compliance.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 17, 2026

PR review packet

Start here

  • 🖼️ Visual review
  • 🚀 Preview deployment
  • ☝️ Cmd/Ctrl-click review links to keep this PR open.
  • 🔑 ?login=demo signs in as the demo user; ?logout=1 clears the session.
  • 💬 For a visual problem, use the comment button in latest.html or reply here with @claude and the checklist item.

Review checklist

  • 🖼️ Court - logged-out 1 changed screenshots - open page
  • 🖼️ Create Task Dialog Person - demo logged-in 2 changed / 2 missing screenshots - open page
  • 🖼️ Dashboard - demo logged-in 2 changed screenshots - open page
  • 🖼️ Email Magic Link - logged-out 2 changed screenshots
  • 🖼️ Home - logged-out 2 changed screenshots - open page
  • 🖼️ Love - logged-out 2 changed / 2 missing screenshots - open page
  • 🖼️ Organization Iam Public - logged-out 2 changed screenshots - open page
  • 🖼️ Organization Iam Survey - logged-out 2 changed screenshots - open page
  • 🖼️ People - logged-out 1 changed screenshots - open page
  • 🖼️ People Demo Assign Dialog - demo logged-in 2 changed / 2 missing screenshots - open page
  • 🖼️ People Demo Owner - demo logged-in 2 changed / 2 missing screenshots - open page
  • 🖼️ People Mike - logged-out 2 changed screenshots - open page
  • 🖼️ Poster - logged-out 2 changed / 2 missing screenshots - open page
  • 🖼️ Profile - demo logged-in 2 changed screenshots - open page
  • 🖼️ Referendum One Percent Treaty - logged-out 2 changed / 2 missing screenshots - open page
  • 🖼️ Side Menu - logged-out 1 changed screenshots - open page
  • 🖼️ Side Menu Auth - demo logged-in 2 changed screenshots - open page
  • 🖼️ Signatories - logged-out 2 changed screenshots - open page
  • 🖼️ Tools - logged-out 2 changed screenshots - open page
  • 🖼️ Treaty - logged-out 1 changed screenshots - open page
  • 🖼️ Treaty Auth - demo logged-in 1 changed screenshots - open page
  • 🖼️ Vote - logged-out 2 changed screenshots - open page
  • 🚀 / - logged-out preview - components/landing/TreatyVoteFlow.tsx, components/site/CampaignActionFab.tsx, components/site/OnePercentTreatyLandingPage.tsx, components/site/TreatyTaskDashboardClient.tsx
  • 🚀 /agencies/dcongress/referendums - logged-out preview - app/agencies/dcongress/referendums/[slug]/page.tsx
  • 🚀 /dashboard - demo logged-in preview - app/dashboard/page.tsx, components/dashboard/DashboardShareCard.tsx, components/dashboard/HumanityManagerStatusPanel.test.tsx, components/dashboard/PrivacyToggle.tsx (+2 more)
  • 🚀 /love - logged-out preview - app/love/page.tsx
  • 🚀 /people - logged-out preview - app/people/[id]/page.tsx
  • 🚀 /poster - logged-out preview - app/poster/page.tsx
  • 🚀 /profile - demo logged-in preview - app/profile/page.tsx
  • 🚀 /tasks - logged-out preview - components/tasks/CreateTaskDialog.tsx, components/tasks/PublicProfileTaskSection.test.tsx, components/tasks/PublicProfileTaskSection.tsx
  • 🚀 /tasks - demo logged-in preview - components/tasks/CreateTaskDialog.tsx, components/tasks/PublicProfileTaskSection.test.tsx, components/tasks/PublicProfileTaskSection.tsx
  • 🚀 /tools - logged-out preview - app/tools/page.tsx
  • 🚀 /treaty - logged-out preview - components/site/CampaignActionFab.tsx, components/site/OnePercentTreatyLandingPage.tsx, components/site/TreatyTaskDashboardClient.tsx, components/treaty/TreatyNameSignatureBox.tsx
Changed files considered
  • .agents/skills/h2ewd-copy/SKILL.md
  • .agents/skills/h2ewd-copy/agents/openai.yaml
  • .claude/codex-delegation.md
  • .claude/hooks/verify-ui-changes.mjs
  • .claude/plans/referred-voters-list.md
  • .claude/plans/thank-and-recruit-dispatch.md
  • .claude/settings.json
  • .codex/agents/voice-critic.toml
  • .codex/config.toml
  • .github/scripts/audit-sentry-preview.mjs
  • .github/scripts/audit-sentry-preview.test.mjs
  • .github/scripts/generate-pr-preview-links.mjs
  • .github/scripts/generate-pr-preview-links.test.mjs
  • .github/workflows/ci.yml
  • .github/workflows/smoke-deploy.yml
  • .husky/pre-commit
  • AGENTS.md
  • CLAUDE.md
  • TODO.md
  • packages/web/AGENTS.md
  • packages/web/e2e/email-screenshots.spec.ts
  • packages/web/e2e/new-user-flow-screenshots.spec.ts
  • packages/web/e2e/smoke.spec.ts
  • packages/web/e2e/utils/visual-routes.ts
  • packages/web/e2e/visual-regression.spec.ts
  • packages/web/package.json
  • packages/web/scripts/build-visual-review.mjs
  • packages/web/scripts/copy-review-gate.ts
  • packages/web/scripts/smoke-deploy.mjs
  • packages/web/src/app/agencies/dcongress/referendums/[slug]/page.tsx
  • packages/web/src/app/api/people/search/route.test.ts
  • packages/web/src/app/api/people/search/route.ts
  • packages/web/src/app/api/tasks/route.test.ts
  • packages/web/src/app/api/tasks/route.ts
  • packages/web/src/app/court/page.logged-out.md
  • packages/web/src/app/dashboard/page.tsx
  • packages/web/src/app/declaration/page.logged-out.md
  • packages/web/src/app/love/love-client.tsx
  • packages/web/src/app/love/page.logged-out.md
  • packages/web/src/app/love/page.tsx
  • ...and 56 more

Updated automatically when this PR's preview or visual review reruns.

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.

🧹 Nitpick comments (1)
packages/web/e2e/smoke.spec.ts (1)

46-49: ⚡ Quick win

Replace cookie-name magic strings with an enum.

Use an enum for these cookie names to match the TypeScript guideline and avoid hardcoded literals.

♻️ Proposed refactor
-const NEXT_AUTH_COOKIE_NAMES = [
-  "next-auth.session-token",
-  "__Secure-next-auth.session-token",
-];
+enum NextAuthCookieName {
+  SessionToken = "next-auth.session-token",
+  SecureSessionToken = "__Secure-next-auth.session-token",
+}
+
+const NEXT_AUTH_COOKIE_NAMES: ReadonlyArray<NextAuthCookieName> = [
+  NextAuthCookieName.SessionToken,
+  NextAuthCookieName.SecureSessionToken,
+];

As per coding guidelines, **/*.{ts,tsx}: Use enums instead of magic strings in TypeScript code.

🤖 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/e2e/smoke.spec.ts` around lines 46 - 49, Replace the hardcoded
NEXT_AUTH_COOKIE_NAMES array with a TypeScript enum (e.g., NextAuthCookieName)
that lists "NextAuthSessionToken" and "__SecureNextAuthSessionToken" (or similar
descriptive enum keys) and then export an array of enum values to use where
NEXT_AUTH_COOKIE_NAMES is referenced; update any usages of
NEXT_AUTH_COOKIE_NAMES to reference the enum values (e.g.,
NextAuthCookieName.NextAuthSessionToken) to remove magic string literals and
keep type safety while preserving the original string values.
🤖 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.

Nitpick comments:
In `@packages/web/e2e/smoke.spec.ts`:
- Around line 46-49: Replace the hardcoded NEXT_AUTH_COOKIE_NAMES array with a
TypeScript enum (e.g., NextAuthCookieName) that lists "NextAuthSessionToken" and
"__SecureNextAuthSessionToken" (or similar descriptive enum keys) and then
export an array of enum values to use where NEXT_AUTH_COOKIE_NAMES is
referenced; update any usages of NEXT_AUTH_COOKIE_NAMES to reference the enum
values (e.g., NextAuthCookieName.NextAuthSessionToken) to remove magic string
literals and keep type safety while preserving the original string values.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b4882621-1fe7-4855-9121-d8513d3ea8f9

📥 Commits

Reviewing files that changed from the base of the PR and between 28a28ab and e1933d5.

📒 Files selected for processing (4)
  • packages/web/e2e/smoke.spec.ts
  • packages/web/src/components/Navbar.tsx
  • packages/web/src/components/profile/PublicProfileOwnerControls.tsx
  • packages/web/src/components/tasks/CreateTaskDialog.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/web/src/components/Navbar.tsx
  • packages/web/src/components/profile/PublicProfileOwnerControls.tsx
  • packages/web/src/components/tasks/CreateTaskDialog.tsx

Before: one profileLink NavItem in routes.ts:509 was being asked to
serve two semantically different jobs — link to /profile (edit form)
AND link to /people/<handle> (public view). Navbar.tsx branched on
publicProfileHref existence and swapped the destination silently. Net
effect: once a user made their profile public, the "manage my profile"
path disappeared from the navbar entirely. Mike caught it.

Split into:
- editProfileLink → /profile, "✏️ Edit Profile", "Edit bio, photo, privacy, and accounts"
- publicProfileLink → dynamic href, "🌐 View Public Profile", "See your profile the way other humans see it"

Navbar.tsx now renders BOTH links via getAuthenticatedProfileLinks()
when the user has a public profile, just editProfileLink when they
don't. ProfileCard.tsx:336 "View Profile" → "Preview public profile"
(disambiguates the inside-edit-page link). Updated consumers in
navSections, footerAppLinks, routeReviewNavItems, profile/page.tsx
metadata. Removed unused profileLink import from site.ts.

Bundled: restore 🔒 + 🌍 emojis to PrivacyToggle.tsx. The recent
treaty-style migration kept the new <button aria-pressed disabled>
accessibility wrapper and treaty-token colors (both wins) but threw
out the toggle-state icons along with the brutalist styling. The
emojis aren't decoration — they're concrete state cues for a binary
privacy switch. aria-hidden so screen readers don't double-announce.

qa-passed: skipped — UI label-only diff + pure helper function tested via Navbar.test.ts; pnpm --filter @optimitron/web exec tsc --noEmit clean.
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: 3

🤖 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 @.github/workflows/ci.yml:
- Around line 529-536: The current lookup uses comments.find(...) which returns
the first (oldest) matching comment; change both lookups that reference
packetMarker and legacyMarker (and the other block at the second occurrence) to
select the newest matching sticky comment instead—e.g., iterate comments in
reverse or use a findLast equivalent to locate the last comment whose body
includes packetMarker or legacyMarker so the code updates the most recent sticky
comment rather than a stale one.

In `@packages/web/src/lib/email/magic-link-render.ts`:
- Around line 22-23: The intro copy is specific to an HTML button but is reused
by buildMagicLinkText(), causing plain-text emails to incorrectly reference a
"button"; update the intro string (and any shared constant used by
buildMagicLinkText) to link-agnostic wording such as "Click the link to verify
your email and save your vote." so both HTML (where buttonLabel: "Save my vote"
remains) and plain-text variants are accurate; locate the intro constant
referenced by buildMagicLinkText and replace the phrase "Click the button below"
with a neutral alternative like "Click the link" or "Use the link."

In `@packages/web/src/lib/messaging.ts`:
- Line 283: Replace the hardcoded "1%" in the message string with the configured
treaty percentage by using the TREATY_REDUCTION_PCT value formatted with
fmtParam so the copy matches the model parameter; locate the message in
packages/web/src/lib/messaging.ts (the string that references POINT_NAME) and
change it to interpolate the formatted treaty percent (use the existing
TREATY_REDUCTION_PCT and fmtParam helpers) so the text reads like "Your
{treatyPct}% Treaty vote is saved…" while still including POINT_NAME.
🪄 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: 7de20e04-3361-4cb6-a910-d6a49a225ee1

📥 Commits

Reviewing files that changed from the base of the PR and between e1933d5 and f1926f4.

📒 Files selected for processing (22)
  • .github/scripts/generate-pr-preview-links.mjs
  • .github/scripts/generate-pr-preview-links.test.mjs
  • .github/workflows/ci.yml
  • packages/web/e2e/email-screenshots.spec.ts
  • packages/web/e2e/new-user-flow-screenshots.spec.ts
  • packages/web/e2e/visual-regression.spec.ts
  • packages/web/scripts/build-visual-review.mjs
  • packages/web/src/app/profile/page.tsx
  • packages/web/src/components/Navbar.test.ts
  • packages/web/src/components/Navbar.tsx
  • packages/web/src/components/dashboard/PrivacyToggle.tsx
  • packages/web/src/components/dashboard/ProfileCard.tsx
  • packages/web/src/components/landing/TreatyVoteFlow.tsx
  • packages/web/src/lib/__tests__/routes.test.ts
  • packages/web/src/lib/email/__tests__/magic-link-email.test.ts
  • packages/web/src/lib/email/__tests__/magic-link-send.test.ts
  • packages/web/src/lib/email/magic-link-email.ts
  • packages/web/src/lib/email/magic-link-render.ts
  • packages/web/src/lib/email/magic-link.email.md
  • packages/web/src/lib/messaging.ts
  • packages/web/src/lib/routes.ts
  • packages/web/src/lib/site.ts
💤 Files with no reviewable changes (1)
  • packages/web/src/lib/site.ts
✅ Files skipped from review due to trivial changes (3)
  • packages/web/src/components/dashboard/ProfileCard.tsx
  • packages/web/e2e/new-user-flow-screenshots.spec.ts
  • packages/web/src/app/profile/page.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/web/e2e/visual-regression.spec.ts
  • packages/web/src/components/dashboard/PrivacyToggle.tsx

Comment thread .github/workflows/ci.yml
Comment thread packages/web/src/lib/email/magic-link-render.ts
Comment thread packages/web/src/lib/messaging.ts Outdated
…kUserQuestion ceremony on every turn

The hook auto-added Pending review items to .claude/state/review-queue-<branch>.md
on every Write/Edit/MultiEdit touching shared files (messaging.ts touch added 47
routes; profile-link work added more) then blocked turn-end via Stop hook until I
AskUserQuestion-ed through each item. Net effect: every multi-file change ended
with stale "review this" prompts for work that had already shipped + been approved.
Mike (verbatim): "do we need to disable some hooks or something that are fucking
you up or getting you stuck? What is the deal here?"

Removed:
- PostToolUse Write/Edit/MultiEdit/AskUserQuestion review-loop-gate triggers
- Stop hook review-loop-gate trigger

Kept (auto-mode safety blocked editing):
- SessionStart review-loop-gate trigger — benign without the per-edit feeders;
  fires once at startup, just reads the queue file. Safe to leave or remove manually.

The /autoplan workflow + manual per-PR review + Vercel preview + CI tests
already provide review coverage. The review-loop-gate duplicated work and added
ceremony without catching anything the other paths missed.
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 @.claude/plans/referred-voters-list.md:
- Line 17: Two unlabeled fenced code blocks containing ASCII diagrams should be
labeled as text to satisfy markdownlint MD040; locate the two triple-backtick
blocks that enclose the ASCII diagrams (the unlabeled ``` blocks) and change
their opening fences from ``` to ```text while leaving their closing fences as
``` so both diagram blocks are fenced with ```text.
🪄 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: 5b2402cc-a85f-44f3-979a-aa4814340ed4

📥 Commits

Reviewing files that changed from the base of the PR and between 03ba8c4 and 0cb2a12.

📒 Files selected for processing (15)
  • .claude/plans/referred-voters-list.md
  • .claude/settings.json
  • .github/scripts/audit-sentry-preview.mjs
  • .github/scripts/audit-sentry-preview.test.mjs
  • .github/workflows/smoke-deploy.yml
  • packages/web/src/app/dashboard/page.tsx
  • packages/web/src/components/dashboard/HumanityManagerStatusPanel.test.tsx
  • packages/web/src/components/site/TreatyTaskDashboardClient.tsx
  • packages/web/src/lib/__tests__/dashboard.server.test.ts
  • packages/web/src/lib/__tests__/humanity-manager-status.server.test.ts
  • packages/web/src/lib/dashboard.server.ts
  • packages/web/src/lib/humanity-manager-status-content.tsx
  • packages/web/src/lib/humanity-manager-status.server.ts
  • packages/web/src/lib/profile-identity.server.ts
  • packages/web/src/types/dashboard.ts
💤 Files with no reviewable changes (1)
  • .claude/settings.json
✅ Files skipped from review due to trivial changes (1)
  • packages/web/src/lib/tests/dashboard.server.test.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • .github/scripts/audit-sentry-preview.test.mjs
  • .github/workflows/smoke-deploy.yml
  • .github/scripts/audit-sentry-preview.mjs

Comment thread .claude/plans/referred-voters-list.md Outdated
Comment thread packages/web/src/components/landing/TreatyVoteFlow.tsx Outdated
Comment thread packages/web/src/lib/email/__tests__/magic-link-send.test.ts Outdated
Comment thread packages/web/src/lib/email/__tests__/magic-link-email.test.ts Outdated
Comment thread packages/web/src/lib/__tests__/routes.test.ts
Comment thread packages/web/src/components/profile/PublicProfileOwnerControls.tsx
mikepsinn added 2 commits May 17, 2026 20:27
- Added a check in the verify-ui-changes hook to flag commits that touch packages/web/src/ without staging TODO.md or including appropriate markers in the commit message.
- This advisory-only check ensures that TODO.md is updated in the same commit as related changes, preventing silent drift.

docs: Add thank-and-recruit dispatch plan for Humanity Management Status

- Created a new markdown file detailing the plan to implement "thank" and "recruit" functionality for voters on the Humanity Management Status panel.
- The plan includes audience targeting, research logs, proposed UI changes, and a step-by-step implementation guide.

chore: Remove unused command from pre-commit hook

- Deleted the command for review-loop-gate from the pre-commit hook to streamline the commit process.

fix: Update voice-critic criteria to include strategic-job comparison

- Added a new criterion to the voice-critic agent to ensure that new copy increases the target action compared to old copy.

chore: Enable goals feature in Codex configuration

- Updated the Codex configuration to enable the goals feature for enhanced functionality.

chore: Update Husky pre-commit hook to include copy review gate

- Added a command to run the copy review gate script in the pre-commit hook to ensure public copy is reviewed before committing.

feat: Add new visual route for referendum one percent treaty

- Introduced a new visual route for the "referendum-one-percent-treaty" in the visual routes configuration.

feat: Include referendum one percent treaty in visual review build

- Updated the build script for visual reviews to include the new "referendum-one-percent-treaty" route.

feat: Create copy review gate script

- Implemented a new script to enforce a review gate for public copy changes before allowing commits.
@github-actions
Copy link
Copy Markdown
Contributor

Preview deploy smoke failed

Target: https://optimitron-nvah3b4mr-mike-p-sinns-projects.vercel.app/
Run: https://github.com/mikepsinn/optimitron/actions/runs/26052766023

Route Status Error marker Detail
/ 200 none missing h1 "Drag the slider to split the money." (found: Do Something | Tell Someone Else | Learn Something | Your Organization | PLEASE TAKE 30 SECONDS TO END WAR AND DISEASE)

The smoke request uses the Vercel automation bypass header and checks HTTP 200, owned error markers, and expected h1 text.

@claude
Copy link
Copy Markdown

claude Bot commented May 18, 2026

Code review — 3 security bugs, 3 CLAUDE.md violations.


Issue 1 — Security: private profiles exposed + email enumeration oracle
File: packages/web/src/app/api/people/search/route.ts lines 36–48

where: {
deletedAt: null,
lifeStatus: { not: PersonLifeStatus.DECEASED },
OR: [
{ displayName: { contains: query, mode: "insensitive" } },
{ firstName: { contains: query, mode: "insensitive" } },
{ lastName: { contains: query, mode: "insensitive" } },
{ handle: { contains: query.replace(/^@/u, ""), mode: "insensitive" } },
{ headline: { contains: query, mode: "insensitive" } },
{ currentAffiliation: { contains: query, mode: "insensitive" } },
...(emailSearch ? [{ email: { equals: emailSearch } }] : []),
],
},

The where clause has no isPublic: true filter. Any authenticated user can search for and retrieve displayName, handle, headline, image for users who have set their profile to private. The established pattern in tasks.server.ts:1465 gates person data behind isPublic (or viewer-is-owner).

Additionally, the @-detection branch at line 23 creates an email-to-identity oracle: any authenticated user can call ?q=alice@example.com to confirm whether that email belongs to any Person record and receive their profile data — with no rate limiting.

Fix: add isPublic: true to the where clause alongside deletedAt: null.


Issue 2 — Security: upsert silently un-deletes soft-deleted Persons
File: packages/web/src/app/api/tasks/route.ts lines 121–122

update: {
deletedAt: null,
},
where: { email: normalizedEmail },

update: { deletedAt: null } in findOrCreateInvitedAssigneePerson runs unconditionally when a Person with that email already exists — including records that were soft-deleted (admin moderation, GDPR erasure, account deletion). Any authenticated user who knows a deleted person's email can resurrect their record by assigning a task to that address.

The assigneePersonIdentifier path (lines 208–218) correctly checks deletedAt: null before using a Person; this path has no equivalent guard.

Fix: use findFirst with { email, deletedAt: null } before the upsert, or set update: {} to prevent resurrection.


Issue 3 — Security: assigneePersonId accepted without isPublic validation
File: packages/web/src/app/api/tasks/route.ts line 182

let assigneePersonId = rest.assigneePersonId ?? null;
const assigneeTargetCount = [
rest.assigneeOrganizationId,
assigneePersonId,
assigneePersonIdentifier,
assigneePersonInvite,
].filter(Boolean).length;
if (assigneeTargetCount > 1) {
return NextResponse.json(
{ error: "Choose one assignee." },
{ status: 400 },
);
}
if (!assigneePersonId && assigneePersonIdentifier) {

When assigneePersonId is supplied directly it is forwarded to createTask (line 265) without verifying the Person exists, is not deleted, or is public. Creating a public task assigned to a private Person leaks their displayName, handle, image, currentAffiliation, and countryCode through taskDetailSelect and taskListSelect, which have no isPublic gate on the assigneePerson relation.

Fix: validate before accepting the ID:

if (assigneePersonId) {
  const person = await prisma.person.findFirst({
    where: { id: assigneePersonId, isPublic: true, deletedAt: null },
    select: { id: true },
  });
  if (!person) return NextResponse.json({ error: 'Person not found.' }, { status: 404 });
}

Issue 4 — CLAUDE.md: tasks created with parentTaskId: null
File: packages/web/src/components/tasks/CreateTaskDialog.tsx line 350

body: JSON.stringify({
assigneePersonInvite:
!fixedAssigneePerson &&
taskMode === "person" &&
addingNewPerson &&
parsedNewPersonName
? {
currentAffiliation:
newPerson.currentAffiliation.trim() || undefined,
email: newPerson.email.trim(),
firstName: parsedNewPersonName.firstName,
lastName: parsedNewPersonName.lastName,
}
: undefined,
assigneePersonIdentifier:
!fixedAssigneePerson &&
taskMode === "person" &&
!selectedAssignee &&
!addingNewPerson
? explicitIdentifier
: undefined,
assigneePersonId: fixedAssigneePerson
? fixedAssigneePerson.id
: taskMode === "self"
? currentPersonId
: selectedAssignee?.id,
claimPolicy: taskMode === "open" ? "OPEN_SINGLE" : undefined,
description: description.trim(),
isPublic: fixedAssigneePerson ? true : taskMode !== "self",
title: trimmedTitle,
}),

The fetch body never includes parentTaskId. The API defaults to null when omitted, creating root-level tasks. CLAUDE.md Task Tree: "No new parentTaskId: null tasks. If a task isn't a bet on HALE or income, it should not exist."

Fix: import OPTIMIZE_EARTH_ROOT_TASK_ID from @optimitron/db and add parentTaskId: OPTIMIZE_EARTH_ROOT_TASK_ID to the fetch body. Alternatively, default it in the API route's POST handler.


Issue 5 — CLAUDE.md: hardcoded bg-white / text-black / bg-black / text-white in ReferralLinkEditor
File: packages/web/src/components/dashboard/ReferralLinkEditor.tsx lines 257, 272, 291

className={cn(
"min-h-12 justify-center gap-2 px-4 text-xs font-black uppercase tracking-[0.12em] shadow-none hover:translate-x-0 hover:translate-y-0 sm:w-auto",
"border border-black bg-white text-black hover:bg-black hover:text-white",
)}
>
<Save className="h-4 w-4 stroke-[2.5px]" />
{saving ? "Saving" : "Save"}
</Button>
) : null}
</div>
</div>
<div className="grid gap-2 sm:grid-cols-[minmax(0,1fr)_auto] sm:items-end">
<div className="min-w-0">
<p
className={cn(
"text-xs font-black uppercase tracking-[0.14em]",
isTreaty ? "text-black" : "text-muted-foreground",
)}
>
Share this link
</p>
<p className="mt-2 min-h-12 break-all border border-black bg-background px-3 py-3 font-mono text-sm font-black text-foreground">
{displayedUrl}
</p>
</div>
<div className="flex">
<Button
type="button"
onClick={() => {
void copyLink();
}}
disabled={saving}
className={cn(
"min-h-12 w-full justify-center gap-2 px-4 text-xs font-black uppercase tracking-[0.12em] shadow-none hover:translate-x-0 hover:translate-y-0 sm:w-auto",
"border border-black bg-black text-white hover:bg-white hover:text-black",
)}
>

Three net-new or touched locations use banned hardcoded color literals. CLAUDE.md Visual Style Rules: "Never use: Hardcoded bg-white / text-white / bg-black / text-black." The migration rule also applies: "when touching public UI, remove neobrutalist styling instead of copying it forward."

Line 257 (Save button): bg-white text-black hover:bg-black hover:text-white
Line 272 (Share label): isTreaty ? text-black : ...
Line 291 (Copy button): bg-black text-white hover:bg-white hover:text-black

Replace with semantic tokens: bg-background / bg-foreground / text-foreground / text-background.


Issue 6 — CLAUDE.md: hardcoded text-black on new Slide me label
File: packages/web/src/components/landing/TreatyVoteFlow.tsx line 974

<div className="pointer-events-none mt-3 flex justify-center">
<div className="border border-black bg-white px-3 py-1 text-center sm:py-2">
<p className="whitespace-nowrap text-[10px] font-black uppercase tracking-[0.18em] text-black sm:text-xs sm:tracking-[0.22em]">
Slide me
</p>

This label was rewritten in this PR (old floating version deleted); the new version is subject to the ban on text-black on public treaty/campaign surfaces. Replace text-black with text-foreground.

@mikepsinn mikepsinn merged commit e277b7e into main May 19, 2026
20 checks passed
@mikepsinn mikepsinn deleted the feature/public-profile-task-assignment branch May 19, 2026 04:19
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