diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5376c79e5..9a1bfb771 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -82,11 +82,11 @@ jobs: timeout-minutes: 30 permissions: actions: read - contents: read + # peaceiris/actions-gh-pages needs write to push the visual review to + # the long-lived `gh-pages` branch. + contents: write deployments: read - id-token: write issues: write - pages: write pull-requests: write statuses: write env: @@ -314,22 +314,39 @@ jobs: packages/web/test-results if-no-files-found: ignore - - name: Prepare visual review Pages site + # Publish the per-PR visual review to the long-lived `gh-pages` branch + # so reviews from different PRs don't clobber each other. The previous + # setup used `actions/deploy-pages`, which atomically REPLACES the + # entire Pages site each deploy — so whenever any PR's CI finished, + # every other PR's review URL went 404 until that PR's CI ran again. + # + # `peaceiris/actions-gh-pages` with `keep_files: true` writes only the + # current PR's directory and preserves everything else on `gh-pages`. + # + # One-time repo setup: Settings → Pages → Source must be set to + # "Deploy from a branch" with branch `gh-pages` (root). The action + # creates the branch on its first push. + - name: Prepare per-PR visual review directory if: always() && github.event_name == 'pull_request' + id: prepare_pages shell: bash run: | - pages_root="packages/web/output/playwright/pages" - pr_dir="$pages_root/pr-${{ github.event.pull_request.number }}" head_sha="${{ github.event.pull_request.head.sha }}" short_sha="${head_sha:0:12}" - commit_dir="$pr_dir/$short_sha" - rm -rf "$pages_root" - mkdir -p "$pr_dir" "$commit_dir" - - cp -R packages/web/output/playwright/review/. "$pr_dir/" + pr_root="packages/web/output/playwright/pages-per-pr/pr-${{ github.event.pull_request.number }}" + commit_dir="$pr_root/$short_sha" + latest_dir="$pr_root/latest" + rm -rf "$pr_root" + mkdir -p "$commit_dir" "$latest_dir" cp -R packages/web/output/playwright/review/. "$commit_dir/" - - cat > "$pages_root/index.html" <<'HTML' + cp -R packages/web/output/playwright/review/. "$latest_dir/" + # Seed `.nojekyll` and a root `index.html` at the publish root so the + # gh-pages site doesn't try to render through Jekyll and so the + # bare https://.github.io// URL renders something + # meaningful. `keep_files: true` preserves these across deploys. + publish_root="packages/web/output/playwright/pages-per-pr" + touch "$publish_root/.nojekyll" + cat > "$publish_root/index.html" <<'HTML' @@ -339,26 +356,26 @@ jobs:

Optimitron Visual Reviews

-

Open the pull request comment for the latest visual review link.

+

Open the "Visual review" check on a pull request for its review page.

HTML - touch "$pages_root/.nojekyll" - - - name: Configure GitHub Pages - if: always() && github.event_name == 'pull_request' - uses: actions/configure-pages@v5 + echo "short_sha=$short_sha" >> "$GITHUB_OUTPUT" + echo "publish_dir=packages/web/output/playwright/pages-per-pr" >> "$GITHUB_OUTPUT" - - name: Upload GitHub Pages artifact - if: always() && github.event_name == 'pull_request' - uses: actions/upload-pages-artifact@v3 - with: - path: packages/web/output/playwright/pages - - - name: Deploy visual review to GitHub Pages + - name: Publish visual review to gh-pages if: always() && github.event_name == 'pull_request' id: visual_review_pages - uses: actions/deploy-pages@v4 + uses: peaceiris/actions-gh-pages@v4 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ${{ steps.prepare_pages.outputs.publish_dir }} + publish_branch: gh-pages + keep_files: true + enable_jekyll: false + commit_message: "Visual review for pr-${{ github.event.pull_request.number }}@${{ steps.prepare_pages.outputs.short_sha }}" + user_name: github-actions[bot] + user_email: github-actions[bot]@users.noreply.github.com - name: Post Visual review commit status if: always() && github.event_name == 'pull_request' diff --git a/TODO.md b/TODO.md index 6c1952e56..1767ffdac 100644 --- a/TODO.md +++ b/TODO.md @@ -189,12 +189,13 @@ supporting detail or parked work; they should not override this sequence. benchmark children are retired. 4. [x] **Wire production deploy to run managed-data sync.** This prevents future canonical task/title/trigger changes from requiring one-off data migrations. -5. [ ] **Then update the UI presentation.** `/tasks/optimize-earth`, dashboard, and - visual-review routes should show the simplified tree, while `warondisease.org` +5. [~] **Then update the UI presentation.** Dashboard is now a focused share + card + collapsed disclosures (PR #71); /tasks/optimize-earth and the + visual-review routes still want a simplified tree view. War on Disease still pushes the 1% Treaty vote first. -6. [ ] **Only add `allowsUserSubtasks` before exposing public subtask creation UI.** - Seeded/admin-managed task trees can ship before this. Public UGC needs the - permission column/guard so arbitrary users cannot clutter canonical parents. +6. [—] **`allowsUserSubtasks` schema column — parked.** Existing schema is + sufficient to add subtasks when needed. Revisit only when public subtask + creation UI is on the immediate roadmap. 7. [x] **Fold task triggers into managed data after the task-tree sync is proven.** Trigger definitions are the same kind of semi-permanent app data and no longer use a separate one-off production seed path. @@ -213,9 +214,11 @@ cross-check so they do not disappear into chat history. **Dashboard and task UX** -- Replace the logged-in dashboard with a short action checklist: - sign the 1% Treaty, render verdict, register plaintiff, summon jurors, - pressure/manage presidents. Link each row to the actual page. +- [x] Replace the logged-in dashboard with a focused share surface: plaintiff + status, share message + copy button, collapsed disclosures for register-a-plaintiff / + remind-overdue-presidents / endorse / assigned-tasks. Shipped PR #71 — the + composer, leaderboard, plaintiff form, and full treaty text are no longer + embedded; each lives only on its dedicated page. - Remove the generic task-detail metadata block where it duplicates header information. Keep title, assignee, due date, primary action, markdown body, comments, complete/reassign/admin controls. @@ -228,8 +231,29 @@ cross-check so they do not disappear into chat history. - Add E2E coverage that a signed-in demo user can open an assigned/private task from "Your Tasks" without hitting 404. +**Post-vote email (decided 2026-05-11)** + +- Build a single triggered email (not a drip) fired the moment `ReferendumVote.status == COUNTED`. +- Body is the "I love you and don't want you to suffer and die of horrible + diseases" share message verbatim, with the user's referral URL inline. +- CTA above the body: "Hit forward, paste two email addresses, send. That's + the whole job." The email *is* the forward-friendly recruitment vehicle, not + a screen the user has to copy from. +- Reuses existing Resend pipeline + reply-handling + unsubscribe-on-reply rails. +- No subsequent drips. Reminder spam is explicitly banned (see PR #66 + "Disable generic overdue email reminders"). + **Visual review and preview workflow** +- [x] Visual review surfaces a `Visual review` commit status pointing to the + SHA-pinned gallery in the merge box (PR #71), replacing the 6-link bot + comment that buried the actual gallery under filler. +- Add email-template screenshots to the visual review HTML. Render each email + template (`buildMagicLinkHtml`, task-assignment, task-comment-notification, + inbound-monitor-forward, the future post-vote forward email) with a + representative token set, screenshot, and emit alongside the page + screenshots in `latest.html`. Reviewers currently can't see email copy + without setting up Resend locally + emailing themselves a test. - Add the Central Time generation timestamp to visual review HTML. - Use commit-hash or otherwise cache-busted GitHub Pages paths for generated visual reviews so a new PR comment cannot show an old cached `latest.html`. @@ -449,19 +473,14 @@ The IAM smoke-test email I wrote first would have failed this lint on the word-count rule alone — exactly the regression class the human flagged. Schema-zero. Cheap. -### P1 — Plaintiff damages surface on `/plaintiffs/page.tsx` - -`/plaintiffs/page.tsx` imports `WAR_DEATHS_SINCE_1900` and -military-spending parameters but not -`CORPORATE_DAMAGES_FORWARD_SETTLEMENT_VALUE_PER_CAPITA` or the -cohort constant. So a visitor sees the gallery without learning -what each registered plaintiff is owed (~$10.6M NPV / $25.2M -lifetime cohort). The case page surfaces this; the registration -page should too. ~30 lines of JSX added under the existing -parameter imports — schema-zero. Highest per-line conversion lift -on the to-do list right now; a visitor who lands on `/plaintiffs` -from the case-page CTA without first reading the case currently -has no damages number to anchor on. +### P1 — Plaintiff damages surface on `/plaintiffs/page.tsx` ✅ done + +Shipped: the page renders a "Demanded recovery per registered plaintiff" +section above the conversion form with the $10.6M NPV (per +`CORPORATE_DAMAGES_FORWARD_SETTLEMENT_VALUE_PER_CAPITA`) and the +$25.2M lifetime cohort (per `LOST_PROSPERITY_LIFETIME_DAMAGES_PER_CAPITA`) +side-by-side, then an explanation of the family-share frame and the +4B-vote enforcement gate. ### P1 — Centralize communication templates (audit findings 2026-05-08) diff --git a/packages/db/src/task-keys.ts b/packages/db/src/task-keys.ts index f0bd02e55..3e3555f2b 100644 --- a/packages/db/src/task-keys.ts +++ b/packages/db/src/task-keys.ts @@ -135,57 +135,3 @@ export const ACCOUNTABILITY_TASK_KEY_PREFIX = "accountability"; export function buildAccountabilityTaskKey(countryCode: string, activitySlug: string) { return `${ACCOUNTABILITY_TASK_KEY_PREFIX}:${countryCode.toLowerCase()}:${activitySlug}`; } - -// Optimize-Earth system tasks (autonomous agent's per-action subtasks) - -export const OPTIMIZE_EARTH_TASK_KEY_PREFIX = "system:optimize-earth"; - -// Static optimize-earth subkeys (one row each in the seed/queue). -export const OPTIMIZE_EARTH_WEAPONIZE_OVERDUE_TASK_LIST_KEY = `${OPTIMIZE_EARTH_TASK_KEY_PREFIX}:weaponize-overdue-task-list`; -export const OPTIMIZE_EARTH_CROSSLINK_PAGES_KEY = `${OPTIMIZE_EARTH_TASK_KEY_PREFIX}:crosslink-task-government-politician-pages`; -export const OPTIMIZE_EARTH_CONVERT_PITCH_PAGES_KEY = `${OPTIMIZE_EARTH_TASK_KEY_PREFIX}:convert-pitch-pages-into-task-traffic`; -export const OPTIMIZE_EARTH_DISCOVER_OFFICE_CHANNELS_KEY = `${OPTIMIZE_EARTH_TASK_KEY_PREFIX}:discover-missing-signer-office-channels`; -export const OPTIMIZE_EARTH_DISCOVER_PRESS_KEY = `${OPTIMIZE_EARTH_TASK_KEY_PREFIX}:discover-country-journalist-and-coalition-targets`; -export const OPTIMIZE_EARTH_GROUND_GENERATOR_KEY = `${OPTIMIZE_EARTH_TASK_KEY_PREFIX}:ground-task-generation-in-existing-pages-and-manual`; -export const OPTIMIZE_EARTH_GENERATE_GROWTH_TASKS_KEY = `${OPTIMIZE_EARTH_TASK_KEY_PREFIX}:generate-growth-conversion-tasks`; -export const OPTIMIZE_EARTH_GENERATE_CONTACT_DISCOVERY_KEY = `${OPTIMIZE_EARTH_TASK_KEY_PREFIX}:generate-contact-discovery-tasks`; -export const OPTIMIZE_EARTH_GENERATE_SYSTEM_IMPROVEMENT_KEY = `${OPTIMIZE_EARTH_TASK_KEY_PREFIX}:generate-system-improvement-tasks`; - -// Action-follow-through dynamic subkeys (one tree per action). -export const OPTIMIZE_EARTH_ACTION_FOLLOW_THROUGH_PREFIX = `${OPTIMIZE_EARTH_TASK_KEY_PREFIX}:action-follow-through`; -export const OPTIMIZE_EARTH_PUBLISH_BUDGET_BRIEF_PREFIX = `${OPTIMIZE_EARTH_TASK_KEY_PREFIX}:publish-budget-brief`; -export const OPTIMIZE_EARTH_ROUTE_PROOF_PAGES_PREFIX = `${OPTIMIZE_EARTH_TASK_KEY_PREFIX}:route-proof-pages-into-funding`; -export const OPTIMIZE_EARTH_SOURCE_COUNTERPARTIES_PREFIX = `${OPTIMIZE_EARTH_TASK_KEY_PREFIX}:source-counterparties-and-price-ceiling`; -export const OPTIMIZE_EARTH_PREPARE_APPROVAL_PACKET_PREFIX = `${OPTIMIZE_EARTH_TASK_KEY_PREFIX}:prepare-approval-packet`; - -/** - * Prefixes whose presence on a taskKey means the action-follow-through tree - * has already been spawned for that task — used to avoid recursive spawning. - */ -export const OPTIMIZE_EARTH_ACTION_FOLLOW_THROUGH_KEY_PREFIXES = [ - `${OPTIMIZE_EARTH_ACTION_FOLLOW_THROUGH_PREFIX}:`, - `${OPTIMIZE_EARTH_PUBLISH_BUDGET_BRIEF_PREFIX}:`, - `${OPTIMIZE_EARTH_ROUTE_PROOF_PAGES_PREFIX}:`, - `${OPTIMIZE_EARTH_SOURCE_COUNTERPARTIES_PREFIX}:`, - `${OPTIMIZE_EARTH_PREPARE_APPROVAL_PACKET_PREFIX}:`, -] as const; - -export function buildOptimizeEarthActionFollowThroughKey(taskSlug: string) { - return `${OPTIMIZE_EARTH_ACTION_FOLLOW_THROUGH_PREFIX}:${taskSlug}`; -} - -export function buildOptimizeEarthPublishBudgetBriefKey(taskSlug: string) { - return `${OPTIMIZE_EARTH_PUBLISH_BUDGET_BRIEF_PREFIX}:${taskSlug}`; -} - -export function buildOptimizeEarthRouteProofPagesKey(taskSlug: string) { - return `${OPTIMIZE_EARTH_ROUTE_PROOF_PAGES_PREFIX}:${taskSlug}`; -} - -export function buildOptimizeEarthSourceCounterpartiesKey(taskSlug: string) { - return `${OPTIMIZE_EARTH_SOURCE_COUNTERPARTIES_PREFIX}:${taskSlug}`; -} - -export function buildOptimizeEarthPrepareApprovalPacketKey(taskSlug: string) { - return `${OPTIMIZE_EARTH_PREPARE_APPROVAL_PACKET_PREFIX}:${taskSlug}`; -} diff --git a/packages/web/src/components/tasks/live-counter.tsx b/packages/web/src/components/tasks/live-counter.tsx index 970c5471c..362b9a3f5 100644 --- a/packages/web/src/components/tasks/live-counter.tsx +++ b/packages/web/src/components/tasks/live-counter.tsx @@ -15,6 +15,17 @@ interface LiveCounterProps { /** Tick every 250ms — 4 updates/sec, smooth enough to feel live, cheap on CPU. */ const TICK_INTERVAL_MS = 250; +/** + * Visual-review placeholders. The e2e visual-regression spec swaps any node + * with `data-visual-mask="dynamic"` to render its `data-visual-placeholder` + * text instead of the live value, so screenshots stay byte-identical across + * runs. Without this, every CI run captured a different tick of the counter + * and produced false-positive diffs on every page that embeds a LiveCounter + * (notably /employees, /presidents, and signer rows). + */ +const VISUAL_REVIEW_INTEGER_PLACEHOLDER = "123,456"; +const VISUAL_REVIEW_CURRENCY_PLACEHOLDER = "$123,456,789,012"; + const intFormatter = new Intl.NumberFormat("en-US", { minimumFractionDigits: 0, maximumFractionDigits: 0, @@ -59,7 +70,12 @@ export function LiveCounter({ return ( {displayValue ?? "…"} diff --git a/packages/web/src/lib/optimize-earth-grounding.server.test.ts b/packages/web/src/lib/optimize-earth-grounding.server.test.ts deleted file mode 100644 index 0a1969905..000000000 --- a/packages/web/src/lib/optimize-earth-grounding.server.test.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { enrichTaskTreeWithManualGrounding } from "./optimize-earth-grounding.server"; -import { OPTIMITRON_CANONICAL_ORIGIN } from "./site"; - -describe("enrichTaskTreeWithManualGrounding", () => { - it("preserves existing refs and appends retrieved manual citations recursively", async () => { - const roots = await enrichTaskTreeWithManualGrounding( - [ - { - description: "Make the overdue task list more memetic.", - id: "root_growth", - sourceUrls: [`${OPTIMITRON_CANONICAL_ORIGIN}/tasks`], - title: "Weaponize the overdue task list", - children: [ - { - description: "Use the politician pages as pressure surfaces.", - id: "child_growth", - sourceUrls: [`${OPTIMITRON_CANONICAL_ORIGIN}/governments/US/politicians`], - title: "Cross-link politician pages", - }, - ], - }, - ], - { - retrieve: async (query) => ({ - citations: [ - { - score: 0.9, - title: "Earth Optimization Prize", - url: query.includes("politician") - ? "https://manual.warondisease.org/knowledge/appendix/incentive-alignment-bonds-paper.html" - : "https://manual.warondisease.org/knowledge/strategy/earth-optimization-prize.html", - }, - ], - context: "", - }), - }, - ); - - expect(roots[0]?.sourceUrls).toEqual( - expect.arrayContaining([ - `${OPTIMITRON_CANONICAL_ORIGIN}/tasks`, - "https://manual.warondisease.org/knowledge/strategy/earth-optimization-prize.html", - ]), - ); - expect(roots[0]?.children?.[0]?.sourceUrls).toEqual( - expect.arrayContaining([ - `${OPTIMITRON_CANONICAL_ORIGIN}/governments/US/politicians`, - "https://manual.warondisease.org/knowledge/appendix/incentive-alignment-bonds-paper.html", - ]), - ); - }); -}); diff --git a/packages/web/src/lib/optimize-earth-grounding.server.ts b/packages/web/src/lib/optimize-earth-grounding.server.ts deleted file mode 100644 index be2ff22fe..000000000 --- a/packages/web/src/lib/optimize-earth-grounding.server.ts +++ /dev/null @@ -1,48 +0,0 @@ -import type { TaskTreeNode } from "@optimitron/agent"; -import { retrieveManualContext, type ManualSearchCitation } from "./manual-search.server"; - -export interface ManualGroundingRetriever { - ( - query: string, - options?: { forceRefresh?: boolean; maxChars?: number; maxResults?: number }, - ): Promise<{ citations: ManualSearchCitation[]; context: string }>; -} - -function dedupeUrls(urls: string[]) { - return Array.from(new Set(urls.map((url) => url.trim()).filter(Boolean))); -} - -function buildGroundingQuery(node: TaskTreeNode) { - return [node.title, node.description ?? "", ...(node.sourceUrls ?? [])] - .filter(Boolean) - .join(" "); -} - -async function enrichNodeWithManualGrounding( - node: TaskTreeNode, - retrieve: ManualGroundingRetriever, -): Promise { - const manual = await retrieve(buildGroundingQuery(node), { - maxResults: 3, - }); - const manualUrls = manual.citations.map((citation) => citation.url); - const children = await Promise.all( - (node.children ?? []).map((child) => enrichNodeWithManualGrounding(child, retrieve)), - ); - - return { - ...node, - children, - sourceUrls: dedupeUrls([...(node.sourceUrls ?? []), ...manualUrls]), - }; -} - -export async function enrichTaskTreeWithManualGrounding( - roots: TaskTreeNode[], - options?: { - retrieve?: ManualGroundingRetriever; - }, -) { - const retrieve = options?.retrieve ?? retrieveManualContext; - return Promise.all(roots.map((root) => enrichNodeWithManualGrounding(root, retrieve))); -} diff --git a/packages/web/src/lib/optimize-earth-page-context.test.ts b/packages/web/src/lib/optimize-earth-page-context.test.ts deleted file mode 100644 index d3fddfd27..000000000 --- a/packages/web/src/lib/optimize-earth-page-context.test.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { buildActionFollowThroughRoots } from "./optimize-earth-page-context"; -import { OPTIMITRON_CANONICAL_ORIGIN } from "./site"; - -describe("buildActionFollowThroughRoots", () => { - it("builds grounded funding follow-through tasks for a budget-blocked top action", () => { - const roots = buildActionFollowThroughRoots({ - action: { - actionKind: "FUNDING_UNBLOCKER", - audit: { - aggregateDelayEconomicValueUsdPerDay: 1, - aggregateExpectedValuePerHourUsd: 1, - dominantFamily: "growth-conversion", - dominantFamilyShare: 0.3, - familyCounts: { - "contact-discovery": 1, - "growth-conversion": 3, - other: 0, - "system-improvement": 2, - "treaty-signer": 0, - "treaty-support-contact": 0, - "treaty-support-endorsement": 0, - "treaty-support-evidence": 0, - "treaty-support-explainer": 0, - }, - impactCoverageRatio: 1, - issues: [], - systemImprovementPriority: "normal", - taskCount: 6, - treatySignerCount: 0, - }, - autoExecutable: false, - economics: { - autoExecutable: false, - availableBudgetUsd: 0, - capabilityFit: 0.6, - delayEconomicValueUsdLostPerDay: 1_000_000, - estimatedExternalCostUsd: 500, - estimatedNetValueUsd: 999_500, - executionV1: { - allowedExecutionModes: ["PREPARE_PROCUREMENT", "FUNDING_UNBLOCKER"], - canAgentDoDirectly: false, - counterpartyHints: ["growth-operator"], - estimatedExternalCostUsd: 500, - fundingGapUsd: 500, - groundingRefs: [`${OPTIMITRON_CANONICAL_ORIGIN}/tasks`], - lawfulSpendTypes: ["ADS"], - maxRationalSpendUsd: 500_000, - procurementPriority: "HIGH", - }, - expectedEconomicValueUsd: 2_000_000, - expectedValuePerHourUsd: 250_000, - fundingGapUsd: 500, - lawfulSpendTypes: ["ADS"], - maxRationalSpendUsd: 500_000, - options: [], - requiredApproval: false, - suggestedActionKind: "FUNDING_UNBLOCKER", - }, - groundingRefs: [`${OPTIMITRON_CANONICAL_ORIGIN}/tasks`], - queueRepairPlan: null, - rationale: ["Top task is budget-blocked."], - requiredApproval: false, - task: { - id: "task_memetic", - taskKey: "system:optimize-earth:weaponize-overdue-task-list", - title: "Turn the overdue leader task list into a memetic share-and-pressure machine", - impact: { - delayEconomicValueUsdLostPerDay: 1_000_000, - expectedValuePerHourUsd: 250_000, - }, - }, - } as any, - }); - - expect(roots).toHaveLength(1); - expect(roots[0]?.taskKey).toContain("action-follow-through"); - expect(roots[0]?.children?.map((child) => child.taskKey)).toEqual( - expect.arrayContaining([ - expect.stringContaining("publish-budget-brief"), - expect.stringContaining("route-proof-pages-into-funding"), - expect.stringContaining("source-counterparties-and-price-ceiling"), - expect.stringContaining("prepare-approval-packet"), - ]), - ); - expect( - roots[0]?.children?.every((child) => - (child.sourceUrls ?? []).some( - (url) => - url.includes("optimitron.com") || url.includes("manual.warondisease.org"), - ), - ), - ).toBe(true); - }); - - it("does not generate follow-through tasks for existing follow-through work", () => { - const roots = buildActionFollowThroughRoots({ - action: { - actionKind: "FUNDING_UNBLOCKER", - economics: { - delayEconomicValueUsdLostPerDay: 1, - estimatedExternalCostUsd: 1, - expectedValuePerHourUsd: 1, - fundingGapUsd: 1, - maxRationalSpendUsd: 1, - }, - groundingRefs: [], - task: { - id: "task_follow_through", - taskKey: - "system:optimize-earth:publish-budget-brief:system-optimize-earth-weaponize-overdue-task-list", - title: - "Publish the quantified budget brief for Turn the overdue leader task list into a memetic share-and-pressure machine", - }, - } as any, - }); - - expect(roots).toEqual([]); - }); -}); diff --git a/packages/web/src/lib/optimize-earth-page-context.ts b/packages/web/src/lib/optimize-earth-page-context.ts deleted file mode 100644 index f17ddaa95..000000000 --- a/packages/web/src/lib/optimize-earth-page-context.ts +++ /dev/null @@ -1,422 +0,0 @@ -import type { EarthNextActionDecision, TaskTreeNode } from "@optimitron/agent"; -import { ROUTES } from "./routes"; -import { slugify } from "./slugify"; -import { absoluteCanonicalSiteUrl } from "./site"; -import { - buildOptimizeEarthActionFollowThroughKey, - buildOptimizeEarthPrepareApprovalPacketKey, - buildOptimizeEarthPublishBudgetBriefKey, - buildOptimizeEarthRouteProofPagesKey, - buildOptimizeEarthSourceCounterpartiesKey, - OPTIMIZE_EARTH_ACTION_FOLLOW_THROUGH_KEY_PREFIXES, - OPTIMIZE_EARTH_CONVERT_PITCH_PAGES_KEY, - OPTIMIZE_EARTH_CROSSLINK_PAGES_KEY, - OPTIMIZE_EARTH_DISCOVER_OFFICE_CHANNELS_KEY, - OPTIMIZE_EARTH_DISCOVER_PRESS_KEY, - OPTIMIZE_EARTH_GENERATE_CONTACT_DISCOVERY_KEY, - OPTIMIZE_EARTH_GENERATE_GROWTH_TASKS_KEY, - OPTIMIZE_EARTH_GENERATE_SYSTEM_IMPROVEMENT_KEY, - OPTIMIZE_EARTH_GROUND_GENERATOR_KEY, - OPTIMIZE_EARTH_WEAPONIZE_OVERDUE_TASK_LIST_KEY, -} from "./tasks/task-keys"; - -const EARTH_OPTIMIZATION_PRIZE_URL = - "https://manual.warondisease.org/knowledge/strategy/earth-optimization-prize.html"; -const INCENTIVE_ALIGNMENT_BONDS_URL = - "https://manual.warondisease.org/knowledge/appendix/incentive-alignment-bonds-paper.html"; -const EARTH_OPTIMIZATION_PROTOCOL_URL = - "https://manual.warondisease.org/strategy/earth-optimization-protocol-v1"; - -interface PageContext { - filePath: string; - purpose: string; - route: string; - title: string; -} - -function absoluteUrl(route: string) { - return absoluteCanonicalSiteUrl(route); -} - -function scaleImpact( - impact: TaskTreeNode["impact"], - multiplier: number, -): TaskTreeNode["impact"] { - if (!impact) { - return impact ?? null; - } - - return { - delayDalysLostPerDay: - impact.delayDalysLostPerDay == null - ? null - : impact.delayDalysLostPerDay * multiplier, - delayEconomicValueUsdLostPerDay: - impact.delayEconomicValueUsdLostPerDay == null - ? null - : impact.delayEconomicValueUsdLostPerDay * multiplier, - expectedValuePerHourDalys: - impact.expectedValuePerHourDalys == null - ? null - : impact.expectedValuePerHourDalys * multiplier, - expectedValuePerHourUsd: - impact.expectedValuePerHourUsd == null - ? null - : impact.expectedValuePerHourUsd * multiplier, - }; -} - -const CORE_PAGE_CONTEXT: Record = { - demo: { - filePath: "packages/web/src/app/demo/page.tsx", - purpose: "Long-form persuasion and onboarding flow.", - route: ROUTES.demo, - title: "Demo", - }, - governments: { - filePath: "packages/web/src/app/governments/page.tsx", - purpose: "Public comparative government report card and scatterplot.", - route: ROUTES.governments, - title: "Government Report Cards", - }, - politicians: { - filePath: "packages/web/src/app/governments/[code]/politicians/page.tsx", - purpose: "Country politician leaderboard and accountability rankings.", - route: "/governments/US/politicians", - title: "Politician Leaderboard", - }, - scoreboard: { - filePath: "packages/web/src/app/scoreboard/page.tsx", - purpose: "Two-number humanity scoreboard and macro proof.", - route: ROUTES.scoreboard, - title: "Humanity's Scoreboard", - }, - tasks: { - filePath: "packages/web/src/app/tasks/page.tsx", - purpose: "Highest-value overdue tasks, including leader signatures.", - route: ROUTES.tasks, - title: "Tasks", - }, - taskDetail: { - filePath: "packages/web/src/app/tasks/[id]/page.tsx", - purpose: "Task-level evidence, delay math, milestones, and action hooks.", - route: `${ROUTES.tasks}/[id]`, - title: "Task Detail", - }, - video: { - filePath: "packages/web/src/app/video/page.tsx", - purpose: "High-compression treaty pitch for sharing.", - route: ROUTES.video, - title: "Video", - }, - wishonia: { - filePath: "packages/web/src/app/wishonia/page.tsx", - purpose: "Concrete north-star civilization and institutional endpoint.", - route: ROUTES.wishonia, - title: "Wishonia", - }, -}; - -function growthChildren(parent: TaskTreeNode): TaskTreeNode[] { - return [ - { - description: - `Turn the existing ${CORE_PAGE_CONTEXT.tasks.route} overdue-leader list into a memetic reminder surface with sharper share hooks, politician deep-links, and obvious next actions. Use the actual page instead of inventing new funnel pages.`, - estimatedEffortHours: 3, - id: "system_weaponize_overdue_task_list", - impact: scaleImpact(parent.impact, 0.55), - isPublic: true, - roleTitle: "Growth Operator", - sourceUrls: [ - absoluteUrl(CORE_PAGE_CONTEXT.tasks.route), - absoluteUrl(CORE_PAGE_CONTEXT.taskDetail.route.replace("[id]", "")), - absoluteUrl(CORE_PAGE_CONTEXT.politicians.route), - ], - status: "DRAFT", - taskKey: OPTIMIZE_EARTH_WEAPONIZE_OVERDUE_TASK_LIST_KEY, - title: "Turn the overdue leader task list into a memetic share-and-remind machine", - }, - { - description: - `Exploit the existing ${CORE_PAGE_CONTEXT.governments.route}, ${CORE_PAGE_CONTEXT.politicians.route}, and ${CORE_PAGE_CONTEXT.scoreboard.route} pages as proof assets. Add direct cross-links from leader tasks into politician and scoreboard pages so the outrage has somewhere concrete to go.`, - estimatedEffortHours: 2, - id: "system_crosslink_accountability_pages", - impact: scaleImpact(parent.impact, 0.45), - isPublic: true, - roleTitle: "Growth Operator", - sourceUrls: [ - absoluteUrl(CORE_PAGE_CONTEXT.tasks.route), - absoluteUrl(CORE_PAGE_CONTEXT.governments.route), - absoluteUrl(CORE_PAGE_CONTEXT.politicians.route), - absoluteUrl(CORE_PAGE_CONTEXT.scoreboard.route), - ], - status: "DRAFT", - taskKey: OPTIMIZE_EARTH_CROSSLINK_PAGES_KEY, - title: "Cross-link task, government, politician, and scoreboard pages into one accountability funnel", - }, - { - description: - `Use the existing ${CORE_PAGE_CONTEXT.video.route}, ${CORE_PAGE_CONTEXT.demo.route}, and ${CORE_PAGE_CONTEXT.wishonia.route} assets as conversion rails. Make the pitch pages feed directly into overdue leader tasks, politician pages, and share actions instead of leaving visitors in explanation land.`, - estimatedEffortHours: 2, - id: "system_convert_pitch_pages", - impact: scaleImpact(parent.impact, 0.4), - isPublic: true, - roleTitle: "Growth Operator", - sourceUrls: [ - absoluteUrl(CORE_PAGE_CONTEXT.video.route), - absoluteUrl(CORE_PAGE_CONTEXT.demo.route), - absoluteUrl(CORE_PAGE_CONTEXT.wishonia.route), - absoluteUrl(CORE_PAGE_CONTEXT.tasks.route), - ], - status: "DRAFT", - taskKey: OPTIMIZE_EARTH_CONVERT_PITCH_PAGES_KEY, - title: "Turn existing pitch pages into direct traffic for overdue leader and politician tasks", - }, - ]; -} - -function contactDiscoveryChildren(parent: TaskTreeNode): TaskTreeNode[] { - return [ - { - description: - "Build missing office-channel discovery tasks for the full signer roster first, especially countries that only have generic government pages or no direct contact URL yet.", - estimatedEffortHours: 3, - id: "system_fill_missing_office_channels", - impact: scaleImpact(parent.impact, 0.55), - isPublic: true, - roleTitle: "Outreach Operator", - sourceUrls: [ - absoluteUrl(CORE_PAGE_CONTEXT.tasks.route), - absoluteUrl(CORE_PAGE_CONTEXT.governments.route), - ], - status: "DRAFT", - taskKey: OPTIMIZE_EARTH_DISCOVER_OFFICE_CHANNELS_KEY, - title: "Discover missing office contact channels for the full signer roster", - }, - { - description: - "Create targeted journalist and coalition contact-discovery tasks using the countries already exposed on the public task and politician surfaces, not generic global PR lists.", - estimatedEffortHours: 2, - id: "system_targeted_press_and_coalitions", - impact: scaleImpact(parent.impact, 0.4), - isPublic: true, - roleTitle: "Outreach Operator", - sourceUrls: [ - absoluteUrl(CORE_PAGE_CONTEXT.tasks.route), - absoluteUrl(CORE_PAGE_CONTEXT.politicians.route), - absoluteUrl(CORE_PAGE_CONTEXT.scoreboard.route), - ], - status: "DRAFT", - taskKey: OPTIMIZE_EARTH_DISCOVER_PRESS_KEY, - title: "Discover country-specific journalists and coalition targets from the actual treaty queue", - }, - ]; -} - -function systemChildren(parent: TaskTreeNode): TaskTreeNode[] { - return [ - { - description: - "Ground future queue generation in existing public pages, task pages, politician pages, and manual sources so the system proposes leverageable work instead of generic consultant sludge.", - estimatedEffortHours: 2, - id: "system_ground_generator_in_existing_assets", - impact: scaleImpact(parent.impact, 0.5), - isPublic: true, - roleTitle: "System Operator", - sourceUrls: [ - absoluteUrl(CORE_PAGE_CONTEXT.tasks.route), - absoluteUrl(CORE_PAGE_CONTEXT.governments.route), - absoluteUrl(CORE_PAGE_CONTEXT.politicians.route), - absoluteUrl(CORE_PAGE_CONTEXT.video.route), - absoluteUrl(CORE_PAGE_CONTEXT.wishonia.route), - "https://manual.warondisease.org", - ], - status: "DRAFT", - taskKey: OPTIMIZE_EARTH_GROUND_GENERATOR_KEY, - title: "Ground task generation in existing pages and the Wishonia/manual context", - }, - ]; -} - -function buildActionRootImpact(input: { - action: EarthNextActionDecision; - taskTitle: string; -}): NonNullable { - const economics = input.action.economics; - const task = input.action.task; - - return { - delayDalysLostPerDay: - task?.impact?.delayDalysLostPerDay ?? - null, - delayEconomicValueUsdLostPerDay: - economics?.delayEconomicValueUsdLostPerDay ?? - task?.impact?.delayEconomicValueUsdLostPerDay ?? - null, - expectedValuePerHourDalys: - task?.impact?.expectedValuePerHourDalys ?? - null, - expectedValuePerHourUsd: - economics?.expectedValuePerHourUsd ?? - task?.impact?.expectedValuePerHourUsd ?? - null, - }; -} - -export function buildActionFollowThroughRoots(input: { - action: EarthNextActionDecision; -}) { - const task = input.action.task; - const economics = input.action.economics; - if ( - !task || - (input.action.actionKind !== "FUNDING_UNBLOCKER" && - input.action.actionKind !== "PREPARE_PROCUREMENT") - ) { - return [] as TaskTreeNode[]; - } - if ( - typeof task.taskKey === "string" && - OPTIMIZE_EARTH_ACTION_FOLLOW_THROUGH_KEY_PREFIXES.some((prefix) => - task.taskKey?.startsWith(prefix), - ) - ) { - return [] as TaskTreeNode[]; - } - - const taskSlug = slugify(task.taskKey ?? task.title); - const rootImpact = buildActionRootImpact({ - action: input.action, - taskTitle: task.title, - }); - const groundingRefs = Array.from( - new Set([ - absoluteUrl(CORE_PAGE_CONTEXT.tasks.route), - absoluteUrl(CORE_PAGE_CONTEXT.scoreboard.route), - absoluteUrl(CORE_PAGE_CONTEXT.video.route), - absoluteUrl(CORE_PAGE_CONTEXT.demo.route), - absoluteUrl(CORE_PAGE_CONTEXT.politicians.route), - ...((input.action.groundingRefs ?? []).filter(Boolean) as string[]), - EARTH_OPTIMIZATION_PRIZE_URL, - INCENTIVE_ALIGNMENT_BONDS_URL, - EARTH_OPTIMIZATION_PROTOCOL_URL, - ]), - ); - - const root: TaskTreeNode = { - description: - `Unblock ${task.title} by turning the current ${input.action.actionKind.toLowerCase().replace("_", " ")} decision into concrete, grounded funding and procurement work tied to existing Optimitron proof surfaces.`, - estimatedEffortHours: 1, - id: `system_action_follow_through_${taskSlug}`, - impact: rootImpact, - isPublic: true, - roleTitle: "System Operator", - sourceUrls: groundingRefs, - status: "DRAFT", - taskKey: buildOptimizeEarthActionFollowThroughKey(taskSlug), - title: `Unblock funding/procurement for ${task.title}`, - children: [ - { - description: - `Publish a quantified budget and expected-value brief for ${task.title} using the overdue task list, scoreboard, politician pages, and current manual sources. Include the funding gap, the max rational spend, and the exact deliverable being purchased or enabled.`, - estimatedEffortHours: 2, - id: `system_publish_budget_brief_${taskSlug}`, - impact: scaleImpact(rootImpact, 0.45), - isPublic: true, - roleTitle: "Funding Operator", - sourceUrls: [ - absoluteUrl(CORE_PAGE_CONTEXT.tasks.route), - absoluteUrl(CORE_PAGE_CONTEXT.scoreboard.route), - absoluteUrl(CORE_PAGE_CONTEXT.politicians.route), - EARTH_OPTIMIZATION_PRIZE_URL, - ...(input.action.groundingRefs ?? []), - ], - status: "DRAFT", - taskKey: buildOptimizeEarthPublishBudgetBriefKey(taskSlug), - title: `Publish the quantified budget brief for ${task.title}`, - }, - { - description: - `Add a direct funding path from existing proof surfaces into ${task.title}. Use the demo, video, overdue task list, and scoreboard pages so visitors can move from "this matters" to "fund this exact bottleneck" without wandering into generic explanation land.`, - estimatedEffortHours: 3, - id: `system_route_funding_from_existing_pages_${taskSlug}`, - impact: scaleImpact(rootImpact, 0.4), - isPublic: true, - roleTitle: "Growth Operator", - sourceUrls: [ - absoluteUrl(CORE_PAGE_CONTEXT.demo.route), - absoluteUrl(CORE_PAGE_CONTEXT.video.route), - absoluteUrl(CORE_PAGE_CONTEXT.tasks.route), - absoluteUrl(CORE_PAGE_CONTEXT.scoreboard.route), - EARTH_OPTIMIZATION_PRIZE_URL, - ], - status: "DRAFT", - taskKey: buildOptimizeEarthRouteProofPagesKey(taskSlug), - title: `Route proof-page traffic into funding for ${task.title}`, - }, - { - description: - `Source the cheapest qualified executor for ${task.title}, define the ceiling price, and make the acceptance criteria explicit before any real procurement. Use the current expected-value math and funding gap as the spend boundary.`, - estimatedEffortHours: 2, - id: `system_source_counterparties_${taskSlug}`, - impact: scaleImpact(rootImpact, 0.35), - isPublic: true, - roleTitle: "Procurement Operator", - sourceUrls: [ - absoluteUrl(CORE_PAGE_CONTEXT.tasks.route), - EARTH_OPTIMIZATION_PROTOCOL_URL, - INCENTIVE_ALIGNMENT_BONDS_URL, - ...(input.action.groundingRefs ?? []), - ], - status: "DRAFT", - taskKey: buildOptimizeEarthSourceCounterpartiesKey(taskSlug), - title: `Source counterparties and set the price ceiling for ${task.title}`, - }, - { - description: - `Prepare the approval packet for ${task.title}: funding gap ${economics?.fundingGapUsd?.toFixed(2) ?? "unknown"} USD, estimated external cost ${economics?.estimatedExternalCostUsd?.toFixed(2) ?? "unknown"} USD, max rational spend ${economics?.maxRationalSpendUsd?.toFixed(2) ?? "unknown"} USD, and the exact pages/evidence that justify paying for it now.`, - estimatedEffortHours: 1.5, - id: `system_prepare_approval_packet_${taskSlug}`, - impact: scaleImpact(rootImpact, 0.3), - isPublic: true, - roleTitle: "System Operator", - sourceUrls: groundingRefs, - status: "DRAFT", - taskKey: buildOptimizeEarthPrepareApprovalPacketKey(taskSlug), - title: `Prepare the approval packet for ${task.title}`, - }, - ], - }; - - return [root]; -} - -export function enrichOptimizeEarthBootstrapRoots(roots: TaskTreeNode[]) { - return roots.map((root) => ({ - ...root, - children: (root.children ?? []).map((child) => { - if (child.taskKey === OPTIMIZE_EARTH_GENERATE_GROWTH_TASKS_KEY) { - return { - ...child, - children: growthChildren(child), - }; - } - - if (child.taskKey === OPTIMIZE_EARTH_GENERATE_CONTACT_DISCOVERY_KEY) { - return { - ...child, - children: contactDiscoveryChildren(child), - }; - } - - if (child.taskKey === OPTIMIZE_EARTH_GENERATE_SYSTEM_IMPROVEMENT_KEY) { - return { - ...child, - children: systemChildren(child), - }; - } - - return child; - }), - })); -} diff --git a/packages/web/src/lib/site.ts b/packages/web/src/lib/site.ts index 1ceb914ff..87662be5d 100644 --- a/packages/web/src/lib/site.ts +++ b/packages/web/src/lib/site.ts @@ -22,6 +22,7 @@ import { exploreLinks, feedbackLink, footerAppLinks, + fullManualPaperLink, humanityVGovernmentLink, inviteVoterLink, navSections, @@ -490,6 +491,7 @@ const WAR_ON_DISEASE_UI: SiteVariantUiConfig = { treatyLink, humanityVGovernmentLink, onePercentTreatyPaperLink, + fullManualPaperLink, courtLink, ], },