feat: rebuild live payment network explorer - #2628
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughThe pull request adds a server-rendered payment-network explorer with typed API access, filtering, graph and table views, inspection, search, and temporary reveals. It also adds route-scoped privacy controls for analytics, Sentry, CSP reports, caching, headers, and production access. ChangesPayment Network Explorer
Estimated code review effort: 5 (Critical) | ~120 minutes Sequence Diagram(s)sequenceDiagram
participant Browser
participant PaymentNetworkExplorer
participant ExplorerAPI
participant Sentry
participant PostHog
Browser->>PaymentNetworkExplorer: open payment-network explorer route
PaymentNetworkExplorer->>PostHog: suppress capture and recording
PaymentNetworkExplorer->>Sentry: stop replay and close client
PaymentNetworkExplorer->>ExplorerAPI: request session and network data
ExplorerAPI-->>PaymentNetworkExplorer: return typed explorer response
Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 13
🧹 Nitpick comments (22)
src/features/payment-network-explorer/query.ts (1)
104-117: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winNormalize
stateslike the other list filters.Every other list field passes through
sanitizeFilterValues, which dedupes and sorts.statesonly filters. Two equal filter selections in different order therefore produce different request keys inusePaymentNetworkExplorerand different query strings, which causes redundant fetches and sends duplicate values to the API.♻️ Proposed normalization
- states: filters.states.filter(isMovementState), + states: Array.from(new Set(filters.states.filter(isMovementState))).sort(),🤖 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 `@src/features/payment-network-explorer/query.ts` around lines 104 - 117, Update the states field in the returned filter object to pass through sanitizeFilterValues while preserving isMovementState validation, so states are filtered, deduplicated, and sorted consistently with the other list filters. Keep the existing normalized state values and request-key behavior for valid selections.src/features/payment-network-explorer/selectors.ts (1)
94-107: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winReuse one
Intl.Collatorfor string comparisons.
localeComparecreates collation state on each call.RelationshipTablesorts up to 5000 relationships (the requestlimit) on every sort change and every response, so this runs on the render path. A module-level collator removes that per-comparison cost.♻️ Proposed collator reuse
+const relationshipCollator = new Intl.Collator('en-US') + export function sortRelationships( @@ if (typeof a === 'number' && typeof b === 'number') return (a - b) * multiplier - return String(a).localeCompare(String(b)) * multiplier + return relationshipCollator.compare(String(a), String(b)) * multiplier🤖 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 `@src/features/payment-network-explorer/selectors.ts` around lines 94 - 107, Update sortRelationships to reuse a single module-level Intl.Collator for string comparisons instead of calling localeCompare directly on each sort comparison. Keep numeric sorting and direction handling unchanged, and use the shared collator in the non-numeric branch.src/features/payment-network-explorer/usePaymentNetworkExplorer.ts (1)
182-182: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low valueWrap
reloadinuseCallback.
reloadis a new function on every render. Consumers that memoize on this prop re-render on each parent render.♻️ Proposed refactor
+ const reload = useCallback(() => setReloadKey((value) => value + 1), []) + return { data, session, status, error, searching, revealing, - reload: () => setReloadKey((value) => value + 1), + reload, focusUsername, revealNode, }🤖 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 `@src/features/payment-network-explorer/usePaymentNetworkExplorer.ts` at line 182, Wrap the reload function in usePaymentNetworkExplorer with useCallback so its reference remains stable across renders, using setReloadKey as its dependency and preserving the existing increment behavior.src/features/payment-network-explorer/__tests__/query.test.ts (1)
33-38: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winAdd the accepted 120-day boundary case.
This assertion covers only the rejected side of the maximum window. The exact 120-day window is the boundary most likely to regress from an off-by-one change in the resolver.
💚 Proposed addition
expect(() => resolveExplorerWindow( { range: 'custom', customFrom: '2026-04-07T11:59:59.999Z', customTo: NOW.toISOString() }, NOW ) ).toThrow(ExplorerWindowError) + expect( + resolveExplorerWindow( + { range: 'custom', customFrom: '2026-04-08T12:00:00.000Z', customTo: NOW.toISOString() }, + NOW + ) + ).toEqual({ from: '2026-04-08T12:00:00.000Z', to: NOW.toISOString() })🤖 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 `@src/features/payment-network-explorer/__tests__/query.test.ts` around lines 33 - 38, Add a test near the existing resolveExplorerWindow maximum-window rejection assertion that passes a custom range exactly 120 days before NOW through NOW and expects successful resolution. Keep the current over-limit case unchanged, using the existing resolveExplorerWindow and NOW symbols to cover the accepted boundary.src/app/sw.ts (1)
24-38: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueMove the PostHog
/relay/explanation above the/relay/rule.Lines 27-31 describe the PostHog reverse-proxy path and the
no-responseerrors on recorder scripts. That text now sits above the payment-network rule, which has its own comment at Lines 34-35. A reader attributes the PostHog rationale to the wrong matcher.♻️ Proposed change
// A controlled service worker sees cross-origin fetches initiated by its // clients. Keep privacy-sensitive API routes ahead of every default rule. - // - // /relay/* is the PostHog reverse-proxy path (see next.config.js rewrites). - // Workbox's defaultCache strategies threw "no-response" on the recorder + - // dead-clicks scripts, polluting the console. PostHog assets carry their - // own versioning + cache headers; let the network handle them. NetworkOnly - // first so it wins ahead of any defaultCache JS-asset rule. runtimeCaching: [ { // Covers the protected document/RSC path on peanut.me and every // legacy/v2 graph endpoint across API origins, regardless of query. matcher: ({ url }) => isSensitivePaymentNetworkUrl(url), handler: new NetworkOnly(), }, { + // /relay/* is the PostHog reverse-proxy path (see next.config.js + // rewrites). Workbox's defaultCache strategies threw "no-response" + // on the recorder + dead-clicks scripts, polluting the console. + // PostHog assets carry their own versioning + cache headers; let + // the network handle them. NetworkOnly wins ahead of any + // defaultCache JS-asset rule. matcher: ({ url }) => url.pathname.startsWith('/relay/'), handler: new NetworkOnly(), },🤖 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 `@src/app/sw.ts` around lines 24 - 38, Move the PostHog `/relay/` explanation so it immediately precedes the runtime-caching rule whose matcher handles `/relay/` requests, keeping the payment-network rule’s comment directly attached to `isSensitivePaymentNetworkUrl` and leaving rule behavior unchanged.src/features/payment-network-explorer/__tests__/PaymentNetworkExplorer.test.tsx (3)
169-171: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winRestore the spies in
afterEachso a failed assertion does not leak them.The
consolespies are restored at the end of the test body (Lines 192-194). If any assertion between Lines 180-191 fails, the test aborts and the spies stay installed. Later tests in this file then run with a silenced console. TheDate.nowspy at Line 198 has the same problem, since it is restored at Line 243.Move the restoration into
afterEach, or addrestoreMocks: trueto the Jest config.♻️ Proposed change
afterEach(() => { + jest.restoreAllMocks() window.history.replaceState({}, '', '/') })Note that
jest.restoreAllMocks()also resets thejest.mocked(...)implementations set inbeforeEach; sincebeforeEachre-applies them on every test, the order remains correct.Also applies to: 192-194
🤖 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 `@src/features/payment-network-explorer/__tests__/PaymentNetworkExplorer.test.tsx` around lines 169 - 171, Update the PaymentNetworkExplorer test setup to restore all spies in an afterEach hook, including the console spies created in the test and the Date.now spy. Remove the inline restoration from the test body, and ensure beforeEach mock setup still runs after restoration for each test.
197-244: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low valueExpose
document.visibilityStatein the synthetic visibilitychange test.The
clearExpiredRevealhandler does not gate ondocument.visibilityState, but line 228 still does not set it to'hidden'. Setdocument.visibilityStateto'hidden'in jsdom before dispatchingvisibilitychangeso the test is not accidental if a visibility guard is added later.🤖 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 `@src/features/payment-network-explorer/__tests__/PaymentNetworkExplorer.test.tsx` around lines 197 - 244, Update the visibilitychange portion of the test case “clears privileged reveals on background resume and pagehide” to set document.visibilityState to 'hidden' before dispatching the synthetic visibilitychange event, preserving the existing expiration and reveal assertions.
169-194: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winEvery new test file cleans up global state at the end of the test body. The shared root cause is that cleanup is not registered with
afterEachorfinally. A failed assertion or a thrownrequireskips the cleanup, and the mutated global leaks into the remaining tests in the same Jest worker. EnablingrestoreMocks: truein the Jest config removes the spy half of this problem for all four files.
src/features/payment-network-explorer/__tests__/PaymentNetworkExplorer.test.tsx#L169-L194: move theconsole.log,console.warn,console.error, andDate.nowrestorations into the existingafterEach.src/features/payment-network-explorer/__tests__/headers.test.ts#L3-L21: restoreLOCAL_BUILD,NODE_ENV, and theconsole.logspy in anafterEachblock.src/features/payment-network-explorer/__tests__/privacy.test.ts#L84-L94: restore thedocument.head.appendChildspy in anafterEachblock instead of at Line 92.src/features/payment-network-explorer/__tests__/sentryServerEdge.test.ts#L37-L43: restoreNODE_ENVin afinallyblock around thejest.isolateModulescall.🤖 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 `@src/features/payment-network-explorer/__tests__/PaymentNetworkExplorer.test.tsx` around lines 169 - 194, Register global cleanup outside test bodies so failures cannot leak state: in src/features/payment-network-explorer/__tests__/PaymentNetworkExplorer.test.tsx#L169-L194, move console.log, console.warn, console.error, and Date.now restoration into the existing afterEach; in src/features/payment-network-explorer/__tests__/headers.test.ts#L3-L21, restore LOCAL_BUILD, NODE_ENV, and the console.log spy in afterEach; in src/features/payment-network-explorer/__tests__/privacy.test.ts#L84-L94, move document.head.appendChild restoration into afterEach; and in src/features/payment-network-explorer/__tests__/sentryServerEdge.test.ts#L37-L43, restore NODE_ENV in a finally block surrounding jest.isolateModules. Enable restoreMocks in the Jest configuration to provide automatic spy restoration.src/features/payment-network-explorer/__tests__/privacy.test.ts (1)
84-94: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueSuppress the scanner finding on
new Function, and restore the spy inafterEach.OpenGrep flags Line 88 as dynamic code execution. The input comes from the project's own
googleAnalyticsBootstrapScript, so the finding is a false positive for this test. Add an inline suppression so the rule does not stay noisy on this file.Separately,
append.mockRestore()at Line 92 runs only if the assertions at Lines 90-91 pass.jest.clearAllMocks()inbeforeEachclears calls but does not restore the spy, so a failure leavesdocument.head.appendChildpatched for later tests.♻️ Proposed change
const append = jest.spyOn(document.head, 'appendChild') const script = googleAnalyticsBootstrapScript('G-QATEST') + // nosemgrep: coderabbit.code-injection.new-function-js -- the script is + // generated by googleAnalyticsBootstrapScript, not by external input. new Function('window', 'document', 'location', script)(window, document, window.location) expect((window as unknown as Window & Record<string, unknown>)['ga-disable-G-QATEST']).toBe(true) expect(append).not.toHaveBeenCalled() - append.mockRestore() window.history.replaceState({}, '', '/')Add the restoration to the suite:
+ afterEach(() => { + jest.restoreAllMocks() + })🤖 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 `@src/features/payment-network-explorer/__tests__/privacy.test.ts` around lines 84 - 94, Add an inline OpenGrep suppression for the dynamic execution at `new Function` in the direct legacy URL test, documenting it as a false positive for the trusted `googleAnalyticsBootstrapScript` output. Move spy cleanup to the suite’s `afterEach` so `document.head.appendChild` is restored even when assertions fail, and remove the test-local `append.mockRestore()` to avoid duplicate cleanup.Source: Linters/SAST tools
src/features/payment-network-explorer/Inspector.tsx (1)
171-172: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low valueMemoize the connection list and show when it is truncated.
relationshipsForNodescans all relationships on every Inspector render, and.slice(0, 100)drops the remainder with no indication in the UI. Wrap the computation inuseMemo, and render a count when connections exceed the limit.♻️ Proposed refactor
+ const nodeRelationships = useMemo( + () => (selection?.type === 'node' ? relationshipsForNode(relationships, selection.node.id) : []), + [relationships, selection] + )Then render
nodeRelationships.slice(0, 100)in the list, and add a trailing note whennodeRelationships.length > 100.🤖 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 `@src/features/payment-network-explorer/Inspector.tsx` around lines 171 - 172, In the Inspector component, memoize the result of relationshipsForNode(relationships, selection.node.id) as nodeRelationships with useMemo and appropriate dependencies. Render nodeRelationships.slice(0, 100) in the connection list, and add a trailing UI note indicating the number of additional connections when nodeRelationships.length exceeds 100.tailwind.config.js (1)
16-16: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider a general
src/featuresglob.The new entry covers one feature directory. Every later feature under
src/featureswill need another entry, and a missing entry produces silently unstyled components.'./src/features/**/*.{js,ts,jsx,tsx,mdx}'covers the whole directory and matches the pattern of the entries above it.- './src/features/payment-network-explorer/**/*.{js,ts,jsx,tsx}', + './src/features/**/*.{js,ts,jsx,tsx,mdx}',🤖 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 `@tailwind.config.js` at line 16, Update the Tailwind content configuration entry for the payment-network-explorer feature to use the general src/features glob './src/features/**/*.{js,ts,jsx,tsx,mdx}', covering all current and future feature directories while preserving the existing extensions.src/features/payment-network-explorer/PaymentNetworkExplorer.tsx (1)
58-85: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider replacing the JSON round trip with a normal memo.
requestFilterKeyserializes the filters only to have Line 76 parse them back. The round trip stabilizes the memo across new array identities, but it hides that intent and it allocates two objects plus a string on every render. A memo over the individual filter fields expresses the same dependency set directly.- const requestResult = useMemo(() => { - const requestFilters = JSON.parse(requestFilterKey) as ExplorerFilters try {If you keep the current form, add a short comment that states why the key is serialized.
🤖 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 `@src/features/payment-network-explorer/PaymentNetworkExplorer.tsx` around lines 58 - 85, Replace the requestFilterKey JSON serialization and parsing in the requestResult useMemo with direct references to the individual filter fields, using those fields as the memo dependency set while preserving the existing buildExplorerRequest and error-handling behavior. Keep the request filters equivalent to the current ExplorerFilters object and remove the unnecessary serialization round trip.src/features/payment-network-explorer/__tests__/reducedMotion.test.tsx (1)
28-55: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueAdd coverage for a runtime preference change.
mockMatchMediastubsaddEventListenerwith ajest.fn()that never calls the listener. The test at Line 52 therefore only covers the initial read inuseReducedMotion. Thechangesubscription and the listener cleanup atuseReducedMotion.tsLines 14-15 stay untested. Capture the listener in the mock and invoke it, so a preference change is asserted to update the hook result.💚 Proposed test addition
function mockMatchMedia(matches: boolean) { + const listeners = new Set<() => void>() window.matchMedia = jest.fn( () => ({ matches, media: '(prefers-reduced-motion: reduce)', onchange: null, - addEventListener: jest.fn(), - removeEventListener: jest.fn(), + addEventListener: jest.fn((_: string, listener: () => void) => listeners.add(listener)), + removeEventListener: jest.fn((_: string, listener: () => void) => listeners.delete(listener)), addListener: jest.fn(), removeListener: jest.fn(), dispatchEvent: jest.fn(), }) as unknown as MediaQueryList ) + return listeners }🤖 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 `@src/features/payment-network-explorer/__tests__/reducedMotion.test.tsx` around lines 28 - 55, Update mockMatchMedia and the reduced-motion test to capture the change listener registered through addEventListener, then invoke it with an updated matches value and assert useReducedMotion reflects the preference change. Also exercise unmounting so the removeEventListener cleanup in useReducedMotion is covered.src/app/api/csp-report/__tests__/route.test.ts (1)
26-61: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd a positive control, and cover
script-src-elemandscript-src-attr.Both cases assert
mockedFetchwas not called. The route returns 204 without forwarding for several unrelated reasons, including an unaccepted content type and an empty report list. If a future change stops forwarding every report, this suite still passes and the explorer-privacy guarantee is no longer proven.Add a case with a non-explorer
document-urithat assertsmockedFetchwas called. Also extend the fixtures beyond the barescript-srcdirective: browsers report inline scripts asscript-src-elemand inline handlers asscript-src-attr.💚 Proposed test addition
+ it('forwards a non-explorer report', async () => { + const request = { + headers: new Headers({ 'content-type': 'application/csp-report' }), + json: jest.fn().mockResolvedValue({ + 'csp-report': { + 'document-uri': 'https://peanut.me/card', + 'blocked-uri': 'https://blocked.example/script.js', + 'effective-directive': 'script-src-elem', + }, + }), + } as unknown as NextRequest + + expect((await POST(request)).status).toBe(204) + expect(mockedFetch).toHaveBeenCalledTimes(1) + })Based on learnings: in the CSP reporting pipeline, aggregation and handling tests must cover both
script-src-elemandscript-src-attr, not onlyscript-src.🤖 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 `@src/app/api/csp-report/__tests__/route.test.ts` around lines 26 - 61, Strengthen the CSP report route tests around the parameterized fixtures by adding a non-explorer document URI case that expects mockedFetch to be called, proving eligible reports are forwarded. Add coverage for script-src-elem and script-src-attr in both supported report formats while retaining the existing explorer-privacy assertions that expect no forwarding.Source: Learnings
src/app/payment-network-sw-privacy.ts (1)
1-11: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winShare the explorer API path for service-worker privacy checks.
src/app/payment-network-sw-privacy.tskeeps a separatePAYMENT_GRAPH_API_PATH = '/invites/graph'copy from the explorer API callers. Use one shared constant across the service worker and payment-network-explorer callers so a route rename cannot disable cache purging while leaving API paths enabled.🤖 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 `@src/app/payment-network-sw-privacy.ts` around lines 1 - 11, The payment graph API path is duplicated in isSensitivePaymentNetworkPathname; replace the local PAYMENT_GRAPH_API_PATH with the shared explorer API-path constant used by payment-network-explorer callers, updating imports or exports as needed. Preserve both exact and trailing-subpath checks so privacy handling remains unchanged after route renames.src/features/payment-network-explorer/FilterPanel.tsx (1)
184-190: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueNarrow the direction values instead of casting.
Line 189 casts the incoming
string[]toExplorerFilters['directions']. The values come from server facets, so an unexpected value passes the type system unchecked. Filter against the known direction union before callingonChange.🤖 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 `@src/features/payment-network-explorer/FilterPanel.tsx` around lines 184 - 190, Update the Direction FacetChecklist onChange handler to filter incoming strings against the known ExplorerFilters direction union before calling onChange, instead of casting the entire array. Preserve only valid direction values and pass that narrowed collection to onChange.sentry.utils.ts (1)
5-5: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueMove the payment-graph route into shared route constants.
src/constants/routes.tsonly lists/devas a dev-route pattern and does not exportPAYMENT_NETWORK_PATH. Add the explicit/dev/payment-graphconstant there and use it fromsentry.utils.ts,privacy-route.ts, and other route code that hard-code this path.🤖 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 `@sentry.utils.ts` at line 5, Move the /dev/payment-graph path into the shared route constants by adding and exporting a PAYMENT_NETWORK_PATH symbol in routes.ts, then replace hard-coded path values and local definitions in sentry.utils.ts, privacy-route.ts, and other route code with that shared constant while preserving existing route-matching behavior.src/features/payment-network-explorer/FacetChecklist.tsx (1)
64-81: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueAssign an ARIA role to the badge
<span>elements.A generic
<span>does not reliably supportaria-label. Addrole="img"to the active and inactive badge spans so screen readers can expose their accessible names.🤖 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 `@src/features/payment-network-explorer/FacetChecklist.tsx` around lines 64 - 81, Add role="img" to both badge span elements rendered for active and inactive facets in FacetChecklist, preserving their existing aria-label values and styling.next.config.js (1)
407-407: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win
sourcematches only the exact path.
'/dev/payment-graph'does not match descendants. The shared predicateisPaymentNetworkExplorerPathinsrc/features/payment-network-explorer/privacy-route.tstreats${PAYMENT_NETWORK_PATH}/...as private too. If the explorer gains a sub-route, these privacy headers would not apply to it. Adding a second entry for'/dev/payment-graph/:path*'keeps the header scope and the predicate scope aligned.🤖 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 `@next.config.js` at line 407, The headers configuration currently matches only the exact payment graph path; add a companion source entry for the payment graph descendants using the same header configuration as the existing '/dev/payment-graph' entry. Keep it aligned with the descendant matching performed by isPaymentNetworkExplorerPath and do not alter unrelated routes.src/utils/csp-report.utils.ts (1)
73-80: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick winConsider matching the explorer URL in other report fields.
The filter inspects only
document-uri. A CSP report can also carry the page URL inreferrer. If a report for the explorer route arrives withoutdocument-uri, the current logic keeps it and the sensitive URL reaches the log. The route headers innext.config.jsremove report delivery for this path, so this filter is defense in depth. Extending the same pathname check toreferrercloses the remaining gap for one extra line.🛡️ Proposed hardening
export function shouldIgnoreCspReport(report: CspReport): boolean { if (isUnfixableOrigin(report['blocked-uri']) || isUnfixableOrigin(report['source-file'])) return true - const documentUri = report['document-uri'] - if (typeof documentUri !== 'string') return false - try { - return isPaymentNetworkExplorerPath(new URL(documentUri, 'https://peanut.invalid').pathname) - } catch { - return false - } + return [report['document-uri'], report['referrer']].some((value) => { + if (typeof value !== 'string') return false + try { + return isPaymentNetworkExplorerPath(new URL(value, 'https://peanut.invalid').pathname) + } catch { + return false + } + }) }🤖 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 `@src/utils/csp-report.utils.ts` around lines 73 - 80, Extend the URL filtering logic after the documentUri handling to also inspect the report’s referrer field, safely validating it as a string and parsing it with the existing fallback base URL. Return true when isPaymentNetworkExplorerPath matches the referrer pathname, while preserving false for missing or invalid URL values and reusing the existing error-handling pattern.src/features/payment-network-explorer/NetworkCanvas.tsx (1)
41-41: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueName the dense-graph thresholds.
The literals
1000at Line 79 and4/1.8at Line 109 control the same dense-view behavior asDENSE_LINK_LIMITS. Named constants next toDENSE_LINK_LIMITSwould keep the tuning values in one place.Also applies to: 79-79, 109-109
🤖 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 `@src/features/payment-network-explorer/NetworkCanvas.tsx` at line 41, Define named constants alongside DENSE_LINK_LIMITS for the dense-view tuning values currently represented by 1000, 4, and 1.8, then replace those inline literals at the referenced logic points with the new constants while preserving the existing behavior.src/features/payment-network-explorer/InfoTooltip.tsx (1)
40-52: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueUse
useEffectfor the tooltip position listeners.This client component can be rendered during SSR, and
useLayoutEffectin the open path can trigger React’s server rendering warning. The tooltip starts closed and stays hidden untilpositionis set, so the listener setup does not need paint-blocking layout behavior.🤖 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 `@src/features/payment-network-explorer/InfoTooltip.tsx` around lines 40 - 52, Replace useLayoutEffect with useEffect in the tooltip listener setup around the open state and updatePosition callback, preserving the existing position reset, listener registration, and cleanup behavior.
🤖 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 `@next.config.js`:
- Around line 406-420: Update the global `/:path*` security-header configuration
so it excludes `/dev/payment-graph` and does not append its CSP reporting
headers or `Reporting-Endpoints` entry. Preserve the route-specific headers
using `contentSecurityPolicyReportOnly(false)` and the `csp-disabled` endpoint,
while leaving global behavior unchanged for all other paths.
In `@src/app/sw.ts`:
- Around line 79-84: Update the activate handler around
purgeSensitivePaymentNetworkCacheEntries so the promise passed to
event.waitUntil handles any rejection from cacheStorage.keys(), including a safe
catch or equivalent recovery path that prevents activation failure. Preserve the
existing per-cache purge behavior and ensure the purge failure is contained
without affecting unrelated caches.
In `@src/features/payment-network-explorer/__tests__/ExplorerSummary.test.tsx`:
- Around line 56-58: Update the number formatting in ExplorerSummary to pass an
explicit stable locale, preferably toLocaleString('en-US'), for the values
rendered in the tooltip. Ensure all formatted counts used by the
assertions—matched events, nodes, and relationships—use this locale while
preserving their existing output.
In `@src/features/payment-network-explorer/__tests__/headers.test.ts`:
- Line 37: Update the Reporting-Endpoints assertion in the headers test to match
the CSP report path without requiring a trailing quote, using the existing
null-safe fallback so an absent header still fails the containment check.
In `@src/features/payment-network-explorer/__tests__/privacy.test.ts`:
- Around line 48-49: Make the privacy test assertions verify concrete storage
and PostHog behavior rather than relying on object spreads or absent mock
properties. In the relevant test, inspect storage through explicit key/value or
length/key checks for the before/after state, and update the posthog-js
mock/assertion so the opt_out_capturing configuration is explicitly observable
and validated.
In `@src/features/payment-network-explorer/__tests__/sentryServerEdge.test.ts`:
- Around line 37-43: Update the NODE_ENV override in the test setup to preserve
writability, matching the existing headers.test.ts pattern, and wrap the
isolated module require in cleanup that always restores the original value even
when it throws. Keep the mock reset and module isolation behavior unchanged.
In `@src/features/payment-network-explorer/api.ts`:
- Around line 39-51: Update requestSignal to immediately abort its internal
controller when the provided external signal is already aborted, while
preserving the existing listener for future abort events and cleanup behavior.
Ensure callers such as usePaymentNetworkExplorer do not proceed with
fetchPaymentNetwork after an already-aborted signal.
In `@src/features/payment-network-explorer/ExplorerHeader.tsx`:
- Around line 29-41: Add role="group" to the view-toggle container div with
aria-label="Explorer view" so assistive technology announces the group name for
the aria-pressed buttons. Leave the existing button behavior and labeling
unchanged.
In `@src/features/payment-network-explorer/Inspector.tsx`:
- Around line 41-42: Extract the inline reveal form into a RevealForm child
component that owns the reason and revealError state, then render it keyed by
selection.node.id with the existing node, revealing, and onReveal inputs so both
states reset whenever the selected node changes.
In `@src/features/payment-network-explorer/PaymentNetworkExplorer.tsx`:
- Around line 32-35: Move consumeLegacyGraphUsername out of the useState
initializer in PaymentNetworkExplorer and invoke it from a mount-only effect
instead. Initialize legacyUsername and legacyFocusPending with SSR-safe empty
values, then update both states from the consumed username after mount so the
first server and client renders match while preserving legacy focus behavior.
In `@src/features/payment-network-explorer/RelationshipTable.tsx`:
- Around line 100-114: Replace aria-selected on the relationship row in the
table with aria-current so the selected state is exposed correctly while
preserving the static table semantics. Keep the existing selected styling, click
handling, and keyboard behavior unchanged.
In `@src/features/payment-network-explorer/SearchBox.tsx`:
- Around line 17-23: The submit handler in SearchBox’s submit function must
preserve username when onSearch results in an error. Clear the input only after
a successful search, or condition the existing setUsername('') on the error
state returned or updated by onSearch.
In `@src/features/payment-network-explorer/usePaymentNetworkExplorer.ts`:
- Around line 56-71: Update ensureSession so the deduplicated
createExplorerSession request is not created with a caller-scoped AbortSignal;
its shared lifetime must be independent of any individual effect cleanup. Add a
liveness check before setCurrentSession so an aborted or no-longer-active flow
cannot write the resolved session to state, while preserving request
deduplication and cleanup of sessionRequestRef.
---
Nitpick comments:
In `@next.config.js`:
- Line 407: The headers configuration currently matches only the exact payment
graph path; add a companion source entry for the payment graph descendants using
the same header configuration as the existing '/dev/payment-graph' entry. Keep
it aligned with the descendant matching performed by
isPaymentNetworkExplorerPath and do not alter unrelated routes.
In `@sentry.utils.ts`:
- Line 5: Move the /dev/payment-graph path into the shared route constants by
adding and exporting a PAYMENT_NETWORK_PATH symbol in routes.ts, then replace
hard-coded path values and local definitions in sentry.utils.ts,
privacy-route.ts, and other route code with that shared constant while
preserving existing route-matching behavior.
In `@src/app/api/csp-report/__tests__/route.test.ts`:
- Around line 26-61: Strengthen the CSP report route tests around the
parameterized fixtures by adding a non-explorer document URI case that expects
mockedFetch to be called, proving eligible reports are forwarded. Add coverage
for script-src-elem and script-src-attr in both supported report formats while
retaining the existing explorer-privacy assertions that expect no forwarding.
In `@src/app/payment-network-sw-privacy.ts`:
- Around line 1-11: The payment graph API path is duplicated in
isSensitivePaymentNetworkPathname; replace the local PAYMENT_GRAPH_API_PATH with
the shared explorer API-path constant used by payment-network-explorer callers,
updating imports or exports as needed. Preserve both exact and trailing-subpath
checks so privacy handling remains unchanged after route renames.
In `@src/app/sw.ts`:
- Around line 24-38: Move the PostHog `/relay/` explanation so it immediately
precedes the runtime-caching rule whose matcher handles `/relay/` requests,
keeping the payment-network rule’s comment directly attached to
`isSensitivePaymentNetworkUrl` and leaving rule behavior unchanged.
In
`@src/features/payment-network-explorer/__tests__/PaymentNetworkExplorer.test.tsx`:
- Around line 169-171: Update the PaymentNetworkExplorer test setup to restore
all spies in an afterEach hook, including the console spies created in the test
and the Date.now spy. Remove the inline restoration from the test body, and
ensure beforeEach mock setup still runs after restoration for each test.
- Around line 197-244: Update the visibilitychange portion of the test case
“clears privileged reveals on background resume and pagehide” to set
document.visibilityState to 'hidden' before dispatching the synthetic
visibilitychange event, preserving the existing expiration and reveal
assertions.
- Around line 169-194: Register global cleanup outside test bodies so failures
cannot leak state: in
src/features/payment-network-explorer/__tests__/PaymentNetworkExplorer.test.tsx#L169-L194,
move console.log, console.warn, console.error, and Date.now restoration into the
existing afterEach; in
src/features/payment-network-explorer/__tests__/headers.test.ts#L3-L21, restore
LOCAL_BUILD, NODE_ENV, and the console.log spy in afterEach; in
src/features/payment-network-explorer/__tests__/privacy.test.ts#L84-L94, move
document.head.appendChild restoration into afterEach; and in
src/features/payment-network-explorer/__tests__/sentryServerEdge.test.ts#L37-L43,
restore NODE_ENV in a finally block surrounding jest.isolateModules. Enable
restoreMocks in the Jest configuration to provide automatic spy restoration.
In `@src/features/payment-network-explorer/__tests__/privacy.test.ts`:
- Around line 84-94: Add an inline OpenGrep suppression for the dynamic
execution at `new Function` in the direct legacy URL test, documenting it as a
false positive for the trusted `googleAnalyticsBootstrapScript` output. Move spy
cleanup to the suite’s `afterEach` so `document.head.appendChild` is restored
even when assertions fail, and remove the test-local `append.mockRestore()` to
avoid duplicate cleanup.
In `@src/features/payment-network-explorer/__tests__/query.test.ts`:
- Around line 33-38: Add a test near the existing resolveExplorerWindow
maximum-window rejection assertion that passes a custom range exactly 120 days
before NOW through NOW and expects successful resolution. Keep the current
over-limit case unchanged, using the existing resolveExplorerWindow and NOW
symbols to cover the accepted boundary.
In `@src/features/payment-network-explorer/__tests__/reducedMotion.test.tsx`:
- Around line 28-55: Update mockMatchMedia and the reduced-motion test to
capture the change listener registered through addEventListener, then invoke it
with an updated matches value and assert useReducedMotion reflects the
preference change. Also exercise unmounting so the removeEventListener cleanup
in useReducedMotion is covered.
In `@src/features/payment-network-explorer/FacetChecklist.tsx`:
- Around line 64-81: Add role="img" to both badge span elements rendered for
active and inactive facets in FacetChecklist, preserving their existing
aria-label values and styling.
In `@src/features/payment-network-explorer/FilterPanel.tsx`:
- Around line 184-190: Update the Direction FacetChecklist onChange handler to
filter incoming strings against the known ExplorerFilters direction union before
calling onChange, instead of casting the entire array. Preserve only valid
direction values and pass that narrowed collection to onChange.
In `@src/features/payment-network-explorer/InfoTooltip.tsx`:
- Around line 40-52: Replace useLayoutEffect with useEffect in the tooltip
listener setup around the open state and updatePosition callback, preserving the
existing position reset, listener registration, and cleanup behavior.
In `@src/features/payment-network-explorer/Inspector.tsx`:
- Around line 171-172: In the Inspector component, memoize the result of
relationshipsForNode(relationships, selection.node.id) as nodeRelationships with
useMemo and appropriate dependencies. Render nodeRelationships.slice(0, 100) in
the connection list, and add a trailing UI note indicating the number of
additional connections when nodeRelationships.length exceeds 100.
In `@src/features/payment-network-explorer/NetworkCanvas.tsx`:
- Line 41: Define named constants alongside DENSE_LINK_LIMITS for the dense-view
tuning values currently represented by 1000, 4, and 1.8, then replace those
inline literals at the referenced logic points with the new constants while
preserving the existing behavior.
In `@src/features/payment-network-explorer/PaymentNetworkExplorer.tsx`:
- Around line 58-85: Replace the requestFilterKey JSON serialization and parsing
in the requestResult useMemo with direct references to the individual filter
fields, using those fields as the memo dependency set while preserving the
existing buildExplorerRequest and error-handling behavior. Keep the request
filters equivalent to the current ExplorerFilters object and remove the
unnecessary serialization round trip.
In `@src/features/payment-network-explorer/query.ts`:
- Around line 104-117: Update the states field in the returned filter object to
pass through sanitizeFilterValues while preserving isMovementState validation,
so states are filtered, deduplicated, and sorted consistently with the other
list filters. Keep the existing normalized state values and request-key behavior
for valid selections.
In `@src/features/payment-network-explorer/selectors.ts`:
- Around line 94-107: Update sortRelationships to reuse a single module-level
Intl.Collator for string comparisons instead of calling localeCompare directly
on each sort comparison. Keep numeric sorting and direction handling unchanged,
and use the shared collator in the non-numeric branch.
In `@src/features/payment-network-explorer/usePaymentNetworkExplorer.ts`:
- Line 182: Wrap the reload function in usePaymentNetworkExplorer with
useCallback so its reference remains stable across renders, using setReloadKey
as its dependency and preserving the existing increment behavior.
In `@src/utils/csp-report.utils.ts`:
- Around line 73-80: Extend the URL filtering logic after the documentUri
handling to also inspect the report’s referrer field, safely validating it as a
string and parsing it with the existing fallback base URL. Return true when
isPaymentNetworkExplorerPath matches the referrer pathname, while preserving
false for missing or invalid URL values and reusing the existing error-handling
pattern.
In `@tailwind.config.js`:
- Line 16: Update the Tailwind content configuration entry for the
payment-network-explorer feature to use the general src/features glob
'./src/features/**/*.{js,ts,jsx,tsx,mdx}', covering all current and future
feature directories while preserving the existing extensions.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 7c3290be-7d2e-4e9f-af81-152f058f77b4
📒 Files selected for processing (55)
instrumentation-client.tsnext.config.jssentry.client.config.tssentry.edge.config.tssentry.server.config.tssentry.utils.tssrc/app/(mobile-ui)/dev/payment-graph/page.tsxsrc/app/__tests__/payment-network-sw-privacy.test.tssrc/app/api/csp-report/__tests__/route.test.tssrc/app/layout.tsxsrc/app/payment-network-sw-privacy.tssrc/app/sw.tssrc/constants/__tests__/routes.test.tssrc/constants/routes.tssrc/features/payment-network-explorer/DesktopGuard.tsxsrc/features/payment-network-explorer/ExplorerHeader.tsxsrc/features/payment-network-explorer/ExplorerStatePanel.tsxsrc/features/payment-network-explorer/ExplorerSummary.tsxsrc/features/payment-network-explorer/FacetChecklist.tsxsrc/features/payment-network-explorer/FilterPanel.tsxsrc/features/payment-network-explorer/FocusBanner.tsxsrc/features/payment-network-explorer/InfoTooltip.tsxsrc/features/payment-network-explorer/Inspector.tsxsrc/features/payment-network-explorer/NetworkCanvas.tsxsrc/features/payment-network-explorer/PaymentNetworkExplorer.tsxsrc/features/payment-network-explorer/RelationshipDetails.tsxsrc/features/payment-network-explorer/RelationshipTable.tsxsrc/features/payment-network-explorer/SearchBox.tsxsrc/features/payment-network-explorer/__tests__/ExplorerSummary.test.tsxsrc/features/payment-network-explorer/__tests__/FacetChecklist.test.tsxsrc/features/payment-network-explorer/__tests__/InfoTooltip.test.tsxsrc/features/payment-network-explorer/__tests__/PaymentNetworkExplorer.test.tsxsrc/features/payment-network-explorer/__tests__/api.test.tssrc/features/payment-network-explorer/__tests__/format.test.tssrc/features/payment-network-explorer/__tests__/headers.test.tssrc/features/payment-network-explorer/__tests__/privacy.test.tssrc/features/payment-network-explorer/__tests__/query.test.tssrc/features/payment-network-explorer/__tests__/reducedMotion.test.tsxsrc/features/payment-network-explorer/__tests__/selectors.test.tssrc/features/payment-network-explorer/__tests__/sentryServerEdge.test.tssrc/features/payment-network-explorer/__tests__/usePaymentNetworkExplorer.test.tsxsrc/features/payment-network-explorer/api.tssrc/features/payment-network-explorer/format.tssrc/features/payment-network-explorer/privacy-route.tssrc/features/payment-network-explorer/privacy.tssrc/features/payment-network-explorer/query.tssrc/features/payment-network-explorer/selectors.tssrc/features/payment-network-explorer/types.tssrc/features/payment-network-explorer/useDesktopViewport.tssrc/features/payment-network-explorer/useExplorerUrlState.tssrc/features/payment-network-explorer/usePaymentNetworkExplorer.tssrc/features/payment-network-explorer/useReducedMotion.tssrc/utils/__tests__/csp-report.utils.test.tssrc/utils/csp-report.utils.tstailwind.config.js
|
@coderabbitai review |
|
Summary
payment-network.v2API.no-store, no referrers, and route-level GA/PostHog/Sentry/CSP-report suppression.Task
TASK-21220
Cross-repo / deploy order
peanut-api-tsPR is still pending. Deploy the backend contract before this UI; the explorer intentionally rejects a missing/mismatched contract instead of falling back to the legacy graph.Backend deployment must configure
PAYMENT_NETWORK_TEAM_USER_IDS,PAYMENT_NETWORK_REVEAL_USER_IDS, andPAYMENT_NETWORK_TOKEN_SECRET. The browser exchanges the normal app bearer once for a short-lived Secure HttpOnly session; graph/focus/reveal requests then use that cookie only. Full-graph delivery remains disabled unless the backend benchmark gates pass, so deterministic server sampling is the safe default.Design notes / accepted trade-offs
QA
prettier --check .✅npm run typecheck✅npm test -- --runInBand✅ — 222 suites, 2,745 passed, 3 skippedearlyoompressure; remote CI/Preview is the authoritative build gate.Screenshots
Summary by CodeRabbit
New Features
Bug Fixes