Skip to content

[PROJ-1285] Ship Corgi product UI and guided reviewer demo - #331

Merged
AndrewNordstrom merged 18 commits into
mainfrom
dev/PROJ-1285-corgi-v2-homepage-polish
Jul 11, 2026
Merged

AndrewNordstrom merged 18 commits into
mainfrom
dev/PROJ-1285-corgi-v2-homepage-polish

Conversation

@AndrewNordstrom

@AndrewNordstrom AndrewNordstrom commented Jul 10, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Ships the final web-next product release for the logged-out Corgi experience: a polished landing page, a guided how-it-works walkthrough, coherent public/product navigation, and a real no-login multi-epoch shadow-governance demo wired to production contract v3.

Linear: PROJ-1285

The branch is based on production backend SHA d238388bb4d9228334d89e9df9c76e9f460151db from PRs #333-#335.

Changes

  • Unifies public and product surfaces around the Corgi design system, shared brand link, responsive headers/footers, page heroes, policy bars, score breakdowns, and Bluesky-faithful feed presentation.
  • Adds /how-it-works, /about, /docs, /start, /support, /proposals, and /settings static routes plus intentional error/404 surfaces.
  • Rebuilds /demo as a guided Open Science Builders flow: freeze a live-scored corpus, cast one reviewer vote, run 24 scripted deterministic voter archetypes, advance repeated shadow epochs, watch rank movement, and inspect receipt/counterfactual math.
  • Uses a fail-closed same-origin HTTP client with Zod envelope validation, request cancellation, ambiguous-mutation recovery, stale-receipt protection, and no silent production mock fallback.
  • Makes session creation retry-safe through a required client nonce, with a narrow one-request client bridge only for the already-deployed pre-nonce v3 backend.
  • Renders every returned topic term used by the authoritative weighted-sum formula so the visible receipt can be reconstructed without hidden contributions.
  • Adds mobile Ranked feed | Receipt switching, guided/free-play boundaries, focus/live-region handling, reduced-motion support, realistic local demo avatars, and responsive product page patterns.
  • Closes the final product accessibility and correctness findings: rank-popover focus return, mobile-shell focus containment, explicit flow-step states, FAQ relationships, logout in-flight protection, authoritative signal labels, invalid receipt timestamp handling, and strict replay-weight validation.
  • Resolves the exact-head reviewer pass: the landing replay is explicitly local/illustrative, backend epoch states map to the UI's open/agent-voting/closed/published contract, empty reranks clear stale receipts, public support offers email and GitHub intake, live Bluesky avatars are allowed, and the mobile menu is a fully trapped modal with inert background content.

Product Truth

  • Demo votes and epochs remain in the backend's dedicated Redis and isolated demo:* namespace.
  • The primary demo uses production-scored public posts when available and a clearly labeled fixture only when corpus loading degrades.
  • Rank badges and receipts are Corgi-site annotations; the UI does not claim Bluesky renders them natively.
  • Open Science Builders is a reviewer shadow community, not a published native Bluesky feed.
  • 1 of 25 is ballot share, not a claim of independent causal influence; the direct-ballot ablation holds scripted ballots fixed.

Verification

  • Exact-head deploy-parity npm run verify: 133 Vitest files / 1,331 tests, backend TypeScript, CLI, SDK, fixture, legacy web lint/build, and 23-route Next static export.
  • Final targeted reviewer regressions: 5 files / 39 tests.
  • cd web-next && npm run lint: no warnings or errors.
  • cd web-next && npx tsc --noEmit: passed.
  • npm run docs:verify: 14 tracked docs / 37 Markdown files.
  • Local same-origin Playwright QA passed at 1440x1100, 756x762, and 390x844: no-login session creation, live corpus, reviewer vote, 24 scripted ballots, 25/2 trimmed aggregation, epoch advance, reranking, multiple receipt selections, receipt math/provenance, and five guided epochs. The final receipt renders all four returned topic terms and their displayed contributions reconcile exactly to the weighted sum. Mobile measured 390/390 with no horizontal overflow; the menu traps Tab/Shift+Tab, marks the background inert, and restores focus on Escape; the rank explanation also restores trigger focus.
  • Against the currently deployed pre-nonce backend, QA saw the expected logged-out session 401 and one intentional legacy clientNonce rejection before the compatibility retry succeeded. The latter must disappear after this exact backend/UI head deploys.
  • Production no-login walkthrough remains a release gate after current-head review and deployment.

Checklist

  • Based on deployed shadow-demo v3 backend
  • Tests, builds, lint, types, and docs pass locally
  • CHANGELOG.md updated under ## [Unreleased]
  • Local Playwright output and scout drafts excluded
  • No secrets, private infrastructure, or production write controls introduced
  • Review gate converged through the audited timeout exemption after bounded hosted retries; zero live CodeRabbit threads remain
  • Desktop/tablet/mobile walkthrough passes on exact merge candidate
  • Exact merge SHA deploy and production route/demo smoke pass

CodeRabbit Audit

  • Status: exempt
  • Reason: Exact-head review did not converge after governed full-review triggers and a final bounded wait. The control-plane result for 92c1308f35144ad92a73ff615c31cefad5ba3314 is state=exemptable; all current-head CI is green, local CodeRabbit Major/Minor findings were fixed, and zero live CodeRabbit threads remain.
  • Follow-up: Remove the temporary exemption label after merge and retain the local-review, timeout, deploy, and production-smoke receipts in PROJ-1285. Do not post another raw or wrapper review trigger for this PR.
  • Expires: 2026-07-12

Production Delivery

  • Reviewer UI merge: 43c2370892e8c4a7cc62b3ea476247e39d47362c
  • Deploy-probe hotfix merge and runtime SHA: 14156ff641aca5eee2cc223a798cb0f617a0f82a
  • Successful deploy: run 29154641031, completed in 3m21s with no rollback
  • Full no-login production flow passed: live public corpus, reviewer vote, 24 scripted ballots, 25/2 trimmed aggregate, shadow epoch advance, 11 rank movements, receipt math, provenance, and counterfactuals
  • Public route matrix passed for 12 routes; unknown route returned 404
  • HTML returned no-cache with CSP; Next chunk returned immutable one-year caching
  • VPS exact SHA, service active, zero restarts, demo Redis noeviction at 64 MiB, zero demo/production Redis evictions, and zero demo:* keys in production Redis
  • Production browser walkthrough completed with zero console errors

@cursor

cursor Bot commented Jul 10, 2026 •

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@coderabbitai

coderabbitai Bot commented Jul 10, 2026 •

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The PR productizes the web-next experience with public landing and governance pages, a deterministic no-login shadow demo, replay and receipt views, shared policy visualizations, typed API contracts, live-data normalization, and extensive tests for ranking, privacy boundaries, routing, and request behavior.

Changes

Web-next productization

Layer / File(s) Summary
Shadow-demo contracts and deterministic engine
web-next/app/demo/*
Adds typed API envelopes, session lifecycle phases, deterministic fixtures, vote aggregation, reranking, receipts, counterfactuals, and isolation metadata.
HTTP/live data adapters
web-next/app/demo/http-shadow-demo-client.ts, web-next/app/demo/live-demo-data.ts, web-next/lib/post-uri.ts, web-next/lib/receipt-display-math.ts
Adds validated HTTP flows, cancellation and timeout handling, public-feed redaction, receipt selection, URL parsing, and display-safe receipt math.
Interactive demo workbench
web-next/app/demo/page.tsx, web-next/components/demo/*
Replaces the snapshot walkthrough with session-driven voting, agent aggregation, epoch advancement, reranked feeds, selectable posts, and receipt inspection.
Landing and replay surfaces
web-next/app/page.tsx, web-next/components/replay-teaser.tsx, web-next/app/how-it-works/page.tsx, web-next/components/how-it-works-replay.tsx
Adds interactive ranking replay, epoch comparison, movement indicators, receipt explanations, counterfactuals, and product-focused landing composition.
Governance and account pages
web-next/app/dashboard/page.tsx, web-next/app/history/page.tsx, web-next/app/vote/page.tsx, web-next/app/proposals/page.tsx, web-next/app/settings/page.tsx
Updates governance pages to use shared policy bars, current/previous weight views, epoch-scoped queries, proposal history, auth-aware settings, and simplified audit presentation.
Public information and support surfaces
web-next/app/about/*, web-next/app/docs/*, web-next/app/start/*, web-next/app/support/*, web-next/app/sign-in/*, web-next/app/error.tsx, web-next/app/not-found.tsx
Adds public informational, onboarding, support, sign-in, error, and not-found routes with shared shells and navigation.
Shared design and feed primitives
web-next/components/ui/*, web-next/components/feed/*, web-next/components/app-shell.tsx, web-next/components/header.tsx, web-next/components/footer-section.tsx
Adds reusable containers, policy bars, signal colors, feed cards, rank badges, focus behavior, route-aware navigation, logout handling, and responsive layout primitives.
Validation and documentation
tests/*, web-next/app/demo/__tests__/*, web-next/docs/design-system.md, CHANGELOG.md
Adds coverage for demo copy boundaries, API contracts, mock state transitions, ranking determinism, receipts, URL parsing, navigation anchors, and design-system rules.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related issues

  • PROJ-1431 — Implements the isolated shadow-governance reviewer flow with sessions, votes, reranking, receipts, and disclosure boundaries.
  • PROJ-1550 — Adds the product-grade homepage, guided demo, ranking replay, receipt proof, and disclosure surfaces.
  • PROJ-1285 — Expands the unauthenticated landing and read-only governance experience.
  • PROJ-1692 — Consumes and validates the hardened demo contract, envelopes, isolation metadata, receipt formulas, and corpus visibility behavior.
  • PROJ-1440 — Implements the productized no-auth reviewer demo and inspectable ranking flow.

Possibly related PRs

Suggested labels: javascript

Sequence Diagram(s)

sequenceDiagram
  participant Visitor
  participant DemoPage
  participant ShadowDemoClient
  participant DemoSession
  participant FrozenCorpus
  Visitor->>DemoPage: Start guided demo
  DemoPage->>ShadowDemoClient: createSession
  ShadowDemoClient->>DemoSession: initialize baseline epoch
  Visitor->>DemoPage: Submit policy vote
  DemoPage->>ShadowDemoClient: castVote
  DemoPage->>ShadowDemoClient: runAgents
  ShadowDemoClient->>DemoSession: aggregate synthetic votes
  Visitor->>DemoPage: Advance epoch
  DemoPage->>ShadowDemoClient: advanceEpoch
  ShadowDemoClient->>FrozenCorpus: rank before and after policy update
  ShadowDemoClient-->>DemoPage: feed snapshots and receipt data
  DemoPage-->>Visitor: render ranked feed and explanation
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.27% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR delivers the public product surfaces and the guided no-login demo flow, with read-only views, receipts, and replay tooling aligned to the linked objectives.
Out of Scope Changes check ✅ Passed The changes are broadly aligned with the productization and reviewer-demo scope; no clearly unrelated feature work stands out.
Title check ✅ Passed The title clearly summarizes the main change: shipping the Corgi product UI and guided reviewer demo.
Description check ✅ Passed The description matches the changeset, covering the new product pages, navigation, demo flow, safety, and verification work.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dev/PROJ-1285-corgi-v2-homepage-polish
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch dev/PROJ-1285-corgi-v2-homepage-polish

Warning

Review ran into problems

🔥 Problems

These MCP integrations need to be re-authenticated in the Integrations settings: Notion


Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot added documentation Improvements or additions to documentation javascript Pull requests that update javascript code labels Jul 10, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 31

Caution

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

⚠️ Outside diff range comments (3)
web-next/components/ui/score-breakdown.tsx (1)

39-51: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Preserve the signal-key invariant instead of casting arbitrary strings.

ScoreComponent.key is typed as string, but the cast only hides invalid values. For an unknown key, the label falls back to c.label while SIGNAL_COLORS[...] becomes undefined and is passed to WeightBar, producing an inconsistent or unstyled bar. Either narrow the prop to SignalKey and validate callers, or add a runtime key guard with an explicit color fallback. Add tests for canonical and unknown keys.

AI agent prompt: Trace all ScoreBreakdown callers and WeightBar’s color contract, then implement the narrowest safe fix and cover an unknown-key case.

As per path instructions, keep this TypeScript boundary type-safe and handle invalid values rather than relying on an unchecked cast.

🤖 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 `@web-next/components/ui/score-breakdown.tsx` around lines 39 - 51, Preserve
the signal-key invariant in ScoreBreakdown by tracing its callers and
WeightBar’s color contract, then either narrow ScoreComponent.key to SignalKey
with validated callers or add a runtime SignalKey guard that supplies an
explicit fallback color for unknown keys. Ensure labels, colors, and bar
rendering remain consistent, and add tests covering canonical and unknown keys
without unchecked casts.

Source: Path instructions

web-next/app/dashboard/page.tsx (1)

19-28: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Hardcoded signal-key list duplicates SIGNAL_KEYS.

web-next/lib/signals.ts already exports SIGNAL_KEYS, typed against the same EpochResponse["weights"] shape (used directly in web-next/app/proposals/page.tsx). Re-declaring the list here means it silently goes stale if a signal is ever added/renamed, and forces the as keyof typeof SIGNAL_LABELS cast downstream at line 324.

♻️ Suggested fix
-import { SIGNAL_LABELS } from "`@/lib/signals`"
+import { SIGNAL_KEYS, SIGNAL_LABELS } from "`@/lib/signals`"
 ...
-  const keys = ["recency", "engagement", "bridging", "source_diversity", "relevance"] as const
+  const keys = SIGNAL_KEYS

Also applies to: 30-65

🤖 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 `@web-next/app/dashboard/page.tsx` around lines 19 - 28, Replace the locally
hardcoded signal-key list and related key typing in the dashboard implementation
with the exported SIGNAL_KEYS from lib/signals.ts, using its inferred key type
for weight access and iteration. Update the SIGNAL_LABELS lookup to avoid the
downstream keyof cast, and remove any duplicate signal definitions across the
affected RoundDiff/dashboard logic.
web-next/app/vote/page.tsx (1)

217-232: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Locked ballot mislabels community weights as "your submitted vote".

myWeights (Line 258) comes from signals, which is seeded from myVote?.vote only when the user actually has a vote (Lines 217-232); otherwise it falls back to epoch.weights (the community average). When the round is locked and the viewer never voted — an unauthenticated visitor, or an authenticated user who missed the ballot — the locked branch still renders myWeights under the caption "Ballot locked — your submitted vote for this round is shown above" (Line 380). That's factually wrong: it's showing the community mix, not the user's vote, which is exactly the kind of "honest disclosure" the demo/product surfaces are supposed to guarantee.

🐛 Suggested fix: branch on whether the user actually voted
               {locked ? (
                 <div className="flex flex-col gap-3">
-                  <PolicyBar weights={myWeights} height={12} />
-                  <PolicyLegend weights={myWeights} />
-                  <p className="text-xs text-foreground/40 italic pt-1">Ballot locked — your submitted vote for this round is shown above.</p>
+                  <PolicyBar weights={seedVote ? myWeights : communityWeights} height={12} />
+                  <PolicyLegend weights={seedVote ? myWeights : communityWeights} />
+                  <p className="text-xs text-foreground/40 italic pt-1">
+                    {seedVote
+                      ? "Ballot locked — your submitted vote for this round is shown above."
+                      : "Ballot locked — you didn't vote this round. Showing the community mix."}
+                  </p>
                 </div>
               ) : (

Also applies to: 376-381

🤖 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 `@web-next/app/vote/page.tsx` around lines 217 - 232, Ensure the locked-ballot
caption reflects whether the user actually submitted a vote: use the presence of
myVote (or another explicit voted-state derived from it), not merely the
rendered myWeights/signals values. Update the locked rendering near the
locked-ballot message to distinguish a submitted personal vote from the epoch
community weights, and use accurate wording for users who did not vote.
🤖 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 `@tests/web-next-live-demo-data.test.ts`:
- Around line 99-208: Expand the tests around normalizeAppViewFeed and
selectReceiptPost to cover empty feeds, all-hidden feeds, missing
authors/records/labels, mixed post/author labels, null or undefined payload
fields, and malformed AppView responses. Assert normalization fails closed by
suppressing hidden text and identity, while preserving safe fallback metadata
where applicable; also verify selectReceiptPost returns undefined for empty or
all-hidden normalized posts and never selects a hidden row.
- Around line 49-56: Expand the tests for buildAppViewFeedUrl with table-driven
cases covering zero and maximum limits, malformed or missing feed URIs, and feed
values containing query-sensitive characters. Assert the expected encoded URL
parameters for valid inputs and verify invalid limits or feed values throw or
are rejected before any request is sent, using the existing Corgi URL test as
the baseline.

In `@tests/web-next-replay-model.test.ts`:
- Around line 17-56: Add table-driven Vitest coverage for
aggregatePersonaWeights and rankPostsForWeights covering empty coalitions,
duplicate and unknown persona IDs, empty post collections, score ties and
boundary scores, plus null/undefined and other invalid inputs according to the
documented throw or fail-closed behavior. Assert deterministic results and
stable tie handling; add concurrent-invocation coverage only if these helpers
gain shared mutable state.

In `@tests/web-next-score.test.ts`:
- Around line 32-36: Replace the source-text assertion for formatSignedScore in
the test with a focused unit-test suite that imports formatSignedScore from
`@/lib/score` and verifies exact output for zero, positive, negative, and a large
or rounding-boundary value. Retain only structural assertions that are still
necessary, and ensure expected strings match the function’s actual formatting
behavior.

In `@tests/web-next-shadow-demo-contract.test.ts`:
- Around line 77-95: Update the test named “keeps the initial communities and
agents bounded for v1” to explicitly enforce the authoritative community bound
for SHADOW_DEMO_COMMUNITY_IDS: assert its exact expected array or assert its
maximum length along with the required memberships. Ensure the chosen limit
matches the v1 contract and provides meaningful boundary coverage.
- Around line 98-99: The contract fixture read by the test is missing at the
referenced path, causing CI to fail before assertions run. Locate the
authoritative shadow governance contract document, then either add or commit it
at the expected path or update the test’s contractDoc source to that existing
path; preserve the read failure behavior and verify with backend-verify.
- Around line 48-75: Add a table-driven session identifier matrix to the test
covering an empty ID and IDs containing ?, #, %, Unicode, and ../, with expected
URL-encoded endpoint paths. Use each identifier across the relevant
SHADOW_DEMO_ENDPOINTS helpers and assert every result remains under /api/demo/
without governance or external route prefixes, preserving encoded boundaries and
preventing truncation, fragment injection, or traversal-like routing.

In `@web-next/app/dashboard/page.tsx`:
- Around line 365-366: Update the retry handler in the audit error card to
explicitly discard the promise returned by auditQuery.refetch(), matching the
existing handlers in the proposals and settings pages and satisfying
floating-promise lint rules.

In `@web-next/app/demo/__tests__/demo-copy.test.ts`:
- Line 4: Update the overclaim tests in allDemoCopyStrings-related assertions to
evaluate each copy string independently instead of joining them into blob,
preventing regex matches from crossing entry boundaries. Make the mutation and
native-UI checks order-independent so they catch both existing and reversed
phrasing, and add table-driven adversarial fixtures covering cross-string
boundaries, both word orders, and relevant invalid/error cases.

In `@web-next/app/demo/__tests__/http-client.test.ts`:
- Around line 142-160: Add a test alongside the existing timeout case that
supplies an AbortController signal to getFeed, aborts it immediately with an
AbortError after starting the request, and asserts the promise rejects promptly
without advancing the 10-second timer; verify pending timers are cleaned up as
appropriate.
- Around line 36-59: The HTTP client tests cover only successful envelopes and
need an error-path case for invalid server responses. Add a test around
createHttpShadowDemoClient().createSession where the mocked fetch returns a
non-2xx response with a non-JSON body, then assert the client rejects with a
clear, intentional error rather than exposing a JSON parse exception; verify the
request count as appropriate.

In `@web-next/app/demo/__tests__/mock-client-phase-order.test.ts`:
- Line 7: Extend the replay tests around the existing mutation helpers to issue
duplicate requests concurrently via Promise.all, asserting only one logical
mutation and stable sequence state while verifying the expected replay response.
Add an aborted-signal case using the local signal helper and assert cancellation
behavior. Cover zero, minimum, and maximum weight inputs, including expected
validation or mutation results, and ensure error-condition assertions accompany
the existing happy-path tests.

In `@web-next/app/demo/__tests__/mock-ranking.test.ts`:
- Around line 1-66: Add edge-case tests in the existing suite for getPresetById
with an unknown id, asserting it returns undefined; rankedIds with an empty
corpus, asserting it does not throw and returns an empty result; and
buildCounterfactuals with boundary visibleRank values such as 0 or negative,
asserting the intended behavior or clear validation error. Use the existing
symbols getPresetById, rankedIds, and buildCounterfactuals.

In `@web-next/app/demo/__tests__/mock-receipt.test.ts`:
- Around line 7-31: Add coverage in mock-receipt.test.ts for a pre-aborted
signal, unknown session ID, unknown epoch ID, and malformed or empty post URI,
using the existing client flow and helpers. For each rejected getReceipt call,
assert the documented stable error code and status rather than only asserting
rejection; keep the existing successful receipt math test unchanged.
- Around line 34-50: Update the “refuses a receipt for a withheld (hidden) row”
test to obtain the hidden post URI from the existing fixture or helper rather
than hard-coding H1, ensuring it is verified as part of the frozen corpus.
Assert the specific withheld-row error message or type from client.getReceipt,
while leaving the separate out-of-corpus test to cover generic rejection.

In `@web-next/app/demo/http-shadow-demo-client.ts`:
- Around line 648-655: Replace hiddenReason’s free-text substring matching with
a structured API reason code and an exhaustive switch mapping to the supported
categories. Update the caller and related types to pass the structured code,
explicitly handle every known reason, and make unknown codes fail loudly rather
than defaulting to deleted_or_unavailable.

In `@web-next/app/demo/live-demo-data.ts`:
- Around line 430-447: The hiddenPost function currently exposes the source post
URI, leaking identity in hidden rows. Set uri to null instead of copying
post?.uri, and add a regression test covering an at:// URI that asserts the
normalized hidden post has null uri, author fields, text, and URL.
- Around line 379-384: Sanitize the non-OK response handling in the live-demo
fetch helper by removing the raw body from the LiveDemoDataError message; retain
only the context, HTTP status, and safe status text, while logging the response
body only through server-side diagnostics if needed. Add or update tests for
fetchLiveDemoData with a 500 response containing a secret and assert that the
secret is absent from LiveDemoData.errors.

In `@web-next/app/demo/mock-shadow-demo-client.ts`:
- Around line 200-213: Namespace idempotency cache keys by action to prevent
payload collisions across endpoints. Update replay<T>() to accept an action
namespace and build the lookup key from it plus the raw idempotency key, then
update castVote, runAgents, and advanceEpoch replay calls and corresponding
state.idempotency.set calls to use matching namespaced keys.
- Around line 465-471: Replace the non-null assertion in getFeed with explicit
narrowing: when isPublished is true, first verify state.publishedEpoch exists,
then use its weights; otherwise use state.openEpoch.weights. Preserve the
existing previous baseline selection and add an appropriate failure path if the
published epoch is unexpectedly missing.

In `@web-next/app/demo/page.tsx`:
- Around line 224-231: Document or eliminate the implicit capability coupling in
handleAnotherEpoch: preferably introduce a shared transition/capability helper
used by both handleAnotherEpoch and mapSession, or re-derive capabilities when
setting the "corpus_ready" phase; at minimum add a comment referencing
mapSession and its capability logic so future matrix changes are traceable.

In `@web-next/app/demo/shadow-demo-contract.ts`:
- Around line 21-28: Expose the advertised corpus refresh capability in the
contract: add a typed refresh-corpus endpoint to SHADOW_DEMO_ENDPOINTS, define
its request and response envelope types including idempotency and base-epoch
context, and implement the corresponding ShadowDemoClient.refreshCorpus method
with abort-signal support. Add contract tests covering the refresh flow, or
remove refresh_corpus and canRefreshCorpus if refresh is intentionally local.

In `@web-next/app/docs/page.tsx`:
- Line 109: Update the “Source and API” section near the TODO to either link to
the canonical published OpenAPI/API reference or remove the API claim if no
stable page exists; add a route/content assertion covering the section that
fails when the link is absent or remains a placeholder.

In `@web-next/app/settings/page.tsx`:
- Around line 44-48: Extract the duplicated formatDate logic into a shared
helper in a lib module, supporting an optional withTime setting for the
differing date/time formats. Remove the local helpers from the settings and
proposals pages and import the shared formatDate, preserving each page’s
existing formatting behavior.

In `@web-next/app/support/page.tsx`:
- Around line 10-11: Replace the draft/unverified support contact and GitHub
issue destination in the support page with confirmed, monitored production
channels, and remove the “[DRAFT]”/TODO wording near GITHUB_ISSUES and the
contact details. Ensure bug reports, security disclosures, and account-help
requests each point to valid launch-ready destinations.

In `@web-next/components/feed/bluesky-feed.tsx`:
- Around line 18-29: The engagement metrics UI currently converts absent values
to fabricated zero counts via formatCount and unconditional action rendering.
Update the actions-row rendering and metric components around the
likes/replies/reposts display to render each metric only when its count is
non-null and non-undefined, while preserving genuine numeric zero values; add
focused tests covering likes-only, replies-only, and null/undefined inputs.

In `@web-next/components/feed/corgi-rank-badge.tsx`:
- Around line 102-122: Remove role="dialog" from the lightweight popover and use
an appropriate non-modal role such as role="group" (or tooltip if semantically
accurate), updating related ARIA attributes accordingly; alternatively, if
retaining the dialog role, implement aria-modal, initial focus, and Tab focus
trapping in the component’s open-state effect.
- Around line 147-160: Update the popover rendered by CorgiRankBadge to avoid
viewport overflow: replace the rigid 300px width and right-anchored positioning
with responsive sizing and viewport-aware horizontal constraints, while
preserving the existing desktop appearance and alignment. Use the panel’s
motion.div styles/classes and related panel positioning logic to ensure it
remains visible when embedded in narrow feed columns.

In `@web-next/components/get-started-section.tsx`:
- Around line 6-7: Update the get-started section copy to remove “read-only” and
accurately describe the /demo flow as a no-login or interactive shadow demo.
Explicitly state that visitors can participate—including voting and exploring
reranking—without connecting an account; update the related account-connection
text as well.

In `@web-next/components/header.tsx`:
- Around line 106-109: Extract the duplicated active-link calculation into a
shared helper function in the header component, preserving the existing
exclusion for "/#faq-section" and pathname matching behavior. Replace the inline
computations in both desktop and mobile nav render blocks with calls to this
helper.

In `@web-next/components/ui/policy-bar.tsx`:
- Line 14: Update the segment rendering logic in the policy bar component to use
each raw normalized weight for CSS width values, while retaining rounded
percentages only for visible labels, titles, or legend text. Replace any width
usage of pct with an unrounded fractional-to-percent value, and add a component
regression test using weights 0.004, 0.004, 0.004, 0.004, and 0.984 that
verifies the rendered segment widths preserve the complete bar.

---

Outside diff comments:
In `@web-next/app/dashboard/page.tsx`:
- Around line 19-28: Replace the locally hardcoded signal-key list and related
key typing in the dashboard implementation with the exported SIGNAL_KEYS from
lib/signals.ts, using its inferred key type for weight access and iteration.
Update the SIGNAL_LABELS lookup to avoid the downstream keyof cast, and remove
any duplicate signal definitions across the affected RoundDiff/dashboard logic.

In `@web-next/app/vote/page.tsx`:
- Around line 217-232: Ensure the locked-ballot caption reflects whether the
user actually submitted a vote: use the presence of myVote (or another explicit
voted-state derived from it), not merely the rendered myWeights/signals values.
Update the locked rendering near the locked-ballot message to distinguish a
submitted personal vote from the epoch community weights, and use accurate
wording for users who did not vote.

In `@web-next/components/ui/score-breakdown.tsx`:
- Around line 39-51: Preserve the signal-key invariant in ScoreBreakdown by
tracing its callers and WeightBar’s color contract, then either narrow
ScoreComponent.key to SignalKey with validated callers or add a runtime
SignalKey guard that supplies an explicit fallback color for unknown keys.
Ensure labels, colors, and bar rendering remain consistent, and add tests
covering canonical and unknown keys without unchecked casts.
🪄 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: Repository YAML (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: aaf962f4-0a73-4a9d-94fa-b29e12b0c844

📥 Commits

Reviewing files that changed from the base of the PR and between ac0d57c and 393601b.

⛔ Files ignored due to path filters (20)
  • web-next/public/images/avatars/arjun-mehta.png is excluded by !**/*.png
  • web-next/public/images/avatars/avery-brooks.png is excluded by !**/*.png
  • web-next/public/images/avatars/ben-adler.png is excluded by !**/*.png
  • web-next/public/images/avatars/claire-rowan.png is excluded by !**/*.png
  • web-next/public/images/avatars/daniel-weiss.png is excluded by !**/*.png
  • web-next/public/images/avatars/eli-moreno.png is excluded by !**/*.png
  • web-next/public/images/avatars/jules-park.png is excluded by !**/*.png
  • web-next/public/images/avatars/karen-miller.png is excluded by !**/*.png
  • web-next/public/images/avatars/leila-hart.png is excluded by !**/*.png
  • web-next/public/images/avatars/mara-finch.png is excluded by !**/*.png
  • web-next/public/images/avatars/marco-haddad.png is excluded by !**/*.png
  • web-next/public/images/avatars/maya-keene.png is excluded by !**/*.png
  • web-next/public/images/avatars/nick-salazar.png is excluded by !**/*.png
  • web-next/public/images/avatars/nina-valdez.png is excluded by !**/*.png
  • web-next/public/images/avatars/rowan-bell.png is excluded by !**/*.png
  • web-next/public/images/avatars/sam-ortega.png is excluded by !**/*.png
  • web-next/public/images/avatars/tessa-green.png is excluded by !**/*.png
  • web-next/public/images/avatars/theo-kim.png is excluded by !**/*.png
  • web-next/public/images/bluesky-butterfly-logo.svg is excluded by !**/*.svg
  • web-next/public/images/feed/birders-field-notes.png is excluded by !**/*.png
📒 Files selected for processing (88)
  • CHANGELOG.md
  • tests/web-next-demo-fixtures.test.ts
  • tests/web-next-homepage-anchors.test.ts
  • tests/web-next-live-demo-data.test.ts
  • tests/web-next-replay-model.test.ts
  • tests/web-next-score.test.ts
  • tests/web-next-shadow-demo-contract.test.ts
  • web-next/app/about/page.tsx
  • web-next/app/admin/page.tsx
  • web-next/app/dashboard/page.tsx
  • web-next/app/demo/__tests__/_flow.ts
  • web-next/app/demo/__tests__/demo-copy.test.ts
  • web-next/app/demo/__tests__/demo-request-coordinator.test.ts
  • web-next/app/demo/__tests__/http-client.test.ts
  • web-next/app/demo/__tests__/mock-client-phase-order.test.ts
  • web-next/app/demo/__tests__/mock-ranking.test.ts
  • web-next/app/demo/__tests__/mock-receipt.test.ts
  • web-next/app/demo/demo-client.ts
  • web-next/app/demo/demo-request-coordinator.ts
  • web-next/app/demo/http-shadow-demo-client.ts
  • web-next/app/demo/live-demo-data.ts
  • web-next/app/demo/mock-shadow-demo-client.ts
  • web-next/app/demo/page.tsx
  • web-next/app/demo/shadow-demo-contract.ts
  • web-next/app/demo/shadow-demo-copy.ts
  • web-next/app/demo/shadow-demo-fixtures.ts
  • web-next/app/docs/page.tsx
  • web-next/app/error.tsx
  • web-next/app/history/page.tsx
  • web-next/app/how-it-works/page.tsx
  • web-next/app/layout.tsx
  • web-next/app/not-found.tsx
  • web-next/app/page.tsx
  • web-next/app/post/page.tsx
  • web-next/app/privacy/page.tsx
  • web-next/app/proposals/page.tsx
  • web-next/app/research-consent/page.tsx
  • web-next/app/settings/page.tsx
  • web-next/app/sign-in/page.tsx
  • web-next/app/start/page.tsx
  • web-next/app/support/page.tsx
  • web-next/app/tos/page.tsx
  • web-next/app/vote/page.tsx
  • web-next/components/app-shell.tsx
  • web-next/components/bento-section.tsx
  • web-next/components/bluesky-sample-feed.tsx
  • web-next/components/brand-link.tsx
  • web-next/components/changelog-section.tsx
  • web-next/components/community-examples-section.tsx
  • web-next/components/cta-section.tsx
  • web-next/components/dashboard-preview.tsx
  • web-next/components/demo/agents-panel.tsx
  • web-next/components/demo/community-picker.tsx
  • web-next/components/demo/corpus-feed.tsx
  • web-next/components/demo/demo-disclosure.tsx
  • web-next/components/demo/flow-progress.tsx
  • web-next/components/demo/receipt-panel.tsx
  • web-next/components/demo/vote-panel.tsx
  • web-next/components/demo/weight-bars.tsx
  • web-next/components/faq-section.tsx
  • web-next/components/feed/bluesky-feed.tsx
  • web-next/components/feed/corgi-rank-badge.tsx
  • web-next/components/feed/replay-adapter.ts
  • web-next/components/footer-section.tsx
  • web-next/components/get-started-section.tsx
  • web-next/components/header.tsx
  • web-next/components/hero-section.tsx
  • web-next/components/how-it-works-replay.tsx
  • web-next/components/landing-ctas.tsx
  • web-next/components/legal-layout.tsx
  • web-next/components/modality-preview.tsx
  • web-next/components/replay-teaser.tsx
  • web-next/components/sign-in-dialog.tsx
  • web-next/components/social-proof.tsx
  • web-next/components/ui/keyword-input.tsx
  • web-next/components/ui/layout.tsx
  • web-next/components/ui/linked-slider.tsx
  • web-next/components/ui/page-hero.tsx
  • web-next/components/ui/policy-bar.tsx
  • web-next/components/ui/score-breakdown.tsx
  • web-next/components/ui/topic-slider.tsx
  • web-next/components/ui/weight-bar.tsx
  • web-next/docs/design-system.md
  • web-next/lib/live-metrics-snapshot.ts
  • web-next/lib/replay-model.ts
  • web-next/lib/signals.ts
  • web-next/next.config.mjs
  • web-next/tailwind.config.ts

Comment thread tests/web-next-live-demo-data.test.ts
Comment thread tests/web-next-live-demo-data.test.ts
Comment thread tests/web-next-replay-model.test.ts
Comment thread tests/web-next-score.test.ts
Comment thread tests/web-next-shadow-demo-contract.test.ts Outdated
Comment thread web-next/components/feed/corgi-rank-badge.tsx
Comment thread web-next/components/feed/corgi-rank-badge.tsx
Comment thread web-next/components/get-started-section.tsx Outdated
Comment thread web-next/components/header.tsx Outdated
Comment thread web-next/components/ui/policy-bar.tsx Outdated
@AndrewNordstrom

Copy link
Copy Markdown
Collaborator Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 10, 2026 •

Copy link
Copy Markdown
✅ Action performed

Full review finished.


You're currently rate limited under our Fair Usage Limits Policy. Your recent PR review activity is in the 95th percentile or higher among CodeRabbit users, so adaptive limits apply. Your next review will be available in 56 minutes.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

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

⚠️ Outside diff range comments (7)
web-next/app/dashboard/page.tsx (2)

35-35: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Hardcoded signal-key list duplicates the canonical SIGNAL_KEYS/SIGNAL_LABELS source.

SIGNAL_LABELS is already imported from @/lib/signals (Line 12), but the keys used to derive weight deltas are re-typed locally instead of importing SIGNAL_KEYS. If the canonical signal set ever changes (new/removed/renamed signal), this local list silently drifts from EpochResponse["weights"], causing weight_changes to miss or misreport deltas without any type error to catch it.

♻️ Proposed fix: import SIGNAL_KEYS instead of hardcoding
-import { SIGNAL_LABELS } from "`@/lib/signals`"
+import { SIGNAL_KEYS, SIGNAL_LABELS } from "`@/lib/signals`"
-  const keys = ["recency", "engagement", "bridging", "source_diversity", "relevance"] as const
-  const weight_changes = keys
+  const weight_changes = SIGNAL_KEYS
     .map((key) => {
#!/bin/bash
# Confirm SIGNAL_KEYS export and its value/order to validate the drift risk.
rg -n -B1 -A6 'export const SIGNAL_KEYS' web-next/lib/signals.ts
🤖 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 `@web-next/app/dashboard/page.tsx` at line 35, Replace the locally hardcoded
keys array in the weight-delta logic with the canonical SIGNAL_KEYS import from
"`@/lib/signals`", alongside SIGNAL_LABELS. Update the derived iteration to use
SIGNAL_KEYS so it stays synchronized with EpochResponse["weights"] and avoids
duplicated signal definitions.

238-279: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Query failures are silently masked as "no data yet" states.

Neither statsQuery nor epochsQuery branches on isError. On a real backend failure: the "Feed health" strip just vanishes (Line 242 falls through to null), and "What changed this round" (Line 291) shows "Nothing to compare yet" — implying two rounds simply haven't happened yet, when the actual cause is a failed fetch. This hides real outages from users/reviewers trying to diagnose the dashboard.

🐛 Proposed fix: surface explicit error states
-        ) : stats && (participation != null || feed || metrics) ? (
+        ) : statsQuery.isError ? (
+          <ErrorCard heading="Feed health unavailable" body="We couldn't load feed health metrics." onRetry={() => void statsQuery.refetch()} />
+        ) : stats && (participation != null || feed || metrics) ? (
-            {epochsQuery.isLoading ? (
+            {epochsQuery.isLoading ? (
               <WeightsSkeleton />
+            ) : epochsQuery.isError ? (
+              <ErrorCard heading="Round comparison unavailable" body="We couldn't load recent governance rounds." onRetry={() => void epochsQuery.refetch()} />
             ) : !diff ? (

Also applies to: 289-343

🤖 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 `@web-next/app/dashboard/page.tsx` around lines 238 - 279, Surface explicit
failure states for both statsQuery and epochsQuery instead of falling through to
empty-state messaging. In the dashboard rendering around the Feed health section
and the “What changed this round” section, check isError before no-data
conditions and render clear error text (optionally with retry controls) for each
failed query; retain loading and successful data states unchanged.
web-next/app/demo/shadow-demo-contract.ts (1)

70-88: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Expose a usable refresh-corpus contract.

refreshCorpus is advertised on ShadowDemoClient, but no corresponding endpoint exists in SHADOW_DEMO_ENDPOINTS or SHADOW_DEMO_ENDPOINT_TEMPLATES. It also accepts CreateShadowDemoSessionRequest, which cannot express a session-scoped refresh with its idempotency and base-epoch context. Add a typed refresh endpoint/request/response and implementations, or remove the capability if refresh is intentionally local.

AI agent prompt

Add a session-scoped refreshCorpus endpoint and request type containing the required session, idempotency, and base-epoch fields; implement it in HTTP and mock clients with abort support and add contract coverage. Otherwise remove refreshCorpus and canRefreshCorpus from the public contract.

Also applies to: 422-425

🤖 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 `@web-next/app/demo/shadow-demo-contract.ts` around lines 70 - 88, Expose a
complete session-scoped refreshCorpus contract, or remove the advertised
capability if it is intentionally local. Add typed endpoint and template entries
plus a request containing session ID, idempotency, and base-epoch context, then
update ShadowDemoClient HTTP and mock implementations with abort support and
contract tests; use the existing refreshCorpus, canRefreshCorpus,
SHADOW_DEMO_ENDPOINTS, and SHADOW_DEMO_ENDPOINT_TEMPLATES symbols to locate all
affected code.

Source: MCP tools

web-next/app/demo/page.tsx (1)

228-234: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Cancel in-flight work before starting another epoch.

handleAnotherEpoch clears receipt state but leaves the current coordinated request active. If another receipt is loading, it can complete afterward and repopulate stale state. Cancel the coordinator and reset busy before changing rounds.

Proposed fix
 function handleAnotherEpoch(): void {
   if (session === null || feedAfter === null || publishedEpoch === null) {
     return
   }
+  requestCoordinator.current.cancel()
+  setBusy(false)
   setStartingNextEpoch(true)
   setBaselineFeed(feedAfter)
   clearRoundState()
 }
AI agent prompt
Cancel the active DemoRequestCoordinator request before handleAnotherEpoch clears round state, and reset busy because the invalidated request's finally block will not do so. Add a test where receipt loading remains pending while another epoch starts, then resolves, and assert stale receipt/selection state is not restored.

As per path instructions, **/*.ts requires async correctness and prevention of unhandled stale operations.

🤖 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 `@web-next/app/demo/page.tsx` around lines 228 - 234, Update handleAnotherEpoch
to cancel the active DemoRequestCoordinator request and reset busy before
calling clearRoundState or changing epoch state. Ensure the cancellation safely
handles pending receipt work without unhandled stale operations, and add a test
covering a pending receipt request that resolves after starting another epoch,
verifying stale receipt and selection state are not restored.

Source: Path instructions

web-next/app/demo/live-demo-data.ts (1)

379-390: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Sanitize malformed-JSON errors as well.

The non-OK path is now sanitized, but Line 390 includes JSON.parse’s exception text. Browser parser messages can quote part of the malformed body, allowing a 200 response containing sensitive text to reach LiveDemoData.errors.

Return a fixed "returned invalid JSON" message instead.

AI agent prompt
Remove JSON.parse exception text from the public LiveDemoDataError message. Add a test with a 200 malformed response containing a secret and assert neither the thrown error nor LiveDemoData.errors contains that secret.
🤖 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 `@web-next/app/demo/live-demo-data.ts` around lines 379 - 390, Sanitize the
malformed-JSON error in the response parsing within the live demo data request
flow: replace the errorMessage(error) detail in the LiveDemoDataError with the
fixed message “${context} returned invalid JSON”. Add a test for a 200 response
containing malformed JSON with a secret, asserting the thrown error and
LiveDemoData.errors do not contain that secret.
web-next/app/demo/http-shadow-demo-client.ts (2)

417-421: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Do not expose server-provided error messages directly.

Line 419 accepts arbitrary body.message; DemoPage later renders that error publicly. A backend or proxy error can therefore disclose stack details, identifiers, or PII. Map failures to a stable client-safe message and retain diagnostics server-side.

AI agent prompt
Sanitize requestApi non-2xx errors. Do not propagate arbitrary response.message values to the UI. Preserve only explicitly validated public error codes/messages, otherwise return a generic HTTP-status error. Add a response containing a secret message and assert it is absent from the thrown error.
🤖 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 `@web-next/app/demo/http-shadow-demo-client.ts` around lines 417 - 421,
Sanitize non-success errors in requestApi by removing direct use of arbitrary
body.message from the thrown UI error. Allow only explicitly validated
client-safe error codes or messages; otherwise throw a stable generic message
based on the HTTP status, while retaining detailed diagnostics only in
server-side logging. Add a test using a secret response message and assert that
the thrown error does not contain it.

423-432: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Parse the full API envelope before casting it web-next/app/demo/http-shadow-demo-client.ts:423-432

requestApi only checks contractVersion and then casts unknown JSON to ApiEnvelope<TPayload>. A response like { contractVersion, payload: {} } will still pass this gate and fail later in mapSession, mapFeed, or mapReceipt when nested fields are read. Add a schema for the shared envelope plus each endpoint payload, and reject missing or wrongly typed nested fields at the boundary.

AI agent prompt
Add Zod schemas for the common API envelope and each payload type used by requestApi. Parse the unknown JSON before returning it and emit a stable invalid-envelope error for missing, null, or incorrectly typed nested fields. Extend http-client.test.ts with malformed session, feed, vote, and receipt payloads.
🤖 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 `@web-next/app/demo/http-shadow-demo-client.ts` around lines 423 - 432, Replace
the shallow validation in requestApi with Zod schemas for the shared ApiEnvelope
and each session, feed, vote, and receipt payload, validating the full unknown
response before casting or returning it. Ensure missing, null, and incorrectly
typed nested fields produce the stable invalid-envelope error, and add
malformed-payload coverage for all four endpoints in http-client.test.ts.

Source: Path instructions

🤖 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 `@tests/web-next-shadow-demo-contract.test.ts`:
- Around line 106-128: Replace the prefix-only assertions in the “encodes
session identifier boundaries” test with exact expected paths for each
SHADOW_DEMO_ENDPOINTS helper, including every suffix and query string. Add
explicit runtime tests for null and undefined session IDs if the helpers must
reject them; otherwise document that validation is TypeScript-only and exclude
those values from runtime expectations.

In `@web-next/app/demo/__tests__/demo-copy.test.ts`:
- Around line 7-14: Update containsMutationOverclaim to assess negation within
each sentence or clause rather than returning false for a negation anywhere in
the full string; preserve fully negated claims as allowed while detecting later
affirmative mutation overclaims. Add regression tests covering mixed
negated/affirmative claims, fully negated claims, and relevant sentence/clause
boundary cases, following the patterns used by containsNativeUiOverclaim.

In `@web-next/app/demo/mock-shadow-demo-client.ts`:
- Around line 479-484: Update getFeed to validate request.epochId against
state.openEpoch.id and state.publishedEpoch.id, rejecting unknown epochs before
selecting weights or baseline ranks. Preserve the requested epoch by explicitly
setting the returned feed.epochId to request.epochId after buildFeed; add tests
covering unknown, open/closed, and published epoch requests.
- Around line 284-288: Add a shared throwIfAborted(signal) helper in
createMockShadowDemoClient that throws the signal’s Error-valued reason when
present, otherwise an abort error, and invoke it before any state reads or
mutations in every client method, including refreshCorpus, castVote, runAgents,
advanceEpoch, getFeed, and getReceipt. Add pre-aborted tests verifying
createSession, castVote, runAgents, advanceEpoch, getFeed, and getReceipt leave
session sequence and phase unchanged.

In `@web-next/app/support/page.tsx`:
- Line 83: Replace the “Private account details” guidance in the support page
with a verified private account-support channel, or remove it if no such channel
exists; never direct users to public GitHub issues for account context, and
explicitly prohibit sharing credentials, app passwords, or sensitive account
identifiers.

---

Outside diff comments:
In `@web-next/app/dashboard/page.tsx`:
- Line 35: Replace the locally hardcoded keys array in the weight-delta logic
with the canonical SIGNAL_KEYS import from "`@/lib/signals`", alongside
SIGNAL_LABELS. Update the derived iteration to use SIGNAL_KEYS so it stays
synchronized with EpochResponse["weights"] and avoids duplicated signal
definitions.
- Around line 238-279: Surface explicit failure states for both statsQuery and
epochsQuery instead of falling through to empty-state messaging. In the
dashboard rendering around the Feed health section and the “What changed this
round” section, check isError before no-data conditions and render clear error
text (optionally with retry controls) for each failed query; retain loading and
successful data states unchanged.

In `@web-next/app/demo/http-shadow-demo-client.ts`:
- Around line 417-421: Sanitize non-success errors in requestApi by removing
direct use of arbitrary body.message from the thrown UI error. Allow only
explicitly validated client-safe error codes or messages; otherwise throw a
stable generic message based on the HTTP status, while retaining detailed
diagnostics only in server-side logging. Add a test using a secret response
message and assert that the thrown error does not contain it.
- Around line 423-432: Replace the shallow validation in requestApi with Zod
schemas for the shared ApiEnvelope and each session, feed, vote, and receipt
payload, validating the full unknown response before casting or returning it.
Ensure missing, null, and incorrectly typed nested fields produce the stable
invalid-envelope error, and add malformed-payload coverage for all four
endpoints in http-client.test.ts.

In `@web-next/app/demo/live-demo-data.ts`:
- Around line 379-390: Sanitize the malformed-JSON error in the response parsing
within the live demo data request flow: replace the errorMessage(error) detail
in the LiveDemoDataError with the fixed message “${context} returned invalid
JSON”. Add a test for a 200 response containing malformed JSON with a secret,
asserting the thrown error and LiveDemoData.errors do not contain that secret.

In `@web-next/app/demo/page.tsx`:
- Around line 228-234: Update handleAnotherEpoch to cancel the active
DemoRequestCoordinator request and reset busy before calling clearRoundState or
changing epoch state. Ensure the cancellation safely handles pending receipt
work without unhandled stale operations, and add a test covering a pending
receipt request that resolves after starting another epoch, verifying stale
receipt and selection state are not restored.

In `@web-next/app/demo/shadow-demo-contract.ts`:
- Around line 70-88: Expose a complete session-scoped refreshCorpus contract, or
remove the advertised capability if it is intentionally local. Add typed
endpoint and template entries plus a request containing session ID, idempotency,
and base-epoch context, then update ShadowDemoClient HTTP and mock
implementations with abort support and contract tests; use the existing
refreshCorpus, canRefreshCorpus, SHADOW_DEMO_ENDPOINTS, and
SHADOW_DEMO_ENDPOINT_TEMPLATES symbols to locate all affected code.
🪄 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: Repository YAML (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 02ad673e-ff67-4a92-a9ba-09ac7ca7acc2

📥 Commits

Reviewing files that changed from the base of the PR and between 393601b and 7aabb75.

📒 Files selected for processing (23)
  • CHANGELOG.md
  • tests/web-next-live-demo-data.test.ts
  • tests/web-next-shadow-demo-contract.test.ts
  • tests/web-next-shadow-demo-ui.test.ts
  • web-next/app/dashboard/page.tsx
  • web-next/app/demo/__tests__/demo-copy.test.ts
  • web-next/app/demo/__tests__/http-client.test.ts
  • web-next/app/demo/__tests__/mock-client-phase-order.test.ts
  • web-next/app/demo/__tests__/mock-receipt.test.ts
  • web-next/app/demo/http-shadow-demo-client.ts
  • web-next/app/demo/live-demo-data.ts
  • web-next/app/demo/mock-shadow-demo-client.ts
  • web-next/app/demo/page.tsx
  • web-next/app/demo/shadow-demo-contract.ts
  • web-next/app/demo/shadow-demo-contract.type-test.ts
  • web-next/app/docs/page.tsx
  • web-next/app/support/page.tsx
  • web-next/components/feed/bluesky-feed.tsx
  • web-next/components/feed/corgi-rank-badge.tsx
  • web-next/components/get-started-section.tsx
  • web-next/components/header.tsx
  • web-next/components/ui/__tests__/policy-bar.test.ts
  • web-next/components/ui/policy-bar.tsx

Comment thread tests/web-next-shadow-demo-contract.test.ts Outdated
Comment thread web-next/app/demo/__tests__/demo-copy.test.ts Outdated
Comment thread web-next/app/demo/mock-shadow-demo-client.ts Outdated
Comment thread web-next/app/demo/mock-shadow-demo-client.ts
Comment thread web-next/app/support/page.tsx Outdated
@coderabbitai

coderabbitai Bot commented Jul 10, 2026 •

Copy link
Copy Markdown
✅ Action performed

Full review finished.


You're currently rate limited under our Fair Usage Limits Policy. Your recent PR review activity is in the 95th percentile or higher among CodeRabbit users, so adaptive limits apply. Your next review will be available in 26 minutes.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 25

🤖 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 `@tests/web-next-demo-fixtures.test.ts`:
- Around line 286-303: The guardrail in the test named “keeps the primary public
demo out of the redacted snapshot happy path” only scans source strings and
misses indirect or dynamic dependencies. Add a render-based test using the demo
route/page tree to assert the rendered DOM excludes “Anonymized receipt” and
“Post text redacted,” including indirect imports. Expand the shadow-demo-copy.ts
assertions with negative or boundary cases that detect missing, partial, or
incorrectly cased disclosure wording.

In `@tests/web-next-live-demo-data.test.ts`:
- Around line 181-199: Update the fetch mock in the “does not expose upstream
error bodies in public demo errors” test to use mockImplementation and create a
new Response for each invocation, since fetchLiveDemoData issues three parallel
requests. Preserve assertions that all three errors report HTTP 500 and exclude
the upstream secret and stack-trace content.

In `@web-next/app/about/page.tsx`:
- Line 11: Resolve all unverified About-page claims in the component rendering
the route, including stewardship, funding, advertising/data-sale practices,
append-only auditing, vote privacy, collected data, and contact details; replace
them with source-backed facts or keep the route gated behind an explicit draft
flag. Remove draft/TODO markers and placeholder claims from rendered content,
and add a snapshot test covering the rendered About page that fails if such
markers or placeholders appear.

In `@web-next/app/dashboard/page.tsx`:
- Around line 237-278: Surface stats-query failures in the dashboard instead of
rendering nothing. In the stats rendering logic around statsQuery.isLoading and
the feed-health success condition, add a statsQuery.isError branch that renders
ErrorCard with a retry action invoking void statsQuery.refetch(), then retain
the existing success branch. Add or update tests covering loading, successful
feed-health rendering, and error states including retry recovery.

In `@web-next/app/demo/__tests__/demo-copy.test.ts`:
- Around line 37-70: The predicate tests for containsMutationOverclaim and
containsNativeUiOverclaim lack empty and boundary coverage. Add table-driven
cases covering empty and whitespace strings, newline boundaries, missing
terminal punctuation, adjacent clause separators, and null/undefined inputs;
preserve the current contract that null and undefined are excluded unless the
helpers are intentionally changed.

In `@web-next/app/demo/__tests__/demo-request-coordinator.test.ts`:
- Around line 4-25: Add boundary and failure-path tests to the
DemoRequestCoordinator suite: verify cancel() is safe with no active request and
when called repeatedly, verify start() creates a valid current request after
cancellation, and verify three rapid start() calls abort and invalidate the
first two while only the newest remains current. Do not change the production
API or add argument-based cases.

In `@web-next/app/demo/__tests__/mock-client-phase-order.test.ts`:
- Around line 72-91: Update the “rejects a stale baseEpochId” test to first
advance the session via driveFullFlow(), capture the known previous epoch, then
issue both a mutation and read request using that closed epoch. Assert each
rejects with stale_epoch and verify the session’s current epoch remains
unchanged; replace the unknown epoch ID case so the test covers a previously
valid stale epoch.

In `@web-next/app/demo/http-shadow-demo-client.ts`:
- Around line 76-97: Remove the locally declared ApiVoteBase, ApiReviewerVote,
ApiSyntheticVote, and ApiVote types, then import ShadowDemoApiVote,
ShadowDemoApiReviewerVote, and ShadowDemoApiSyntheticVote from
shadow-demo-contract. Update ApiEpoch, ApiSessionPayload, and mapVote to use the
imported contract types consistently.
- Around line 393-433: Harden requestApi by never exposing body.message
directly: log server details separately and throw a fixed HTTP failure message.
Add Zod or equivalent runtime validation for the envelope fields requestId,
generatedAt, sessionId, warnings, and endpoint-specific payloads before mapper
functions execute; update requestApi callers to supply the appropriate payload
schema and throw a clear validation error. Add tests covering malformed payloads
and HTML/plaintext error responses.
- Around line 247-275: The castVote HTTP request omits the caller’s voterLabel.
In castVote, add voterLabel: request.voterLabel to the POST body, then add or
update a test verifying the client sends that field and maps the returned
reviewerVote.voterLabel to the input value.

In `@web-next/app/demo/live-demo-data.ts`:
- Around line 364-390: The fetchJson HTTP boundary only performs a TypeScript
cast, allowing malformed nested payloads to cause raw TypeErrors and discard the
entire feed. Add runtime validation schemas or guards before
normalizeAppViewFeed, labelValuesFromLabels, and scoreComponentsFromExplanation;
reject or isolate invalid rows while preserving valid feed entries, including
null feed items, labels, components, and relevance.topicBreakdown. Add tests
covering each malformed payload and a mixed feed proving one invalid item does
not discard valid rows.
- Around line 381-384: Remove response.statusText from the LiveDemoDataError
message in the HTTP failure branch of the live demo loader, keeping only the
context and numeric response.status. Add a regression test with a custom
statusText and assert that it is absent from result.errors.

In `@web-next/app/demo/mock-shadow-demo-client.ts`:
- Around line 516-544: Update getReceipt to validate request.epochId against the
session’s current openEpoch.id or publishedEpoch.id, rejecting stale or unknown
epochs before calculating weights. Stamp the receipt only with the validated
requested epoch, mirroring getFeed’s epoch-handling behavior, and add coverage
in mock-receipt.test.ts for stale and unknown epochId requests.

In `@web-next/app/demo/shadow-demo-contract.ts`:
- Around line 277-286: Shadow demo status unions include states that the current
mappers never produce. In ShadowDemoEpoch and ShadowDemoCorpusHealth, verify the
backend contract; unless the additional lifecycle states are intentionally
reserved and documented, narrow the unions to the values returned by mapEpoch
and mapFeed, then update any dependent UI switch statements to remove
unreachable branches.

In `@web-next/app/post/page.tsx`:
- Around line 326-335: The score component mapping in the post page assumes
every key exists in explanation.components, so incomplete API responses throw
during rendering. Add runtime validation at the API boundary in the relevant
client parser, or guard the response before mapping in the page’s data-loading
logic; route invalid or partial PostExplanationResponse payloads through the
existing ErrorCard state, and add a test covering a response with one omitted
component.
- Around line 277-280: Normalize input in explain() before routing: add or reuse
a shared parser that trims the value, accepts valid AT-URIs, converts supported
https://bsky.app/profile/.../post/... URLs to AT-URIs, and rejects unsupported
or invalid inputs. Update explain() to route only when parsing succeeds, and add
tests covering AT-URI input, Bluesky URL conversion, surrounding whitespace, and
invalid input.

In `@web-next/app/proposals/page.tsx`:
- Around line 47-48: Update the history filtering near current and past in the
proposals page to include only epochs explicitly marked closed (using closed_at
or the closed phase), rather than excluding only current?.id. Add a regression
test covering currentQuery failure while history includes an active epoch,
verifying the active epoch is absent from “Enacted proposals.”

In `@web-next/app/settings/page.tsx`:
- Around line 172-178: Update the sign-out Button’s onClick handler to handle
rejected logout promises, matching the existing pattern in app-shell.tsx: invoke
logout() and attach a catch handler that safely absorbs failures instead of
using void logout().

In `@web-next/app/vote/page.tsx`:
- Around line 257-258: Update the locked-ballot rendering in the vote page to
use a dedicated submittedWeights value derived from seedVote, rather than using
myVote as the submission check or falling back to communityWeights. Ensure both
displayed weights and locked-ballot copy are based on whether a weight vote
exists, and add a test covering a response with content/topic votes but no
weight vote.

In `@web-next/components/demo/agents-panel.tsx`:
- Around line 80-87: Move the hardcoded voter and bloc-description text from the
agents panel JSX into the centralized copy definitions in shadow-demo-copy.ts,
preferably under STEP_PANELS.agents or a dedicated key, then render those values
in agents-panel.tsx alongside the existing agents heading/body references. Also
route the “Illustrative fallback · frozen for this session” text in page.tsx
through the same centralized copy module so demo-copy.test.ts can audit all
disclosure-sensitive copy.

In `@web-next/components/demo/corpus-feed.tsx`:
- Around line 106-108: Update the hidden-row rendering in the corpus feed to use
copy mapped from each ShadowDemoHiddenFeedItem.hiddenReason value instead of
always displaying moderation-label text. Define a complete hidden-reason copy
map covering unauthenticated, adult-label, deleted/unavailable, and missing-text
cases, render the mapped text for each item, and add tests covering every
supported reason.

In `@web-next/components/demo/weight-bars.tsx`:
- Line 27: Update the bar animation in the weight-bars component to use the
exact percentage, `weights[key] * 100`, instead of
`formatPercent(weights[key])`; retain `formatPercent` for the displayed label
and add tests covering very small and near-100% weights.

In `@web-next/components/feed/replay-adapter.ts`:
- Around line 24-33: Update badgeMovementFor so an undefined previousRank
returns { dir: "new", delta: 0 } instead of "held"; preserve the existing held
behavior when both ranks are defined and equal, and retain the current up/down
delta logic for rank changes.

In `@web-next/components/hero-section.tsx`:
- Around line 144-148: Refactor the CTA in the hero section so it renders
exactly one interactive element instead of nesting Button inside Link. Use the
Button component’s asChild prop with Link as its child, preserving the existing
href, styling, variant, and label.

In `@web-next/components/ui/__tests__/policy-bar.test.ts`:
- Around line 5-15: Add table cases for Number.NEGATIVE_INFINITY, null as
unknown as number, and undefined as unknown as number in the
normalizePolicyWeight parameterized test, expecting each to normalize to 0. Add
a brief note documenting that empty-input, concurrency, and error-path tests are
not applicable to this scalar pure function.
🪄 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: Repository YAML (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: fc7ee296-e6a5-455a-9d7f-121b1178e91c

📥 Commits

Reviewing files that changed from the base of the PR and between c3ce3e8 and e1c89d1.

⛔ Files ignored due to path filters (20)
  • web-next/public/images/avatars/arjun-mehta.png is excluded by !**/*.png
  • web-next/public/images/avatars/avery-brooks.png is excluded by !**/*.png
  • web-next/public/images/avatars/ben-adler.png is excluded by !**/*.png
  • web-next/public/images/avatars/claire-rowan.png is excluded by !**/*.png
  • web-next/public/images/avatars/daniel-weiss.png is excluded by !**/*.png
  • web-next/public/images/avatars/eli-moreno.png is excluded by !**/*.png
  • web-next/public/images/avatars/jules-park.png is excluded by !**/*.png
  • web-next/public/images/avatars/karen-miller.png is excluded by !**/*.png
  • web-next/public/images/avatars/leila-hart.png is excluded by !**/*.png
  • web-next/public/images/avatars/mara-finch.png is excluded by !**/*.png
  • web-next/public/images/avatars/marco-haddad.png is excluded by !**/*.png
  • web-next/public/images/avatars/maya-keene.png is excluded by !**/*.png
  • web-next/public/images/avatars/nick-salazar.png is excluded by !**/*.png
  • web-next/public/images/avatars/nina-valdez.png is excluded by !**/*.png
  • web-next/public/images/avatars/rowan-bell.png is excluded by !**/*.png
  • web-next/public/images/avatars/sam-ortega.png is excluded by !**/*.png
  • web-next/public/images/avatars/tessa-green.png is excluded by !**/*.png
  • web-next/public/images/avatars/theo-kim.png is excluded by !**/*.png
  • web-next/public/images/bluesky-butterfly-logo.svg is excluded by !**/*.svg
  • web-next/public/images/feed/birders-field-notes.png is excluded by !**/*.png
📒 Files selected for processing (92)
  • CHANGELOG.md
  • src/scheduler/epoch-scheduler.ts
  • tests/web-next-demo-fixtures.test.ts
  • tests/web-next-homepage-anchors.test.ts
  • tests/web-next-live-demo-data.test.ts
  • tests/web-next-replay-model.test.ts
  • tests/web-next-score.test.ts
  • tests/web-next-shadow-demo-contract.test.ts
  • tests/web-next-shadow-demo-ui.test.ts
  • web-next/app/about/page.tsx
  • web-next/app/admin/page.tsx
  • web-next/app/dashboard/page.tsx
  • web-next/app/demo/__tests__/_flow.ts
  • web-next/app/demo/__tests__/demo-copy.test.ts
  • web-next/app/demo/__tests__/demo-request-coordinator.test.ts
  • web-next/app/demo/__tests__/http-client.test.ts
  • web-next/app/demo/__tests__/mock-client-phase-order.test.ts
  • web-next/app/demo/__tests__/mock-ranking.test.ts
  • web-next/app/demo/__tests__/mock-receipt.test.ts
  • web-next/app/demo/demo-client.ts
  • web-next/app/demo/demo-request-coordinator.ts
  • web-next/app/demo/http-shadow-demo-client.ts
  • web-next/app/demo/live-demo-data.ts
  • web-next/app/demo/mock-shadow-demo-client.ts
  • web-next/app/demo/page.tsx
  • web-next/app/demo/shadow-demo-contract.ts
  • web-next/app/demo/shadow-demo-contract.type-test.ts
  • web-next/app/demo/shadow-demo-copy.ts
  • web-next/app/demo/shadow-demo-fixtures.ts
  • web-next/app/docs/page.tsx
  • web-next/app/error.tsx
  • web-next/app/history/page.tsx
  • web-next/app/how-it-works/page.tsx
  • web-next/app/layout.tsx
  • web-next/app/not-found.tsx
  • web-next/app/page.tsx
  • web-next/app/post/page.tsx
  • web-next/app/privacy/page.tsx
  • web-next/app/proposals/page.tsx
  • web-next/app/research-consent/page.tsx
  • web-next/app/settings/page.tsx
  • web-next/app/sign-in/page.tsx
  • web-next/app/start/page.tsx
  • web-next/app/support/page.tsx
  • web-next/app/tos/page.tsx
  • web-next/app/vote/page.tsx
  • web-next/components/app-shell.tsx
  • web-next/components/bento-section.tsx
  • web-next/components/bluesky-sample-feed.tsx
  • web-next/components/brand-link.tsx
  • web-next/components/changelog-section.tsx
  • web-next/components/community-examples-section.tsx
  • web-next/components/cta-section.tsx
  • web-next/components/dashboard-preview.tsx
  • web-next/components/demo/agents-panel.tsx
  • web-next/components/demo/community-picker.tsx
  • web-next/components/demo/corpus-feed.tsx
  • web-next/components/demo/demo-disclosure.tsx
  • web-next/components/demo/flow-progress.tsx
  • web-next/components/demo/receipt-panel.tsx
  • web-next/components/demo/vote-panel.tsx
  • web-next/components/demo/weight-bars.tsx
  • web-next/components/faq-section.tsx
  • web-next/components/feed/bluesky-feed.tsx
  • web-next/components/feed/corgi-rank-badge.tsx
  • web-next/components/feed/replay-adapter.ts
  • web-next/components/footer-section.tsx
  • web-next/components/get-started-section.tsx
  • web-next/components/header.tsx
  • web-next/components/hero-section.tsx
  • web-next/components/how-it-works-replay.tsx
  • web-next/components/landing-ctas.tsx
  • web-next/components/legal-layout.tsx
  • web-next/components/modality-preview.tsx
  • web-next/components/replay-teaser.tsx
  • web-next/components/sign-in-dialog.tsx
  • web-next/components/social-proof.tsx
  • web-next/components/ui/__tests__/policy-bar.test.ts
  • web-next/components/ui/keyword-input.tsx
  • web-next/components/ui/layout.tsx
  • web-next/components/ui/linked-slider.tsx
  • web-next/components/ui/page-hero.tsx
  • web-next/components/ui/policy-bar.tsx
  • web-next/components/ui/score-breakdown.tsx
  • web-next/components/ui/topic-slider.tsx
  • web-next/components/ui/weight-bar.tsx
  • web-next/docs/design-system.md
  • web-next/lib/live-metrics-snapshot.ts
  • web-next/lib/replay-model.ts
  • web-next/lib/signals.ts
  • web-next/next.config.mjs
  • web-next/tailwind.config.ts

Comment thread tests/web-next-demo-fixtures.test.ts
Comment thread tests/web-next-live-demo-data.test.ts
Comment thread web-next/app/about/page.tsx Outdated
Comment thread web-next/app/dashboard/page.tsx
Comment thread web-next/app/demo/__tests__/demo-copy.test.ts
Comment thread web-next/components/demo/corpus-feed.tsx Outdated
Comment thread web-next/components/demo/weight-bars.tsx Outdated
Comment thread web-next/components/feed/replay-adapter.ts
Comment thread web-next/components/hero-section.tsx Outdated
Comment thread web-next/components/ui/__tests__/policy-bar.test.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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 `@tests/web-next-post-uri.test.ts`:
- Around line 17-41: In the tests for parseBlueskyUrlOrAtUri, add boundary cases
covering the hard-coded length limits: verify a valid at:// post URI with a
512-character rkey is accepted and one with 513 characters is rejected, and
verify valid DIDs of 2048 characters are accepted while 2049-character DIDs are
rejected. Keep these cases in focused tests alongside the existing
unsupported-reference cases.

In `@tests/web-next-receipt-display-math.test.ts`:
- Around line 27-31: The tests for buildReceiptDisplayMath need broader boundary
coverage: add Number.NEGATIVE_INFINITY to the non-finite values passed to the
existing rejection test, and add a test for buildReceiptDisplayMath([])
asserting it does not throw and returns a totalScore of 0.

In `@web-next/lib/receipt-display-math.ts`:
- Around line 5-10: Prevent malformed receipt values from crashing rendering:
update the ReceiptPanel/buildReceiptDisplayMath path to gracefully fall back
when roundForDisplay receives NaN, Infinity, or -Infinity, or add an error
boundary around the panel. Preserve normal finite-value behavior and add
regression tests covering all three non-finite inputs.
🪄 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: Repository YAML (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 31c26b2c-59ef-40a1-a151-3ad1b30ec105

📥 Commits

Reviewing files that changed from the base of the PR and between e1c89d1 and be89191.

📒 Files selected for processing (13)
  • tests/web-next-post-uri.test.ts
  • tests/web-next-receipt-display-math.test.ts
  • web-next/app/about/page.tsx
  • web-next/app/demo/__tests__/mock-client-phase-order.test.ts
  • web-next/app/demo/mock-shadow-demo-client.ts
  • web-next/app/post/page.tsx
  • web-next/app/settings/page.tsx
  • web-next/app/start/page.tsx
  • web-next/app/vote/page.tsx
  • web-next/components/demo/receipt-panel.tsx
  • web-next/components/ui/linked-slider.tsx
  • web-next/lib/post-uri.ts
  • web-next/lib/receipt-display-math.ts

Comment thread tests/web-next-post-uri.test.ts
Comment thread tests/web-next-receipt-display-math.test.ts Outdated
Comment thread web-next/lib/receipt-display-math.ts
@AndrewNordstrom
AndrewNordstrom force-pushed the dev/PROJ-1285-corgi-v2-homepage-polish branch from 59a5c66 to f2afa7c Compare July 11, 2026 06:38
@AndrewNordstrom
AndrewNordstrom force-pushed the dev/PROJ-1285-corgi-v2-homepage-polish branch from f2afa7c to 70ecaa7 Compare July 11, 2026 06:40
@AndrewNordstrom

Copy link
Copy Markdown
Collaborator Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 11, 2026 •

Copy link
Copy Markdown
✅ Action performed

Full review finished.


You're currently rate limited under our Fair Usage Limits Policy. Your recent PR review activity is in the 95th percentile or higher among CodeRabbit users, so adaptive limits apply. Your next review will be available in 49 minutes.

@AndrewNordstrom

Copy link
Copy Markdown
Collaborator Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 11, 2026 •

Copy link
Copy Markdown
✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot removed the documentation Improvements or additions to documentation label Jul 11, 2026
coderabbitai[bot]
coderabbitai Bot previously requested changes Jul 11, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 22

♻️ Duplicate comments (3)
web-next/app/demo/mock-shadow-demo-client.ts (1)

537-573: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

getReceipt doesn't validate request.epochId — receipts can be mislabeled with the wrong epoch's math.

getFeed (line 516-522) correctly rejects an epochId that matches neither state.openEpoch.id nor state.publishedEpoch.id, and selects weights based on that validated result. getReceipt never got the same treatment: weights = appliedWeights(state) (line 547) always reflects whichever epoch is currently applied to the session, but the receipt is stamped with whatever request.epochId the caller passed (line 569, and again at line 602 in provenance.shadowEpochId). Request the pre-advance epoch's receipt right after advanceEpoch() and you get post-advance math labeled as the pre-advance epoch — silently wrong, not rejected. This directly undermines the "frozen-corpus-only receipts, honest math" guarantee this demo exists to model (PROJ-1431).

🔧 Proposed fix — mirror the getFeed validation
     async getReceipt(sessionId: string, request: ShadowDemoReceiptRequest, signal: AbortSignal) {
       throwIfAborted(signal)
       const state = requireSession(sessionId)
+      const publishedEpoch = state.publishedEpoch
+      const isPublished = publishedEpoch?.id === request.epochId
+      const isOpen = state.openEpoch.id === request.epochId
+      if (!isPublished && !isOpen) {
+        throw new ShadowDemoClientError("That epoch is not part of this demo session.", "stale_epoch")
+      }
       const entry = state.corpus.find((candidate) => candidate.post.uri === request.postUri)
       if (entry === undefined) {
         throw new ShadowDemoClientError("That post is not part of this demo session's frozen corpus.", "unknown_post")
       }
       if (entry.hidden) {
         throw new ShadowDemoClientError("This row is withheld from the public view and has no public receipt.", "unknown_post")
       }
-      const weights = appliedWeights(state)
+      const weights = isPublished && publishedEpoch !== null ? publishedEpoch.weights : state.openEpoch.weights
       const order = rankedIds(state.corpus, weights)
       const visibleRank = order.indexOf(entry.id) + 1
-      const previousRank = state.publishedEpoch ? (state.baselineRankById[entry.id] ?? null) : null
-      const { items } = rankCorpus(state.corpus, weights, state.publishedEpoch ? state.baselineRankById : null)
+      const previousRank = isPublished ? (state.baselineRankById[entry.id] ?? null) : null
+      const { items } = rankCorpus(state.corpus, weights, isPublished ? state.baselineRankById : null)
🤖 Prompt for AI Agents
In web-next/app/demo/mock-shadow-demo-client.ts, update getReceipt (around
lines 537-556) to validate request.epochId against state.openEpoch.id and
state.publishedEpoch.id before computing weights, rejecting with a
"stale_epoch" ShadowDemoClientError for any other value — mirroring the
validation already present in getFeed. Select weights/previousRank/baseline
only from the validated epoch, not from appliedWeights(state) which reflects
whatever epoch is currently applied to the session regardless of the
request. Add tests in mock-receipt.test.ts for a stale (pre-advance) epochId
and an unknown epochId, asserting the request is rejected rather than
returning mislabeled math.
🤖 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 `@web-next/app/demo/mock-shadow-demo-client.ts` around lines 537 - 573, Update
getReceipt to validate request.epochId against state.openEpoch.id and
state.publishedEpoch.id before calculating receipt data, mirroring getFeed;
reject unmatched epochs with ShadowDemoClientError using the "stale_epoch" code.
Derive weights, previousRank, and baseline values from the validated epoch
rather than unconditional appliedWeights(state), and ensure the receipt’s
epochId and provenance.shadowEpochId reflect that same validated epoch. Add
coverage in mock-receipt.test.ts for stale pre-advance and unknown epoch IDs
being rejected.
web-next/app/post/page.tsx (1)

332-343: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Still unguarded against incomplete explanation.components — render crash risk.

Switching to SIGNAL_KEYS.map(...) narrows which keys are read, but explanation.components[key] is still accessed with no existence check. If the backend omits any one of the five signal keys (partial payload, backend regression, or a stale/older API response), c is undefined and c.raw_score throws — crashing the render tree instead of routing through the existing pageState === "error" / ErrorCard path.

Add a Zod schema/runtime guard at the transparencyApi.getPostExplanation boundary (preferred, since web-next/lib/api/client.ts only types the response, per the prior review), or defensively skip/guard here.

AI agent prompt: add a runtime guard so a PostExplanationResponse missing one components entry either fails the query (routing to pageState === "error") or renders with a 0-filled fallback, and add a regression test for a response with one omitted signal component.

🛡️ Minimal guard at the render boundary
   const scoreComponents: ScoreComponent[] = explanation
-    ? SIGNAL_KEYS.map((key) => {
+    ? SIGNAL_KEYS.flatMap((key) => {
         const c = explanation.components[key]
+        if (!c) return []
-        return {
+        return [{
           key,
           label: SIGNAL_META[key]?.label ?? key,
           raw_score: c.raw_score,
           weight: c.weight,
           weighted: c.weighted,
-        }
+        }]
       })
     : []
🤖 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 `@web-next/app/post/page.tsx` around lines 332 - 343, Guard the explanation
rendering flow around SIGNAL_KEYS.map so missing explanation.components entries
cannot dereference undefined. Prefer adding a Zod runtime validation at the
transparencyApi.getPostExplanation boundary that rejects incomplete
PostExplanationResponse payloads and routes through the existing pageState ===
"error" / ErrorCard path; otherwise skip missing components or provide the
established zero-valued fallback. Add a regression test covering a response with
one omitted signal component.
web-next/app/dashboard/page.tsx (1)

237-278: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Feed health still silently disappears on statsQuery error.

statsQuery.isError isn't branched on — when the request fails, stats is undefined, so the whole ternary falls to null and the "Feed health" section vanishes with no ErrorCard, no retry, no indication anything went wrong. The dashboard can look calm and complete while a real data-loading failure occurred.

AI agent prompt: add an explicit statsQuery.isError branch (before the success condition) rendering ErrorCard with onRetry={() => void statsQuery.refetch()}; add tests for loading, success, and error+retry-recovery states of the Feed health section.

🚑 Surface the error instead of hiding the section
         {statsQuery.isLoading ? (
           <div className="rounded-2xl border border-border bg-card px-5 py-4">
             <Skeleton className="h-16 w-full" />
           </div>
+        ) : statsQuery.isError ? (
+          <ErrorCard
+            heading="Feed health unavailable"
+            body="We couldn't load feed-health metrics. Try again shortly."
+            onRetry={() => void statsQuery.refetch()}
+          />
         ) : stats && (participation != null || feed || metrics) ? (

As per path instructions, asynchronous failures must be surfaced and handled explicitly rather than swallowed.

🤖 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 `@web-next/app/dashboard/page.tsx` around lines 237 - 278, Update the Feed
health rendering around statsQuery.isLoading and the existing stats success
condition to add an explicit statsQuery.isError branch before success, rendering
ErrorCard with onRetry={() => void statsQuery.refetch()}. Preserve the current
loading and successful Feed health content, and add tests covering loading,
success, and error followed by retry recovery.

Source: Path instructions

🤖 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 `@tests/web-next-shadow-demo-ui.test.ts`:
- Around line 1-7: Check the existing Vitest/CI entrypoints for an import of
demo __tests__/topic-policy.test.ts; if none loads it, add that test import to
the shadow demo suite alongside the other demo test imports. Also add a focused
topic-policy test covering empty or invalid policy state, using the existing
topic-policy test symbols and conventions.

In `@web-next/app/demo/__tests__/demo-copy.test.ts`:
- Around line 20-30: Update the reversed-claim handling around the reversed
regular expression and claim matching so negation is checked in every captured
context where it can occur, including match[1] as well as match[2]. Add
regression cases covering negation before and after reversed claims, including
“Rank badges are not native Bluesky UI.”, while preserving detection of
non-negated claims.

In `@web-next/app/demo/__tests__/http-client.test.ts`:
- Around line 1-282: Add a test in the HTTP shadow demo client suite covering
direct promise rejection from fetch, distinct from malformed Response and
timeout cases. Stub fetch to reject with an underlying network-style error,
invoke a public client method such as createSession, and assert the resulting
error matches the client’s established sanitized/public error behavior without
leaking the original failure details.

In `@web-next/app/demo/__tests__/topic-policy.test.ts`:
- Around line 5-23: Add a missing-key test for openScienceTopicEntries where
topicWeights omits one required SHADOW_DEMO_TOPIC_KEYS entry, and assert the
actual returned entry value or documented fallback. Keep the existing
complete-input test unchanged and make the assertion capture the contract that
prevents undefined weights from reaching TopicPolicy.

In `@web-next/app/demo/demo-request-coordinator.ts`:
- Around line 6-27: Add unit tests for DemoRequestCoordinator covering
successive start() calls, confirming the prior signal is aborted and its context
isCurrent() becomes false while the newest context remains current; also test
cancel() aborts and invalidates the active context, and a subsequent start()
produces a non-aborted signal with isCurrent() true.

In `@web-next/app/demo/page.tsx`:
- Around line 158-175: Make start-session creation explicitly idempotent:
preserve and transmit the clientNonce from handleStart through the HTTP client
to /api/demo/sessions, then have the server deduplicate or recover the existing
shadow session by that nonce. Add a lost-response retry test verifying that
retrying the same start request does not create a second session.
- Around line 134-146: Update recoverSession to clear or otherwise override
startingNextEpoch using the recovered payload.session.phase before applying the
recovered state, so reviewer_vote_cast and agent_votes_cast render AgentsPanel
rather than remaining at corpus_ready with VotePanel. Preserve the existing
server snapshot updates and phase behavior for other session states.

In `@web-next/app/demo/shadow-demo-api-schemas.ts`:
- Around line 85-89: Update syntheticVoteSchema with a refinement that extracts
the bloc segment from actorId and requires it to equal blocId, rejecting
mismatched synthetic actor/bloc pairs while preserving valid matching pairs. Add
tests covering both matching and mismatched inputs, including TypeScript
type-checking and Zod validation of external input.
- Around line 213-218: Update the receipt schema around the components array to
enforce unique signal values, not just exactly five entries, so duplicate
signals cannot replace missing signals. Add Zod parse tests covering both
duplicate signals and a missing signal, and ensure the TypeScript validation
tests verify external input is rejected.

In `@web-next/app/demo/shadow-demo-fixtures.ts`:
- Around line 448-463: Remove the unused equalVoterAverage export and its
implementation from the demo fixtures module, preserving trimmedVoterAverage as
the sole aggregation strategy. Add or update a test to exercise
trimmedVoterAverage’s intended aggregation behavior and prevent drift.

In `@web-next/app/error.tsx`:
- Around line 9-19: Update the Error component’s rendered error UI to display
error.digest as a user-visible reference when available, using a clear label
such as “Reference” so users can report it and support can correlate logs.
Preserve the existing behavior when no digest is present.

In `@web-next/app/sign-in/page.tsx`:
- Around line 68-74: Add the project-standard focus-visible ring utility classes
to the `/start` Link in the sign-in page, matching the focus styling used by the
nearby “Create an app password” anchor while preserving its existing visual
classes and behavior.

In `@web-next/components/app-shell.tsx`:
- Around line 126-139: The logout behavior is duplicated and inconsistent
between handleLogout and the settings logout implementation. Extract shared
logout state and behavior into a useLogout hook alongside useAuth, including the
pending guard, error handling, and successful logout flow, then update both call
sites to consume the hook and remove their local implementations.
- Around line 110-124: Update the mobile menu flow in AppShell to make the
background main content non-interactive while mobileMenuOpen is true by applying
inert to the main element, or implement a complete Tab focus trap within the
drawer. Preserve Escape closing behavior and focus restoration through
closeOnEscape and mobileMenuButtonRef, and add an interaction test covering
drawer tabbing, Escape, and restored button focus.

In `@web-next/components/bento-section.tsx`:
- Around line 9-45: Update SignalBlendUI to import and use SIGNAL_COLORS from
`@/lib/signals` instead of hardcoded Tailwind hex color classes in examples. Store
each signal’s centralized color and any required dimming separately, then pass
the color to WeightBar through its color prop/style while preserving the
existing widths and visual opacity variants.

In `@web-next/components/bluesky-sample-feed.tsx`:
- Around line 158-170: Update the metric spans in the post feed around the
replies, reposts, and likes icons to include accessible labels (“Replies”,
“Reposts”, and “Likes”) while preserving the existing visual layout and hidden
icons. Add a regression test asserting all three labels are exposed in the
rendered accessible text.

In `@web-next/components/cta-section.tsx`:
- Around line 20-23: Update the CTA paragraph in the component’s hero text to
call the experience an “interactive no-login demo” instead of “read-only,” and
explicitly mention voting, advancing a shadow epoch, and inspecting reranking
before connecting a Bluesky app password.

In `@web-next/components/demo/receipt-panel.tsx`:
- Around line 203-205: Replace the hardcoded “25-ballot aggregate” text in the
receipt panel with the aggregate ballot count exposed by the receipt contract or
provenance. Trace the value from DEMO_AGENTS voterCount totals through receipt
creation, ensure it is available as a typed receipt field, and render that field
in the explanatory copy.
- Around line 195-201: Update the timestamp rendering in the receipt panel for
sampledAt and scoredAt to use a deterministic locale and time zone rather than
toLocaleString() defaults, preserving the existing displayed date/time
semantics. Add a regression test that snapshots the receipt under different TZ
and locale settings and verifies identical output.

In `@web-next/components/faq-section.tsx`:
- Around line 11-12: Update the FAQ description in the visible FAQ entry to
replace the claim that the current demo is read-only with an accurate statement
that it is interactive and supports voting, shadow-epoch advancement, and
reranking without requiring sign-in.

In `@web-next/lib/post-uri.ts`:
- Around line 13-71: Add unit tests for parseBlueskyUrlOrAtUri covering valid
AT-URIs and DID-based bsky.app URLs with canonicalization, plus rejection of
handles, empty and non-string values, query or hash components, malformed
percent-encoding, encoded slashes, "." and ".." rkeys, invalid identifiers, and
maximum-length inputs. Ensure each relevant validation branch and error behavior
is exercised.

In `@web-next/lib/receipt-display-math.ts`:
- Around line 44-75: Introduce a dedicated receipt-math error type for expected
validation failures, and update buildReceiptDisplayMath plus the
rounding-residual validation in tryBuildReceiptDisplayMathWithServerTotal to
throw it instead of generic TypeError. Change both tryBuildReceiptDisplayMath
and tryBuildReceiptDisplayMathWithServerTotal to return null only for that
dedicated error, allowing unrelated TypeError instances to propagate.

---

Duplicate comments:
In `@web-next/app/dashboard/page.tsx`:
- Around line 237-278: Update the Feed health rendering around
statsQuery.isLoading and the existing stats success condition to add an explicit
statsQuery.isError branch before success, rendering ErrorCard with onRetry={()
=> void statsQuery.refetch()}. Preserve the current loading and successful Feed
health content, and add tests covering loading, success, and error followed by
retry recovery.

In `@web-next/app/demo/mock-shadow-demo-client.ts`:
- Around line 537-573: Update getReceipt to validate request.epochId against
state.openEpoch.id and state.publishedEpoch.id before calculating receipt data,
mirroring getFeed; reject unmatched epochs with ShadowDemoClientError using the
"stale_epoch" code. Derive weights, previousRank, and baseline values from the
validated epoch rather than unconditional appliedWeights(state), and ensure the
receipt’s epochId and provenance.shadowEpochId reflect that same validated
epoch. Add coverage in mock-receipt.test.ts for stale pre-advance and unknown
epoch IDs being rejected.

In `@web-next/app/post/page.tsx`:
- Around line 332-343: Guard the explanation rendering flow around
SIGNAL_KEYS.map so missing explanation.components entries cannot dereference
undefined. Prefer adding a Zod runtime validation at the
transparencyApi.getPostExplanation boundary that rejects incomplete
PostExplanationResponse payloads and routes through the existing pageState ===
"error" / ErrorCard path; otherwise skip missing components or provide the
established zero-valued fallback. Add a regression test covering a response with
one omitted signal component.
🪄 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: Repository YAML (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: b10223d5-75b9-410c-8149-41b96afb1853

📥 Commits

Reviewing files that changed from the base of the PR and between d238388 and ec95d30.

⛔ Files ignored due to path filters (20)
  • web-next/public/images/avatars/arjun-mehta.png is excluded by !**/*.png
  • web-next/public/images/avatars/avery-brooks.png is excluded by !**/*.png
  • web-next/public/images/avatars/ben-adler.png is excluded by !**/*.png
  • web-next/public/images/avatars/claire-rowan.png is excluded by !**/*.png
  • web-next/public/images/avatars/daniel-weiss.png is excluded by !**/*.png
  • web-next/public/images/avatars/eli-moreno.png is excluded by !**/*.png
  • web-next/public/images/avatars/jules-park.png is excluded by !**/*.png
  • web-next/public/images/avatars/karen-miller.png is excluded by !**/*.png
  • web-next/public/images/avatars/leila-hart.png is excluded by !**/*.png
  • web-next/public/images/avatars/mara-finch.png is excluded by !**/*.png
  • web-next/public/images/avatars/marco-haddad.png is excluded by !**/*.png
  • web-next/public/images/avatars/maya-keene.png is excluded by !**/*.png
  • web-next/public/images/avatars/nick-salazar.png is excluded by !**/*.png
  • web-next/public/images/avatars/nina-valdez.png is excluded by !**/*.png
  • web-next/public/images/avatars/rowan-bell.png is excluded by !**/*.png
  • web-next/public/images/avatars/sam-ortega.png is excluded by !**/*.png
  • web-next/public/images/avatars/tessa-green.png is excluded by !**/*.png
  • web-next/public/images/avatars/theo-kim.png is excluded by !**/*.png
  • web-next/public/images/bluesky-butterfly-logo.svg is excluded by !**/*.svg
  • web-next/public/images/feed/birders-field-notes.png is excluded by !**/*.png
📒 Files selected for processing (96)
  • CHANGELOG.md
  • tests/web-next-demo-fixtures.test.ts
  • tests/web-next-homepage-anchors.test.ts
  • tests/web-next-live-demo-data.test.ts
  • tests/web-next-post-uri.test.ts
  • tests/web-next-receipt-display-math.test.ts
  • tests/web-next-replay-model.test.ts
  • tests/web-next-score.test.ts
  • tests/web-next-shadow-demo-ui.test.ts
  • web-next/app/about/page.tsx
  • web-next/app/admin/page.tsx
  • web-next/app/dashboard/page.tsx
  • web-next/app/demo/__tests__/_flow.ts
  • web-next/app/demo/__tests__/demo-copy.test.ts
  • web-next/app/demo/__tests__/demo-request-coordinator.test.ts
  • web-next/app/demo/__tests__/http-client.test.ts
  • web-next/app/demo/__tests__/mock-client-phase-order.test.ts
  • web-next/app/demo/__tests__/mock-ranking.test.ts
  • web-next/app/demo/__tests__/mock-receipt.test.ts
  • web-next/app/demo/__tests__/topic-policy.test.ts
  • web-next/app/demo/demo-client.ts
  • web-next/app/demo/demo-request-coordinator.ts
  • web-next/app/demo/http-shadow-demo-client.ts
  • web-next/app/demo/live-demo-data.ts
  • web-next/app/demo/mock-shadow-demo-client.ts
  • web-next/app/demo/page.tsx
  • web-next/app/demo/shadow-demo-api-schemas.ts
  • web-next/app/demo/shadow-demo-copy.ts
  • web-next/app/demo/shadow-demo-fixtures.ts
  • web-next/app/demo/shadow-demo-view-model.ts
  • web-next/app/docs/page.tsx
  • web-next/app/error.tsx
  • web-next/app/history/page.tsx
  • web-next/app/how-it-works/page.tsx
  • web-next/app/layout.tsx
  • web-next/app/not-found.tsx
  • web-next/app/page.tsx
  • web-next/app/post/page.tsx
  • web-next/app/privacy/page.tsx
  • web-next/app/proposals/page.tsx
  • web-next/app/research-consent/page.tsx
  • web-next/app/settings/page.tsx
  • web-next/app/sign-in/page.tsx
  • web-next/app/start/page.tsx
  • web-next/app/support/page.tsx
  • web-next/app/tos/page.tsx
  • web-next/app/vote/page.tsx
  • web-next/components/app-shell.tsx
  • web-next/components/bento-section.tsx
  • web-next/components/bluesky-sample-feed.tsx
  • web-next/components/brand-link.tsx
  • web-next/components/changelog-section.tsx
  • web-next/components/community-examples-section.tsx
  • web-next/components/cta-section.tsx
  • web-next/components/dashboard-preview.tsx
  • web-next/components/demo/agents-panel.tsx
  • web-next/components/demo/community-picker.tsx
  • web-next/components/demo/corpus-feed.tsx
  • web-next/components/demo/demo-disclosure.tsx
  • web-next/components/demo/flow-progress.tsx
  • web-next/components/demo/receipt-panel.tsx
  • web-next/components/demo/topic-policy.tsx
  • web-next/components/demo/vote-panel.tsx
  • web-next/components/demo/weight-bars.tsx
  • web-next/components/faq-section.tsx
  • web-next/components/feed/bluesky-feed.tsx
  • web-next/components/feed/corgi-rank-badge.tsx
  • web-next/components/feed/replay-adapter.ts
  • web-next/components/footer-section.tsx
  • web-next/components/get-started-section.tsx
  • web-next/components/header.tsx
  • web-next/components/hero-section.tsx
  • web-next/components/how-it-works-replay.tsx
  • web-next/components/landing-ctas.tsx
  • web-next/components/legal-layout.tsx
  • web-next/components/modality-preview.tsx
  • web-next/components/replay-teaser.tsx
  • web-next/components/sign-in-dialog.tsx
  • web-next/components/social-proof.tsx
  • web-next/components/ui/__tests__/policy-bar.test.ts
  • web-next/components/ui/keyword-input.tsx
  • web-next/components/ui/layout.tsx
  • web-next/components/ui/linked-slider.tsx
  • web-next/components/ui/page-hero.tsx
  • web-next/components/ui/policy-bar.tsx
  • web-next/components/ui/score-breakdown.tsx
  • web-next/components/ui/topic-slider.tsx
  • web-next/components/ui/weight-bar.tsx
  • web-next/docs/design-system.md
  • web-next/lib/live-metrics-snapshot.ts
  • web-next/lib/post-uri.ts
  • web-next/lib/receipt-display-math.ts
  • web-next/lib/replay-model.ts
  • web-next/lib/signals.ts
  • web-next/next.config.mjs
  • web-next/tailwind.config.ts

Comment thread tests/web-next-shadow-demo-ui.test.ts
Comment thread web-next/app/demo/__tests__/demo-copy.test.ts
Comment thread web-next/app/demo/__tests__/http-client.test.ts
Comment thread web-next/app/demo/__tests__/topic-policy.test.ts
Comment thread web-next/app/demo/demo-request-coordinator.ts
Comment thread web-next/components/demo/receipt-panel.tsx
Comment thread web-next/components/demo/receipt-panel.tsx
Comment thread web-next/components/faq-section.tsx Outdated
Comment thread web-next/lib/post-uri.ts
Comment thread web-next/lib/receipt-display-math.ts
@AndrewNordstrom

Copy link
Copy Markdown
Collaborator Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 11, 2026 •

Copy link
Copy Markdown
✅ Action performed

Full review finished.


You're currently rate limited under our Fair Usage Limits Policy. Your recent PR review activity is in the 95th percentile or higher among CodeRabbit users, so adaptive limits apply. Your next review will be available in 23 minutes.

@AndrewNordstrom

Copy link
Copy Markdown
Collaborator Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 11, 2026 •

Copy link
Copy Markdown
✅ Action performed

Full review finished.


You're currently rate limited under our Fair Usage Limits Policy. Your recent PR review activity is in the 95th percentile or higher among CodeRabbit users, so adaptive limits apply. Your next review will be available in 57 minutes.

@AndrewNordstrom

Copy link
Copy Markdown
Collaborator Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 11, 2026 •

Copy link
Copy Markdown
✅ Action performed

Full review finished.


You're currently rate limited under our Fair Usage Limits Policy. Your recent PR review activity is in the 95th percentile or higher among CodeRabbit users, so adaptive limits apply. Your next review will be available in 59 minutes.

@AndrewNordstrom AndrewNordstrom added the coderabbit:exempt Temporary CodeRabbit exemption with audit note label Jul 11, 2026
@AndrewNordstrom
AndrewNordstrom dismissed coderabbitai[bot]’s stale review July 11, 2026 12:24

Dismissed under the audited CodeRabbit timeout exemption: review targets stale head ec95d30; exact head 92c1308 has green CI, zero live CodeRabbit threads, and all validated local Major/Minor findings resolved. See PR CodeRabbit Audit and PROJ-1285 mini receipt.

@AndrewNordstrom
AndrewNordstrom enabled auto-merge (squash) July 11, 2026 12:29
@AndrewNordstrom
AndrewNordstrom merged commit 43c2370 into main Jul 11, 2026
21 of 26 checks passed
@AndrewNordstrom
AndrewNordstrom deleted the dev/PROJ-1285-corgi-v2-homepage-polish branch July 11, 2026 12:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

javascript Pull requests that update javascript code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant