feat(ui): staleness banner and refresh-base action#1366
Conversation
Slice 2 of the auto-snapshot shared base feature: cloud-mode UI for
detecting and resolving baseline drift on PR sessions.
- StalenessBanner renders a yellow info bar in the lineage/diff view
header when this session's frozen base differs from the project's
current shared base. Persistent (correctness-critical, not
dismissible). Refresh button POSTs /sessions/{id}/refresh-base; on
the metadata_updated WS event, lineage queries invalidate, the
banner clears, and a confirmation toast fires. 30-second timeout
fallback if the WS event never arrives.
- FirstTimePopover gives a one-shot intro the first time the banner
appears, gated by a localStorage flag.
- LineageGraphAdapter handles the new metadata_updated WS message
type and invalidates lineage/checks/runs caches.
- SessionStaleness type and isSessionBaseOutdated selector added to
the cloud session shape; banner subscribes via useQuery so it
re-renders reactively on cache updates.
- Edge case: refresh disabled with tooltip when the project has no
shared base configured. OSS mode unaffected (cloud-mode gated).
Resolves DRC-3311 (PR-B).
Signed-off-by: even-wei <evenwei@infuseai.io>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Code Review: PR #1366Reviewed commit: Validation ResultsPass A: Correctness & Logic — PASS
Pass B: Security — PASSNo new secrets, no PII added to logs/messages, no user input in URLs. The refresh endpoint takes no body; auth flows through the existing Pass C: Cross-Reference Consistency — PASS (with verification done end-to-end against backend PR #1286)
Pass D: Error Handling & Edge Cases — PASS (one NOTE)
Pass E: Test Coverage & Quality — PASS
Pass F: Diff-Specific Checks — PASS (one ISSUE, one NOTE)
Pass H: Async/Concurrency — PASS (one NOTE)
Verification Results
Verdict: NO-GOIssues
Notes
What I could not verify
What I looked for and did not find
|
even-wei
left a comment
There was a problem hiding this comment.
Claude Code Review (self-review): NO-GO. 1 ISSUE (release-note/scope mismatch — banner only mounts on the lineage view, not query/checks as advertised) and 6 NOTES. Cross-PR contract against backend PR DataRecce/recce-cloud-infra#1286 verified end-to-end (endpoint paths, WS payload, type shapes). See review comment for full findings.
- Move StalenessBanner mount from LineageViewOss to MainLayout so it renders across lineage, query, and checks views (was lineage-only, contradicting the release-note scope). - Branch banner copy when current_base_session_id is null: previous "production data has changed" wording was misleading when the project's shared base is absent. - Guard the refresh-error toast behind the same timeout-pending check used for clearTimeout, preventing a double toast on a slow-then- failing POST that races the 30s timeout. - Rename LOCAL_STORAGE_KEYS.snapshotBaseIntroSeen to the hyphenated recce- template used by the other entries. - Remove unused useQueryClient import in LineageGraphAdapter test. - Document the navigate-away-mid-refresh transition-guard limitation in StalenessBanner with an inline comment (accepted for slice 2). Verified: pnpm lint, pnpm type:check, pnpm test (3707 passed), pnpm run build all clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: even-wei <evenwei@infuseai.io>
|
Addressed in commit Decision on the ISSUE (release-note scope): I picked option (a) widen mount points rather than narrow the release-note. Rationale: Per-finding resolution:
Verification:
Browser test: Not run for this round. The user-visible changes (mount point, copy branching, double-toast guard) are covered by the existing component tests, and the cloud-mode banner requires the paired backend PR #1286 running locally to verify end-to-end. Surfacing this transparently per workspace policy. |
Code Review: PR #1366 — Incremental ReviewReviewed commit: Prior findings — status
New code — adversarial passPass A — Correctness. Callback-ref pattern is correct: Pass C — Cross-reference. Pass D — Error handling. Pass E — Test coverage. The new regression test exercises the actual bug:
Notes (non-blocking)
Verdict: GOAll four prior findings are resolved or appropriately deferred. The fix commit introduces no new BLOCKERs or ISSUEs. Approving. |
wcchang1115
left a comment
There was a problem hiding this comment.
Claude Code Review: one ISSUE (FirstTimePopover anchorEl won't open due to useRef pattern), plus 3 NOTEs. See review comment for details.
- ISSUE: convert anchor ref from useRef to useState so MUI Popover re-renders after the banner DOM commits. The previous useRef pattern passed anchorEl=null on first render; FirstTimePopover's internal setOpen(true) re-rendered with the same null anchor and the guard open && anchorEl !== null kept the popover hidden. The unit test bypassed the issue by passing a pre-built anchor — added a banner-level regression test that mounts FirstTimePopover via the real anchor path. - NOTE: extract shared useServerInfo() hook owned by LineageGraphAdapter and StalenessBanner so future query options (staleTime / enabled / retry) stay in lockstep across both call sites. - NOTE: add refreshSessionBase(client) helper to api/info.ts so the banner no longer inlines a raw apiClient.post path; matches the surrounding api/ helper convention (getServerInfo, aggregateRuns). - NOTE on placement (banner above TopBar): deferred. Design pass for the staleness banner is still pending (called out in PR body); will be revisited once the final visual treatment is approved. Verification: pnpm lint, pnpm type:check, pnpm test (3708 passed, 5 skipped — pre-existing), pnpm run build all clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: even-wei <evenwei@infuseai.io>
|
@wcchang1115 Addressed in Per-finding resolution:
Verification on
Backend contract against PR DataRecce/recce-cloud-infra#1286 is unchanged: same endpoint path ( |
wcchang1115
left a comment
There was a problem hiding this comment.
Claude Code Review (incremental on 15200dfd): all prior findings resolved, no new issues found. See updated review comment for details.
PR checklist
What type of PR is this?
feat— slice 2 of the auto-snapshot shared base feature: cloud-mode UI for detecting and resolving baseline drift on PR sessions.What this PR does / why we need it:
When a PR session's frozen-snapshot base diverges from the project's current shared base (production data has moved since the snapshot was captured), the user now sees a yellow staleness banner with a Refresh button on the lineage / query / checks views.
Clicking Refresh:
/sessions/{id}/refresh-base(backend re-clones the latest shared base into this session's S3 prefix).metadata_updatedover WebSocket on completion.source_session_updated_atcatches up tocurrent_base_updated_at.A 30-second timeout fallback fires an error toast if the WebSocket event never arrives (banner remains; user can retry).
What this PR adds:
StalenessBanner.tsx— yellow info bar (MUIBox+role="status") mounted in the shared app shell (MainLayout.tsx) aboveTopBarOss, so the banner appears on the lineage, query, and checks views. Persistent (not dismissible — correctness-critical). Cloud-mode gated viauseLineageGraphContext().cloudMode. Subscribes reactively viauseQuery({ queryKey: cacheKeys.lineage(), select: d => d.session_staleness })— re-renders on cache invalidation without parent re-render dependency. Banner copy branches whencurrent_base_session_id === null(project has no shared base) to avoid implying "production data has changed".FirstTimePopover.tsx— one-shot intro popover when the banner first appears for a user (localStorage flagrecce--snapshot-base-intro-seen). MirrorsSetupConnectionPopover.tsx.LineageGraphAdapter.tsx— addedtype: "metadata_updated"case tows.onmessage; invalidatescacheKeys.lineage(),cacheKeys.checks(),cacheKeys.runs().SessionStalenessinterface +isSessionBaseOutdated()selector +session_staleness?onServerInfoResult.Edge cases handled:
current_base_session_id IS NULL): refresh button disabled with tooltip "No shared base configured for this project.". Banner copy adjusts to make clear no shared base is configured.source_session_id IS NULLbuthas_isolated_base=True): banner suppressed (predicate gates onsource_session_id !== null).LineageGraphAdapterreconnect logic intact; UI re-syncs viaGET /sessions/{id}/info.session_staleness: optional field, frontend treats absence as "all null" — no banner shown, graceful degradation.Known limitations (slice 2):
StalenessBanner.tsx.Which issue(s) this PR fixes:
Resolves DRC-3311 (PR-B frontend). Pairs with DataRecce/recce-cloud-infra#1286 (PR-A backend). Slice 2 ships only when both PRs merge.
Special notes for your reviewer:
no shared basesub-case); design / Karen review still pending before merge.{type: "metadata_updated", data: {session_id}}. The handler is purely additive — existingcommand: "refresh"|"relaunch"|"broadcast"paths are untouched.queryClient.getQueryData(non-reactive). Switched touseQueryselector after self-review to remove reliance on ancestor re-renders (oneReact.memoboundary away from freezing).202 + {"task_id": null, "in_progress": true}. Frontend doesn't currently special-case this — both clicks show the spinner, both wait for the same WS event. Acceptable for this slice.Does this PR introduce a user-facing change?: