Skip to content

feat(ui): staleness banner and refresh-base action#1366

Merged
wcchang1115 merged 5 commits into
mainfrom
feature/drc-3311-slice-2
May 12, 2026
Merged

feat(ui): staleness banner and refresh-base action#1366
wcchang1115 merged 5 commits into
mainfrom
feature/drc-3311-slice-2

Conversation

@even-wei
Copy link
Copy Markdown
Contributor

@even-wei even-wei commented May 10, 2026

PR checklist

  • Ensure you have added or ran the appropriate tests for your PR.
  • DCO signed

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:

  1. POSTs /sessions/{id}/refresh-base (backend re-clones the latest shared base into this session's S3 prefix).
  2. Spinner shows on the button.
  3. Backend emits metadata_updated over WebSocket on completion.
  4. Frontend invalidates the lineage / checks / runs queries; banner clears as source_session_updated_at catches up to current_base_updated_at.
  5. A "Base refreshed" toast appears.

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 (MUI Box + role="status") mounted in the shared app shell (MainLayout.tsx) above TopBarOss, so the banner appears on the lineage, query, and checks views. Persistent (not dismissible — correctness-critical). Cloud-mode gated via useLineageGraphContext().cloudMode. Subscribes reactively via useQuery({ queryKey: cacheKeys.lineage(), select: d => d.session_staleness }) — re-renders on cache invalidation without parent re-render dependency. Banner copy branches when current_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 flag recce--snapshot-base-intro-seen). Mirrors SetupConnectionPopover.tsx.
  • LineageGraphAdapter.tsx — added type: "metadata_updated" case to ws.onmessage; invalidates cacheKeys.lineage(), cacheKeys.checks(), cacheKeys.runs().
  • SessionStaleness interface + isSessionBaseOutdated() selector + session_staleness? on ServerInfoResult.

Edge cases handled:

  • Project has no shared base (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.
  • Manual-standalone-base session (source_session_id IS NULL but has_isolated_base=True): banner suppressed (predicate gates on source_session_id !== null).
  • WebSocket dropped: existing LineageGraphAdapter reconnect logic intact; UI re-syncs via GET /sessions/{id}/info.
  • OSS mode: cloud-mode gate keeps the banner invisible; no behavior change.
  • Backend not yet shipping session_staleness: optional field, frontend treats absence as "all null" — no banner shown, graceful degradation.
  • Slow-then-failing POST that races the 30s timeout: the catch path now short-circuits when the timeout has already fired, preventing a double error toast.

Known limitations (slice 2):

  • If the user navigates away mid-refresh, the success-toast transition guard is component-local and does not survive remount. The cache state is still correct (banner hidden, no error) — the user just misses the explicit confirmation toast. Documented inline in 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:

  • Banner copy is from the spec verbatim (with one branch added for the no shared base sub-case); design / Karen review still pending before merge.
  • WS contract: backend emits {type: "metadata_updated", data: {session_id}}. The handler is purely additive — existing command: "refresh"|"relaunch"|"broadcast" paths are untouched.
  • Reactive subscription: previous draft used queryClient.getQueryData (non-reactive). Switched to useQuery selector after self-review to remove reliance on ancestor re-renders (one React.memo boundary away from freezing).
  • Concurrent refresh (one user double-clicks): backend short-circuits the second call with 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?:

Cloud mode: when a PR session's snapshotted base data has drifted from your project's current shared production base, a yellow banner appears on the lineage, query, and checks views with a "Refresh base" button. Clicking it re-pulls the latest shared base; the banner clears and a confirmation toast appears once the refresh completes (typically within 30 seconds).

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>
@even-wei even-wei self-assigned this May 10, 2026
@even-wei
Copy link
Copy Markdown
Contributor Author

Code Review: PR #1366

Reviewed commit: a5e0b966
Files reviewed: 10 (2 new components, 1 new test file, 7 modified)
Categories: Frontend (UI components, hooks, API types, tests)
Passes run: A, B, C, D, E, F, H

Validation Results

Pass A: Correctness & Logic — PASS

  • isSessionBaseOutdated() (info.ts:179-185) — verified all combinations: legacy (source null), same session same timestamp, same session different timestamp (re-upload), different sessions, current_base removed (current null). All produce the correct boolean.
  • 30s timeout / WS-arrives-during-await race: success path is reached via the outdated true → false useEffect, which clears the pending timeout — verified the state machine in StalenessBanner.tsx:60-76 and :96-105.
  • Strict-mode double-render of effects: no spurious toasts (the prevOutdated.current ref-based transition guard is correct).

Pass B: Security — PASS

No new secrets, no PII added to logs/messages, no user input in URLs. The refresh endpoint takes no body; auth flows through the existing apiClient middleware.

Pass C: Cross-Reference Consistency — PASS (with verification done end-to-end against backend PR #1286)

  • Frontend SessionStaleness interface matches backend get_session_staleness() shape exactly (4 fields, all str | None).
  • WebSocket payload contract: backend notify_metadata_updated() (api_server/core/metadata_notifier.py) emits {"type": "metadata_updated", "data": {"session_id": "..."}} — verified against the frontend union type at LineageGraphAdapter.tsx:79-82.
  • apiClient.post("/api/refresh-base") resolves correctly in cloud mode: the ApiContext rewrites /api/api/v2/sessions/<session_id>, yielding POST /api/v2/sessions/<session_id>/refresh-base. Confirmed the backend mounts sessions_router at /api/v2 (api_server/application.py:278,302) and exposes /sessions/{session_id}/refresh-base.
  • WS URL ${apiPrefix}/ws/api/v2/sessions/<session_id>/ws matches backend @recce_web_router.websocket("/sessions/{session_id}/ws").
  • Timestamps are serialized as UTC ISO 8601 by _iso() on both source and current sides → frontend's !== string comparison is safe.

Pass D: Error Handling & Edge Cases — PASS (one NOTE)

  • Empty/null staleness: StalenessBanner.tsx:88 (if (!staleness) return null) handles the legacy / pre-DRC-3309 case.
  • noSharedBase (current_base_session_id null but source non-null): refresh button disabled, tooltip shown. NOTE: the banner copy "Production data has changed since this PR's base was captured. Comparisons may not reflect current state." is technically inaccurate in this sub-case — the base was removed, not changed. Edge case (project deleted shared base after snapshot), but worth a copy revisit during design review.
  • Double error toast on slow-then-failing POST: if apiClient.post rejects AFTER the 30s timeout has already fired, the catch block fires a second toaster.error (the if (timeoutRef.current) guard at line 114 protects only the clear, not the toast). Minor — flag as NOTE.

Pass E: Test Coverage & Quality — PASS

  • New StalenessBanner.test.tsx: 12 tests covering visibility predicates, refresh-button states, 30s-timeout error path, success toast on cache transition, and the reactive-subscription behavior (H1 fix). Mocks match real signatures.
  • LineageGraphAdapter.test.tsx: new test exercises the metadata_updated WS path with a real MockWebSocket and a spied invalidateQueries — catches regressions on any of the three invalidated keys.
  • Component-test mock updates (LineageView.component.test.tsx) correctly add lineage to cacheKeys mock and stub useQuery so the new banner doesn't break the snapshot.
  • All 3707 tests pass; type-check clean; biome lint clean.

Pass F: Diff-Specific Checks — PASS (one ISSUE, one NOTE)

  • ISSUE — release-note / PR description scope claim: PR body and release-note state the banner appears on "lineage, query, and checks views" but <StalenessBanner /> is mounted only in LineageViewOss.tsx:1397. The query and checks routes (app/query/page.tsx, app/checks/page.tsx) do not render LineageViewOss, so users on those pages will see neither the staleness banner nor a refresh-base entry point. Either (a) mount it on a shared shell that wraps all three views or (b) fix the release-note and PR description before merge. As-is, the release note misrepresents user-visible behavior.
  • NOTE — placement vs description: PR body says the banner sits "between LineageViewTopBar and SetupConnectionBanner"; actual placement at LineageViewOss.tsx:1397 is above LineageViewTopBar (outside the interactive && block). Functional but the description is wrong.
  • NOTE — unused import: LineageGraphAdapter.test.tsx:16 imports useQueryClient but never uses it. Biome's current config doesn't fail on unused imports at error level so CI passes, but it's still dead.
  • NOTE — storage key naming inconsistency: every existing LOCAL_STORAGE_KEYS entry uses the recce- (hyphen, double-dashed) prefix template; the new snapshotBaseIntroSeen: "recce_snapshot_base_intro_seen" (storageKeys.ts:9) uses underscores and no template. Won't break anything but pollutes the namespace.

Pass H: Async/Concurrency — PASS (one NOTE)

  • void handleRefresh() at StalenessBanner.tsx:155 correctly fires-and-forgets — internal try/catch handles rejection.
  • 30s timeout cleanup on unmount: cleanup effect at lines 79-85 clears the pending timer.
  • NOTE — lost success toast on navigation mid-refresh: if the user clicks Refresh, then navigates away before the WS event arrives, the new mount's prevOutdated.current = null (not true), so when staleness flips false on the next render the transition-guard at line 61 is false and the success toast is never shown. The cache state is correct (banner hidden, no error), the user just gets no confirmation. Acceptable for slice 2.

Verification Results

  • pnpm lint — clean
  • pnpm type:check — clean
  • pnpm test — 3707 passed, 5 skipped (5 skips pre-existing, unrelated)
  • pnpm run build — clean

Verdict: NO-GO

Issues

  1. Scope mismatch between release note and implementation. PR description and release note advertise the banner on lineage, query, and checks views, but <StalenessBanner /> is mounted only in LineageViewOss.tsx:1397. Either mount it on the shared shell so all three views show it, or correct the release note and PR description before merge so users / docs / support are not surprised.

Notes

  1. Banner copy "Production data has changed since this PR's base was captured" is misleading when current_base_session_id is null (project removed its shared base) — consider a distinct message for that sub-case.
  2. PR description states placement is "between LineageViewTopBar and SetupConnectionBanner"; actual placement is above the top bar (outside the interactive && block). Doc-vs-code drift.
  3. Unused import useQueryClient in LineageGraphAdapter.test.tsx:16 — biome doesn't flag at error level but should be removed.
  4. LOCAL_STORAGE_KEYS.snapshotBaseIntroSeen uses underscore naming ("recce_snapshot_base_intro_seen") where every other entry uses the hyphenated recce- template. Minor style inconsistency.
  5. Lost success-toast UX if the user navigates away mid-refresh — the prevOutdated.current === true transition guard misses across remounts. Acceptable for this slice.
  6. Slow-then-failing POST can fire two error toasts (30s timeout fires first, then catch fires a second). Guard the toast at StalenessBanner.tsx:119 behind the same if (timeoutRef.current) check used for the clearTimeout.

What I could not verify

  • Manual browser test: PR body doesn't mention how the staleness flow was validated end-to-end against the paired backend PR feat(telemetry): add CLI onboarding funnel events #1286 (e.g. local cloud-mode session against a staging backend, or recorded screenshots). The CLAUDE.md convention is to build and run recce server before declaring frontend changes complete. Worth surfacing the test plan: what manual scenarios were exercised (banner appears, refresh button click → spinner → WS → banner clears → toast), and what dataset / environment was used?
  • WS auth handshake in cloud mode: the backend's WS endpoint expects authentication via the first message; the OSS-side useLineageWatcher only sends "ping". This pre-existed and isn't part of this PR, but it means the metadata_updated path is unverified through the cloud-mode WS chain without slice-1 plumbing that this PR doesn't touch.

What I looked for and did not find

  • API contract drift between frontend and backend PR feat(telemetry): add CLI onboarding funnel events #1286 — verified end-to-end (endpoint path, WS path, payload shapes, timestamp serialization).
  • Type/mock drift between the new test and the real apiClient/getServerInfo signatures.
  • Race conditions in the timeout/spinner/WS-arrival state machine — the transition-guard via ref is sound.
  • Memory leaks: setTimeout cleared on unmount and on staleness clear.
  • React Query key collisions or duplicate fetches: confirmed the banner's useQuery is deduped against LineageGraphAdapter's observer by shared key.

Copy link
Copy Markdown
Contributor Author

@even-wei even-wei left a comment

Choose a reason for hiding this comment

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

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>
@even-wei
Copy link
Copy Markdown
Contributor Author

Addressed in commit 84b24189.

Decision on the ISSUE (release-note scope): I picked option (a) widen mount points rather than narrow the release-note. Rationale: MainLayout.tsx already wraps all three views (lineage / query / checks) with <TopBar /> + <NavBar />, and RecceContextProvider (which includes LineageGraphAdapter) is mounted above it — so the cacheKeys.lineage() query that <StalenessBanner /> subscribes to is always populated regardless of route. Moving the banner from LineageViewOss.tsx:1397 to MainLayout.tsx (above <TopBar />) is a one-line move with no extra plumbing, and it makes the user-visible behavior match the release-note. Cost was lower than splitting the release-note and chasing a follow-up issue. Verified all 3707 tests still pass.

Per-finding resolution:

# Finding Resolution
ISSUE Release-note scope mismatch (banner only on lineage) Mounted <StalenessBanner /> in MainLayout.tsx so it appears on lineage, query, and checks. Removed from LineageViewOss.tsx.
NOTE 1 Banner copy misleading when current_base_session_id is null Branched the copy: when noSharedBase is true, the message now reads "This PR's base snapshot may be out of date, and no shared base is currently configured for this project. Comparisons may not reflect current state."
NOTE 2 Placement description drift (PR body said "between topbar and setup banner") PR body updated to describe the new placement: above <TopBar /> in the shared shell.
NOTE 3 Unused useQueryClient import in LineageGraphAdapter.test.tsx Removed.
NOTE 4 Storage-key naming inconsistency Renamed to `${localPrefix}-snapshot-base-intro-seen` so it matches the existing recce- template (note this produces recce--snapshot-base-intro-seen like the other entries do).
NOTE 5 Lost success-toast on navigate-away mid-refresh Accepted limitation per the reviewer's option; documented inline in StalenessBanner.tsx with a comment explaining why the ref-based transition guard doesn't survive remount and that cache state remains correct.
NOTE 6 Double error toast on slow-then-failing POST Guarded the catch path: if timeoutRef.current is already null (the 30s timeout fired and already toasted), the catch returns early. Otherwise it clears the pending timer and toasts as before.

Verification:

  • pnpm lint clean
  • pnpm type:check clean
  • pnpm test — 3707 passed, 5 skipped (5 pre-existing unrelated)
  • pnpm run build clean

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.

@even-wei even-wei requested a review from wcchang1115 May 11, 2026 03:44
@wcchang1115
Copy link
Copy Markdown
Collaborator

wcchang1115 commented May 11, 2026

Code Review: PR #1366 — Incremental Review

Reviewed commit: 15200dfd (incremental — addresses prior review comments)
Scope: Verify the 4 findings from the prior review are resolved, and no new issues are introduced.
Verification on 15200dfd: pnpm lint ✓ · pnpm type:check ✓ · pnpm test (3708 passed, 5 pre-existing skips) ✓ · pnpm run build

Prior findings — status

# Finding Status
ISSUE useRefanchorEl won't reliably open the FirstTimePopover Resolved. Converted to state-backed callback ref (StalenessBanner.tsx:40,128,167). New regression test at StalenessBanner.test.tsx:355-369 mounts the banner end-to-end and waits for popover text — this would fail under the old useRef pattern.
NOTE 1 Duplicate useQuery for cacheKeys.lineage() in banner and adapter Resolved. Extracted useServerInfo() at js/packages/ui/src/hooks/useServerInfo.ts. Both consumers go through it: LineageGraphAdapter.tsx:284 (no options, full result) and StalenessBanner.tsx:45-47 (select for session_staleness).
NOTE 2 Raw apiClient.post("/api/refresh-base") bypasses api/ helper convention Resolved. Added refreshSessionBase(client) at api/info.ts:281-283, re-exported from api/index.ts:89. Banner now calls the typed helper at StalenessBanner.tsx:107.
NOTE 3 Banner mounted above the brand TopBar ⏸️ Deferred (acknowledged in author response). Design pass still pending; will be revisited with the final visual treatment. Not blocking.

New code — adversarial pass

Pass A — Correctness. Callback-ref pattern is correct: useState setter is referentially stable, so React does not re-attach the ref each render; the setter triggers a re-render when the DOM commits, which is exactly what FirstTimePopover's anchorEl needs. The outdated check at StalenessBanner.tsx:56 still handles staleness == null (covers both undefined from select-on-undefined and null).

Pass C — Cross-reference. useServerInfo's typed Omit<UseQueryOptions, "queryKey" | "queryFn"> prevents callers from overriding the canonical key/fn at compile time. StalenessBanner.tsx:45's select: (d) => d.session_staleness correctly narrows TSelected to SessionStaleness | undefined. Test mock at StalenessBanner.test.tsx:49-65 returns a real useQuery wired to the same cacheKeys.lineage() key — preserving the H1 reactive-subscription coverage (test at lines 372-393 still verifies cache-driven re-renders without parent re-render).

Pass D — Error handling. handleRefresh race conditions unchanged from prior review (which was clean): WS-arrives-before-await, timeout-fired-before-catch, and unmount-during-pending all behave correctly.

Pass E — Test coverage. The new regression test exercises the actual bug:

  • Old code: bannerRef.current set after commit, no re-render → popover sees anchorEl=null → stays closed → waitFor would time out.
  • New code: setBannerEl(div) triggers re-render → popover sees real anchor → opens → test passes.
    This is a non-vacuous regression test.

Notes (non-blocking)

  1. Mock surface narrownessStalenessBanner.test.tsx:49-65 mocks useServerInfo accepting only { select? }. If a future banner change passes staleTime/enabled/retry, the mock will silently ignore them and unit tests will diverge from integration behavior. Acceptable for current call sites; worth tightening if more options get added.

  2. useApiConfig called twice in StalenessBanner — once directly at line 33 for refreshSessionBase, once inside useServerInfo. Harmless (context reads are cheap) and intentional given the helper's signature. No action.

Verdict: GO

All four prior findings are resolved or appropriately deferred. The fix commit introduces no new BLOCKERs or ISSUEs. Approving.

Copy link
Copy Markdown
Collaborator

@wcchang1115 wcchang1115 left a comment

Choose a reason for hiding this comment

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

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>
@even-wei
Copy link
Copy Markdown
Contributor Author

@wcchang1115 Addressed in 15200dfd. Thanks for the careful read — the popover bug was a real one and the unit test was masking it.

Per-finding resolution:

# Finding Resolution
ISSUE useRefanchorEl won't reliably open the FirstTimePopover Converted to state-backed ref via useState/callback ref (StalenessBanner.tsx:40,128,167). Added a regression test that mounts FirstTimePopover through the real banner anchor path (the previous unit test passed a pre-built DOM node, which bypassed the issue you identified).
NOTE 1 Duplicate useQuery with same key in StalenessBanner and LineageGraphAdapter Extracted shared useServerInfo() hook at js/packages/ui/src/hooks/useServerInfo.ts. Both call sites now go through it, so any future staleTime/enabled/retry options stay in lockstep. LineageGraphAdapter still uses the full UseQueryResult (needs error/isLoading); StalenessBanner uses the select overload to scope re-renders to session_staleness.
NOTE 2 Raw apiClient.post("/api/refresh-base") bypasses the api/ helper convention Added refreshSessionBase(client) to api/info.ts and exported from api/index.ts. Banner now calls the typed helper.
NOTE 3 Banner mounted above the brand TopBar Deferring intentionally for this slice. Design pass for the staleness banner is still pending (flagged in PR body); placement will be revisited once the final visual treatment is approved, so as not to churn it twice. Tracking this with the design review rather than landing a layout change here.

Verification on 15200dfd:

  • pnpm lint clean
  • pnpm type:check clean
  • pnpm test — 3708 passed, 5 skipped (the 5 are pre-existing unrelated skips)
  • pnpm run build clean

Backend contract against PR DataRecce/recce-cloud-infra#1286 is unchanged: same endpoint path (/api/refresh-base → resolves to /api/v2/sessions/<id>/refresh-base via ApiContext rewrite), same WS payload shape (metadata_updated), same SessionStaleness response type. Only the call-site wrapping changed.

Copy link
Copy Markdown
Collaborator

@wcchang1115 wcchang1115 left a comment

Choose a reason for hiding this comment

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

Claude Code Review (incremental on 15200dfd): all prior findings resolved, no new issues found. See updated review comment for details.

@wcchang1115 wcchang1115 merged commit 7cd0755 into main May 12, 2026
7 checks passed
@wcchang1115 wcchang1115 deleted the feature/drc-3311-slice-2 branch May 12, 2026 03:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants