Repository navigation
[PROJ-1285] Ship Corgi product UI and guided reviewer demo - #331
Conversation
|
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. |
WalkthroughThe 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. ChangesWeb-next productization
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related issues
Possibly related PRs
Suggested labels: 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
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
✨ Simplify code
Warning Review ran into problems🔥 ProblemsThese MCP integrations need to be re-authenticated in the Integrations settings: Notion Comment |
There was a problem hiding this comment.
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 winPreserve the signal-key invariant instead of casting arbitrary strings.
ScoreComponent.keyis typed asstring, but the cast only hides invalid values. For an unknown key, the label falls back toc.labelwhileSIGNAL_COLORS[...]becomesundefinedand is passed toWeightBar, producing an inconsistent or unstyled bar. Either narrow the prop toSignalKeyand 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
ScoreBreakdowncallers andWeightBar’scolorcontract, 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 winHardcoded signal-key list duplicates
SIGNAL_KEYS.
web-next/lib/signals.tsalready exportsSIGNAL_KEYS, typed against the sameEpochResponse["weights"]shape (used directly inweb-next/app/proposals/page.tsx). Re-declaring the list here means it silently goes stale if a signal is ever added/renamed, and forces theas keyof typeof SIGNAL_LABELScast 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_KEYSAlso 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 winLocked ballot mislabels community weights as "your submitted vote".
myWeights(Line 258) comes fromsignals, which is seeded frommyVote?.voteonly when the user actually has a vote (Lines 217-232); otherwise it falls back toepoch.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 rendersmyWeightsunder 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
⛔ Files ignored due to path filters (20)
web-next/public/images/avatars/arjun-mehta.pngis excluded by!**/*.pngweb-next/public/images/avatars/avery-brooks.pngis excluded by!**/*.pngweb-next/public/images/avatars/ben-adler.pngis excluded by!**/*.pngweb-next/public/images/avatars/claire-rowan.pngis excluded by!**/*.pngweb-next/public/images/avatars/daniel-weiss.pngis excluded by!**/*.pngweb-next/public/images/avatars/eli-moreno.pngis excluded by!**/*.pngweb-next/public/images/avatars/jules-park.pngis excluded by!**/*.pngweb-next/public/images/avatars/karen-miller.pngis excluded by!**/*.pngweb-next/public/images/avatars/leila-hart.pngis excluded by!**/*.pngweb-next/public/images/avatars/mara-finch.pngis excluded by!**/*.pngweb-next/public/images/avatars/marco-haddad.pngis excluded by!**/*.pngweb-next/public/images/avatars/maya-keene.pngis excluded by!**/*.pngweb-next/public/images/avatars/nick-salazar.pngis excluded by!**/*.pngweb-next/public/images/avatars/nina-valdez.pngis excluded by!**/*.pngweb-next/public/images/avatars/rowan-bell.pngis excluded by!**/*.pngweb-next/public/images/avatars/sam-ortega.pngis excluded by!**/*.pngweb-next/public/images/avatars/tessa-green.pngis excluded by!**/*.pngweb-next/public/images/avatars/theo-kim.pngis excluded by!**/*.pngweb-next/public/images/bluesky-butterfly-logo.svgis excluded by!**/*.svgweb-next/public/images/feed/birders-field-notes.pngis excluded by!**/*.png
📒 Files selected for processing (88)
CHANGELOG.mdtests/web-next-demo-fixtures.test.tstests/web-next-homepage-anchors.test.tstests/web-next-live-demo-data.test.tstests/web-next-replay-model.test.tstests/web-next-score.test.tstests/web-next-shadow-demo-contract.test.tsweb-next/app/about/page.tsxweb-next/app/admin/page.tsxweb-next/app/dashboard/page.tsxweb-next/app/demo/__tests__/_flow.tsweb-next/app/demo/__tests__/demo-copy.test.tsweb-next/app/demo/__tests__/demo-request-coordinator.test.tsweb-next/app/demo/__tests__/http-client.test.tsweb-next/app/demo/__tests__/mock-client-phase-order.test.tsweb-next/app/demo/__tests__/mock-ranking.test.tsweb-next/app/demo/__tests__/mock-receipt.test.tsweb-next/app/demo/demo-client.tsweb-next/app/demo/demo-request-coordinator.tsweb-next/app/demo/http-shadow-demo-client.tsweb-next/app/demo/live-demo-data.tsweb-next/app/demo/mock-shadow-demo-client.tsweb-next/app/demo/page.tsxweb-next/app/demo/shadow-demo-contract.tsweb-next/app/demo/shadow-demo-copy.tsweb-next/app/demo/shadow-demo-fixtures.tsweb-next/app/docs/page.tsxweb-next/app/error.tsxweb-next/app/history/page.tsxweb-next/app/how-it-works/page.tsxweb-next/app/layout.tsxweb-next/app/not-found.tsxweb-next/app/page.tsxweb-next/app/post/page.tsxweb-next/app/privacy/page.tsxweb-next/app/proposals/page.tsxweb-next/app/research-consent/page.tsxweb-next/app/settings/page.tsxweb-next/app/sign-in/page.tsxweb-next/app/start/page.tsxweb-next/app/support/page.tsxweb-next/app/tos/page.tsxweb-next/app/vote/page.tsxweb-next/components/app-shell.tsxweb-next/components/bento-section.tsxweb-next/components/bluesky-sample-feed.tsxweb-next/components/brand-link.tsxweb-next/components/changelog-section.tsxweb-next/components/community-examples-section.tsxweb-next/components/cta-section.tsxweb-next/components/dashboard-preview.tsxweb-next/components/demo/agents-panel.tsxweb-next/components/demo/community-picker.tsxweb-next/components/demo/corpus-feed.tsxweb-next/components/demo/demo-disclosure.tsxweb-next/components/demo/flow-progress.tsxweb-next/components/demo/receipt-panel.tsxweb-next/components/demo/vote-panel.tsxweb-next/components/demo/weight-bars.tsxweb-next/components/faq-section.tsxweb-next/components/feed/bluesky-feed.tsxweb-next/components/feed/corgi-rank-badge.tsxweb-next/components/feed/replay-adapter.tsweb-next/components/footer-section.tsxweb-next/components/get-started-section.tsxweb-next/components/header.tsxweb-next/components/hero-section.tsxweb-next/components/how-it-works-replay.tsxweb-next/components/landing-ctas.tsxweb-next/components/legal-layout.tsxweb-next/components/modality-preview.tsxweb-next/components/replay-teaser.tsxweb-next/components/sign-in-dialog.tsxweb-next/components/social-proof.tsxweb-next/components/ui/keyword-input.tsxweb-next/components/ui/layout.tsxweb-next/components/ui/linked-slider.tsxweb-next/components/ui/page-hero.tsxweb-next/components/ui/policy-bar.tsxweb-next/components/ui/score-breakdown.tsxweb-next/components/ui/topic-slider.tsxweb-next/components/ui/weight-bar.tsxweb-next/docs/design-system.mdweb-next/lib/live-metrics-snapshot.tsweb-next/lib/replay-model.tsweb-next/lib/signals.tsweb-next/next.config.mjsweb-next/tailwind.config.ts
|
@coderabbitai full review |
✅ Action performedFull 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. |
There was a problem hiding this comment.
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 winHardcoded signal-key list duplicates the canonical
SIGNAL_KEYS/SIGNAL_LABELSsource.
SIGNAL_LABELSis already imported from@/lib/signals(Line 12), but the keys used to derive weight deltas are re-typed locally instead of importingSIGNAL_KEYS. If the canonical signal set ever changes (new/removed/renamed signal), this local list silently drifts fromEpochResponse["weights"], causingweight_changesto 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 winQuery failures are silently masked as "no data yet" states.
Neither
statsQuerynorepochsQuerybranches onisError. On a real backend failure: the "Feed health" strip just vanishes (Line 242 falls through tonull), 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 liftExpose a usable refresh-corpus contract.
refreshCorpusis advertised onShadowDemoClient, but no corresponding endpoint exists inSHADOW_DEMO_ENDPOINTSorSHADOW_DEMO_ENDPOINT_TEMPLATES. It also acceptsCreateShadowDemoSessionRequest, 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
refreshCorpusendpoint 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 removerefreshCorpusandcanRefreshCorpusfrom 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 winCancel in-flight work before starting another epoch.
handleAnotherEpochclears 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 resetbusybefore 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,
**/*.tsrequires 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 winSanitize 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 a200response containing sensitive text to reachLiveDemoData.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 winDo not expose server-provided error messages directly.
Line 419 accepts arbitrary
body.message;DemoPagelater 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 liftParse the full API envelope before casting it
web-next/app/demo/http-shadow-demo-client.ts:423-432
requestApionly checkscontractVersionand then casts unknown JSON toApiEnvelope<TPayload>. A response like{ contractVersion, payload: {} }will still pass this gate and fail later inmapSession,mapFeed, ormapReceiptwhen 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
📒 Files selected for processing (23)
CHANGELOG.mdtests/web-next-live-demo-data.test.tstests/web-next-shadow-demo-contract.test.tstests/web-next-shadow-demo-ui.test.tsweb-next/app/dashboard/page.tsxweb-next/app/demo/__tests__/demo-copy.test.tsweb-next/app/demo/__tests__/http-client.test.tsweb-next/app/demo/__tests__/mock-client-phase-order.test.tsweb-next/app/demo/__tests__/mock-receipt.test.tsweb-next/app/demo/http-shadow-demo-client.tsweb-next/app/demo/live-demo-data.tsweb-next/app/demo/mock-shadow-demo-client.tsweb-next/app/demo/page.tsxweb-next/app/demo/shadow-demo-contract.tsweb-next/app/demo/shadow-demo-contract.type-test.tsweb-next/app/docs/page.tsxweb-next/app/support/page.tsxweb-next/components/feed/bluesky-feed.tsxweb-next/components/feed/corgi-rank-badge.tsxweb-next/components/get-started-section.tsxweb-next/components/header.tsxweb-next/components/ui/__tests__/policy-bar.test.tsweb-next/components/ui/policy-bar.tsx
✅ Action performedFull 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. |
There was a problem hiding this comment.
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
⛔ Files ignored due to path filters (20)
web-next/public/images/avatars/arjun-mehta.pngis excluded by!**/*.pngweb-next/public/images/avatars/avery-brooks.pngis excluded by!**/*.pngweb-next/public/images/avatars/ben-adler.pngis excluded by!**/*.pngweb-next/public/images/avatars/claire-rowan.pngis excluded by!**/*.pngweb-next/public/images/avatars/daniel-weiss.pngis excluded by!**/*.pngweb-next/public/images/avatars/eli-moreno.pngis excluded by!**/*.pngweb-next/public/images/avatars/jules-park.pngis excluded by!**/*.pngweb-next/public/images/avatars/karen-miller.pngis excluded by!**/*.pngweb-next/public/images/avatars/leila-hart.pngis excluded by!**/*.pngweb-next/public/images/avatars/mara-finch.pngis excluded by!**/*.pngweb-next/public/images/avatars/marco-haddad.pngis excluded by!**/*.pngweb-next/public/images/avatars/maya-keene.pngis excluded by!**/*.pngweb-next/public/images/avatars/nick-salazar.pngis excluded by!**/*.pngweb-next/public/images/avatars/nina-valdez.pngis excluded by!**/*.pngweb-next/public/images/avatars/rowan-bell.pngis excluded by!**/*.pngweb-next/public/images/avatars/sam-ortega.pngis excluded by!**/*.pngweb-next/public/images/avatars/tessa-green.pngis excluded by!**/*.pngweb-next/public/images/avatars/theo-kim.pngis excluded by!**/*.pngweb-next/public/images/bluesky-butterfly-logo.svgis excluded by!**/*.svgweb-next/public/images/feed/birders-field-notes.pngis excluded by!**/*.png
📒 Files selected for processing (92)
CHANGELOG.mdsrc/scheduler/epoch-scheduler.tstests/web-next-demo-fixtures.test.tstests/web-next-homepage-anchors.test.tstests/web-next-live-demo-data.test.tstests/web-next-replay-model.test.tstests/web-next-score.test.tstests/web-next-shadow-demo-contract.test.tstests/web-next-shadow-demo-ui.test.tsweb-next/app/about/page.tsxweb-next/app/admin/page.tsxweb-next/app/dashboard/page.tsxweb-next/app/demo/__tests__/_flow.tsweb-next/app/demo/__tests__/demo-copy.test.tsweb-next/app/demo/__tests__/demo-request-coordinator.test.tsweb-next/app/demo/__tests__/http-client.test.tsweb-next/app/demo/__tests__/mock-client-phase-order.test.tsweb-next/app/demo/__tests__/mock-ranking.test.tsweb-next/app/demo/__tests__/mock-receipt.test.tsweb-next/app/demo/demo-client.tsweb-next/app/demo/demo-request-coordinator.tsweb-next/app/demo/http-shadow-demo-client.tsweb-next/app/demo/live-demo-data.tsweb-next/app/demo/mock-shadow-demo-client.tsweb-next/app/demo/page.tsxweb-next/app/demo/shadow-demo-contract.tsweb-next/app/demo/shadow-demo-contract.type-test.tsweb-next/app/demo/shadow-demo-copy.tsweb-next/app/demo/shadow-demo-fixtures.tsweb-next/app/docs/page.tsxweb-next/app/error.tsxweb-next/app/history/page.tsxweb-next/app/how-it-works/page.tsxweb-next/app/layout.tsxweb-next/app/not-found.tsxweb-next/app/page.tsxweb-next/app/post/page.tsxweb-next/app/privacy/page.tsxweb-next/app/proposals/page.tsxweb-next/app/research-consent/page.tsxweb-next/app/settings/page.tsxweb-next/app/sign-in/page.tsxweb-next/app/start/page.tsxweb-next/app/support/page.tsxweb-next/app/tos/page.tsxweb-next/app/vote/page.tsxweb-next/components/app-shell.tsxweb-next/components/bento-section.tsxweb-next/components/bluesky-sample-feed.tsxweb-next/components/brand-link.tsxweb-next/components/changelog-section.tsxweb-next/components/community-examples-section.tsxweb-next/components/cta-section.tsxweb-next/components/dashboard-preview.tsxweb-next/components/demo/agents-panel.tsxweb-next/components/demo/community-picker.tsxweb-next/components/demo/corpus-feed.tsxweb-next/components/demo/demo-disclosure.tsxweb-next/components/demo/flow-progress.tsxweb-next/components/demo/receipt-panel.tsxweb-next/components/demo/vote-panel.tsxweb-next/components/demo/weight-bars.tsxweb-next/components/faq-section.tsxweb-next/components/feed/bluesky-feed.tsxweb-next/components/feed/corgi-rank-badge.tsxweb-next/components/feed/replay-adapter.tsweb-next/components/footer-section.tsxweb-next/components/get-started-section.tsxweb-next/components/header.tsxweb-next/components/hero-section.tsxweb-next/components/how-it-works-replay.tsxweb-next/components/landing-ctas.tsxweb-next/components/legal-layout.tsxweb-next/components/modality-preview.tsxweb-next/components/replay-teaser.tsxweb-next/components/sign-in-dialog.tsxweb-next/components/social-proof.tsxweb-next/components/ui/__tests__/policy-bar.test.tsweb-next/components/ui/keyword-input.tsxweb-next/components/ui/layout.tsxweb-next/components/ui/linked-slider.tsxweb-next/components/ui/page-hero.tsxweb-next/components/ui/policy-bar.tsxweb-next/components/ui/score-breakdown.tsxweb-next/components/ui/topic-slider.tsxweb-next/components/ui/weight-bar.tsxweb-next/docs/design-system.mdweb-next/lib/live-metrics-snapshot.tsweb-next/lib/replay-model.tsweb-next/lib/signals.tsweb-next/next.config.mjsweb-next/tailwind.config.ts
There was a problem hiding this comment.
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
📒 Files selected for processing (13)
tests/web-next-post-uri.test.tstests/web-next-receipt-display-math.test.tsweb-next/app/about/page.tsxweb-next/app/demo/__tests__/mock-client-phase-order.test.tsweb-next/app/demo/mock-shadow-demo-client.tsweb-next/app/post/page.tsxweb-next/app/settings/page.tsxweb-next/app/start/page.tsxweb-next/app/vote/page.tsxweb-next/components/demo/receipt-panel.tsxweb-next/components/ui/linked-slider.tsxweb-next/lib/post-uri.tsweb-next/lib/receipt-display-math.ts
59a5c66 to
f2afa7c
Compare
f2afa7c to
70ecaa7
Compare
|
@coderabbitai full review |
✅ Action performedFull 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. |
|
@coderabbitai full review |
✅ Action performedFull review finished. |
There was a problem hiding this comment.
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
getReceiptdoesn't validaterequest.epochId— receipts can be mislabeled with the wrong epoch's math.
getFeed(line 516-522) correctly rejects anepochIdthat matches neitherstate.openEpoch.idnorstate.publishedEpoch.id, and selects weights based on that validated result.getReceiptnever 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 whateverrequest.epochIdthe caller passed (line 569, and again at line 602 inprovenance.shadowEpochId). Request the pre-advance epoch's receipt right afteradvanceEpoch()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 winStill unguarded against incomplete
explanation.components— render crash risk.Switching to
SIGNAL_KEYS.map(...)narrows which keys are read, butexplanation.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),cisundefinedandc.raw_scorethrows — crashing the render tree instead of routing through the existingpageState === "error"/ErrorCardpath.Add a Zod schema/runtime guard at the
transparencyApi.getPostExplanationboundary (preferred, sinceweb-next/lib/api/client.tsonly types the response, per the prior review), or defensively skip/guard here.AI agent prompt: add a runtime guard so a
PostExplanationResponsemissing onecomponentsentry either fails the query (routing topageState === "error") or renders with a0-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 winFeed health still silently disappears on
statsQueryerror.
statsQuery.isErrorisn't branched on — when the request fails,statsisundefined, so the whole ternary falls tonulland the "Feed health" section vanishes with noErrorCard, 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.isErrorbranch (before the success condition) renderingErrorCardwithonRetry={() => 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
⛔ Files ignored due to path filters (20)
web-next/public/images/avatars/arjun-mehta.pngis excluded by!**/*.pngweb-next/public/images/avatars/avery-brooks.pngis excluded by!**/*.pngweb-next/public/images/avatars/ben-adler.pngis excluded by!**/*.pngweb-next/public/images/avatars/claire-rowan.pngis excluded by!**/*.pngweb-next/public/images/avatars/daniel-weiss.pngis excluded by!**/*.pngweb-next/public/images/avatars/eli-moreno.pngis excluded by!**/*.pngweb-next/public/images/avatars/jules-park.pngis excluded by!**/*.pngweb-next/public/images/avatars/karen-miller.pngis excluded by!**/*.pngweb-next/public/images/avatars/leila-hart.pngis excluded by!**/*.pngweb-next/public/images/avatars/mara-finch.pngis excluded by!**/*.pngweb-next/public/images/avatars/marco-haddad.pngis excluded by!**/*.pngweb-next/public/images/avatars/maya-keene.pngis excluded by!**/*.pngweb-next/public/images/avatars/nick-salazar.pngis excluded by!**/*.pngweb-next/public/images/avatars/nina-valdez.pngis excluded by!**/*.pngweb-next/public/images/avatars/rowan-bell.pngis excluded by!**/*.pngweb-next/public/images/avatars/sam-ortega.pngis excluded by!**/*.pngweb-next/public/images/avatars/tessa-green.pngis excluded by!**/*.pngweb-next/public/images/avatars/theo-kim.pngis excluded by!**/*.pngweb-next/public/images/bluesky-butterfly-logo.svgis excluded by!**/*.svgweb-next/public/images/feed/birders-field-notes.pngis excluded by!**/*.png
📒 Files selected for processing (96)
CHANGELOG.mdtests/web-next-demo-fixtures.test.tstests/web-next-homepage-anchors.test.tstests/web-next-live-demo-data.test.tstests/web-next-post-uri.test.tstests/web-next-receipt-display-math.test.tstests/web-next-replay-model.test.tstests/web-next-score.test.tstests/web-next-shadow-demo-ui.test.tsweb-next/app/about/page.tsxweb-next/app/admin/page.tsxweb-next/app/dashboard/page.tsxweb-next/app/demo/__tests__/_flow.tsweb-next/app/demo/__tests__/demo-copy.test.tsweb-next/app/demo/__tests__/demo-request-coordinator.test.tsweb-next/app/demo/__tests__/http-client.test.tsweb-next/app/demo/__tests__/mock-client-phase-order.test.tsweb-next/app/demo/__tests__/mock-ranking.test.tsweb-next/app/demo/__tests__/mock-receipt.test.tsweb-next/app/demo/__tests__/topic-policy.test.tsweb-next/app/demo/demo-client.tsweb-next/app/demo/demo-request-coordinator.tsweb-next/app/demo/http-shadow-demo-client.tsweb-next/app/demo/live-demo-data.tsweb-next/app/demo/mock-shadow-demo-client.tsweb-next/app/demo/page.tsxweb-next/app/demo/shadow-demo-api-schemas.tsweb-next/app/demo/shadow-demo-copy.tsweb-next/app/demo/shadow-demo-fixtures.tsweb-next/app/demo/shadow-demo-view-model.tsweb-next/app/docs/page.tsxweb-next/app/error.tsxweb-next/app/history/page.tsxweb-next/app/how-it-works/page.tsxweb-next/app/layout.tsxweb-next/app/not-found.tsxweb-next/app/page.tsxweb-next/app/post/page.tsxweb-next/app/privacy/page.tsxweb-next/app/proposals/page.tsxweb-next/app/research-consent/page.tsxweb-next/app/settings/page.tsxweb-next/app/sign-in/page.tsxweb-next/app/start/page.tsxweb-next/app/support/page.tsxweb-next/app/tos/page.tsxweb-next/app/vote/page.tsxweb-next/components/app-shell.tsxweb-next/components/bento-section.tsxweb-next/components/bluesky-sample-feed.tsxweb-next/components/brand-link.tsxweb-next/components/changelog-section.tsxweb-next/components/community-examples-section.tsxweb-next/components/cta-section.tsxweb-next/components/dashboard-preview.tsxweb-next/components/demo/agents-panel.tsxweb-next/components/demo/community-picker.tsxweb-next/components/demo/corpus-feed.tsxweb-next/components/demo/demo-disclosure.tsxweb-next/components/demo/flow-progress.tsxweb-next/components/demo/receipt-panel.tsxweb-next/components/demo/topic-policy.tsxweb-next/components/demo/vote-panel.tsxweb-next/components/demo/weight-bars.tsxweb-next/components/faq-section.tsxweb-next/components/feed/bluesky-feed.tsxweb-next/components/feed/corgi-rank-badge.tsxweb-next/components/feed/replay-adapter.tsweb-next/components/footer-section.tsxweb-next/components/get-started-section.tsxweb-next/components/header.tsxweb-next/components/hero-section.tsxweb-next/components/how-it-works-replay.tsxweb-next/components/landing-ctas.tsxweb-next/components/legal-layout.tsxweb-next/components/modality-preview.tsxweb-next/components/replay-teaser.tsxweb-next/components/sign-in-dialog.tsxweb-next/components/social-proof.tsxweb-next/components/ui/__tests__/policy-bar.test.tsweb-next/components/ui/keyword-input.tsxweb-next/components/ui/layout.tsxweb-next/components/ui/linked-slider.tsxweb-next/components/ui/page-hero.tsxweb-next/components/ui/policy-bar.tsxweb-next/components/ui/score-breakdown.tsxweb-next/components/ui/topic-slider.tsxweb-next/components/ui/weight-bar.tsxweb-next/docs/design-system.mdweb-next/lib/live-metrics-snapshot.tsweb-next/lib/post-uri.tsweb-next/lib/receipt-display-math.tsweb-next/lib/replay-model.tsweb-next/lib/signals.tsweb-next/next.config.mjsweb-next/tailwind.config.ts
|
@coderabbitai full review |
✅ Action performedFull 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. |
|
@coderabbitai full review |
✅ Action performedFull 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. |
|
@coderabbitai full review |
✅ Action performedFull 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. |
Summary
Ships the final
web-nextproduct 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
d238388bb4d9228334d89e9df9c76e9f460151dbfrom PRs #333-#335.Changes
/how-it-works,/about,/docs,/start,/support,/proposals, and/settingsstatic routes plus intentional error/404 surfaces./demoas 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.Ranked feed | Receiptswitching, guided/free-play boundaries, focus/live-region handling, reduced-motion support, realistic local demo avatars, and responsive product page patterns.Product Truth
demo:*namespace.1 of 25is ballot share, not a claim of independent causal influence; the direct-ballot ablation holds scripted ballots fixed.Verification
npm run verify: 133 Vitest files / 1,331 tests, backend TypeScript, CLI, SDK, fixture, legacy web lint/build, and 23-route Next static export.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.1440x1100,756x762, and390x844: no-login session creation, live corpus, reviewer vote, 24 scripted ballots,25/2trimmed 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 measured390/390with 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.401and one intentional legacyclientNoncerejection before the compatibility retry succeeded. The latter must disappear after this exact backend/UI head deploys.Checklist
CHANGELOG.mdupdated under## [Unreleased]CodeRabbit Audit
92c1308f35144ad92a73ff615c31cefad5ba3314isstate=exemptable; all current-head CI is green, local CodeRabbit Major/Minor findings were fixed, and zero live CodeRabbit threads remain.Production Delivery
43c2370892e8c4a7cc62b3ea476247e39d47362c14156ff641aca5eee2cc223a798cb0f617a0f82ano-cachewith CSP; Next chunk returned immutable one-year cachingnoevictionat 64 MiB, zero demo/production Redis evictions, and zerodemo:*keys in production Redis