Skip to content

feat: preserve orch table state on back nav#713

Merged
ECWireless merged 7 commits into
mainfrom
349-preserve-explorer-state-on-back-navigation
Jul 7, 2026
Merged

feat: preserve orch table state on back nav#713
ECWireless merged 7 commits into
mainfrom
349-preserve-explorer-state-on-back-navigation

Conversation

@ECWireless

@ECWireless ECWireless commented Jun 28, 2026

Copy link
Copy Markdown
Collaborator

Description

This PR preserves orchestrator list browsing context when navigating into an orchestrator account and returning with Back. The home and /orchestrators lists now restore yield assumptions, sorting, pagination, and scroll position, with cached list data left in place so users can continue browsing where they left off.

Type of Change

  • feat: New feature
  • fix: Bug fix
  • docs: Documentation update
  • style: Code style/formatting changes (no logic changes)
  • refactor: Code refactoring (no behavior change)
  • perf: Performance improvement
  • test: Adding or updating tests
  • build: Build system or dependency changes
  • ci: CI/CD changes
  • chore: Other changes

Related Issue(s)

Closes: #349

Changes Made

  • Persisted orchestrator list state for home and /orchestrators.
  • Restored yield assumptions, sorting, pagination, and scroll on Back.
  • Added scroll preservation using route events, click capture, and session storage.

Testing

  • Tested locally
  • Added/updated tests
  • All tests passing

How to test (optional unless test is not trivial)

  • Change filters and page on home page
  • Select an orchestrator
  • Hit back button in browser
  • See that orchestrator table scroll position, page, and filters were preserved
  • Do the same of the /orchestrators page

Impact / Risk

Risk level: Low

Impacted areas: UI

User impact: user does not lose their orchestrator search preferences when they click on a particular orchestrator

Rollback plan: Vercel rollback, then manual git revert

Summary by CodeRabbit

  • New Features
    • Saved list preferences per view, including “Forecasted Yield Assumptions” controls plus table pagination/sorting for home, orchestrators, gateways, performance, and transactions.
    • Restored each list’s scroll position when navigating back.
  • Bug Fixes
    • Kept “Forecasted Yield Assumptions” fully synchronized with saved state; updated the time-horizon badge to “Time horizon:” and ensured the displayed value matches the selected time horizon.
    • Prevented table pagination/sorting from resetting unexpectedly during navigation.

Copilot AI review requested due to automatic review settings June 28, 2026 19:07
@ECWireless ECWireless linked an issue Jun 28, 2026 that may be closed by this pull request
@vercel

vercel Bot commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
explorer-arbitrum-one Ready Ready Preview, Comment Jul 6, 2026 9:33pm

Request Review

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR adds persistence for the orchestrator list browsing experience so that navigating into an orchestrator account and returning via Back restores list context (yield assumptions, sorting, pagination, and scroll position) for both the home page and /orchestrators.

Changes:

  • Adds a keyed persisted orchestrator-list state to the Zustand explorer store (home vs orchestrators).
  • Extends the shared Table wrapper to support disabling auto-resets and emitting pagination/sort state changes.
  • Implements scroll position capture/restore for orchestrator lists using sessionStorage + Next router events.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
pages/orchestrators.tsx Passes a stable listKey so /orchestrators persists/restores its own list state.
pages/index.tsx Passes listKey="home" so the homepage list persists independently from /orchestrators.
hooks/useExplorerStore.tsx Introduces persisted orchestrator list state and an updater API in the global store.
components/Table/index.tsx Adds table state persistence hooks (no auto-reset + onStateChange).
components/OrchestratorList/index.tsx Persists yield assumptions, table state, and scroll position keyed per list route.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread components/OrchestratorList/index.tsx Outdated
Comment thread components/OrchestratorList/index.tsx Outdated
Comment thread hooks/useExplorerStore.tsx Outdated
@coderabbitai

coderabbitai Bot commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds keyed persisted UI state for explorer lists, including pagination, sort order, scroll position, and orchestrator assumptions. Shared table and hook utilities emit and restore that state, and page entry points pass stable list identifiers and route paths.

Changes

Persist Explorer List State

Layer / File(s) Summary
Store types and keyed state
hooks/useExplorerStore.tsx, hooks/index.tsx
Adds OrchestratorListKey, OrchestratorListState, keyed orchestrator list storage, default state seeding, and the hook barrel export.
Table state change plumbing
components/Table/index.tsx
Adds persisted table state types, auto-reset controls, and an onStateChange callback that emits page and sort changes.
Persisted list state hook
hooks/usePersistedExplorerListState.ts
Adds scroll persistence, route-based restoration, and table state persistence for keyed lists.
List components use persisted state
components/OrchestratorList/index.tsx, components/GatewayList/index.tsx, components/PerformanceList/index.tsx, components/TransactionsList/index.tsx
Wires each list to persisted state, saves scroll on interaction, restores table state, and updates the orchestrator assumptions/time-horizon UI.
Page prop wiring
pages/index.tsx, pages/gateways.tsx, pages/orchestrators.tsx, pages/transactions.tsx
Passes stable listKey and routePath props from the page render paths into the list components.

Estimated code review effort: 4 (Complex) | ~60 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and describes the main change: preserving orchestrator table state on back navigation.
Linked Issues check ✅ Passed The PR restores list state, scroll, and persisted data on back navigation, matching #349's expected Explorer behavior.
Out of Scope Changes check ✅ Passed No clear out-of-scope code changes are present beyond the Explorer state-preservation work.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 349-preserve-explorer-state-on-back-navigation

Warning

Tools execution failed with the following error:

Failed to run tools: 13 INTERNAL: Received RST_STREAM with code 2 (Internal server error)


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.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@components/OrchestratorList/index.tsx`:
- Around line 189-218: The scroll restoration in restoreSavedScroll can still
fire before the page is tall enough, so a single RAF loop plus the 250ms timeout
may clamp the position near the top and stop retrying. Update restoreSavedScroll
in OrchestratorList to keep retrying until the document is tall enough for
scrollY (for example by checking document.documentElement.scrollHeight -
window.innerHeight >= scrollY) or by re-invoking restoration when the list
data/rows finish loading, while keeping a bounded timeout. Make sure the
retry/cancellation logic remains tied to restoreSavedScroll and its cleanup
return from useEffect.
- Around line 1348-1355: The delegation input in OrchestratorList’s onChange
handler only caps the upper bound, so invalid values like 0, negatives, or an
empty field can still enter state. Update the setPrinciple flow to clamp both
ends (respecting the minimum of 1 as well as maxSupplyTokens), or switch to
storing the input as a string and only commit a validated numeric value. Make
sure the fix is applied in the same onChange logic that currently calls
Number(e.target.value) and setPrinciple.

In `@hooks/useExplorerStore.tsx`:
- Around line 9-22: OrchestratorListState is duplicating the canonical ROI union
types instead of reusing the shared aliases from lib/roi.ts. Update
useExplorerStore.tsx to import and use ROIFactors, ROIInflationChange, and
ROITimeHorizon in OrchestratorListState so the store stays aligned with the
central ROI definitions and avoids type drift.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3f452562-ceda-4b9b-80d9-e1a54adae7d4

📥 Commits

Reviewing files that changed from the base of the PR and between 2c37a7e and f0747f4.

📒 Files selected for processing (5)
  • components/OrchestratorList/index.tsx
  • components/Table/index.tsx
  • hooks/useExplorerStore.tsx
  • pages/index.tsx
  • pages/orchestrators.tsx

Comment thread components/OrchestratorList/index.tsx Outdated
Comment thread components/OrchestratorList/index.tsx
Comment thread hooks/useExplorerStore.tsx
rickstaa
rickstaa previously approved these changes Jul 1, 2026

@rickstaa rickstaa left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@ECWireless thanks for this pull request. One comment about the caching frequency. Also validated it works:

image

@ECWireless

Copy link
Copy Markdown
Collaborator Author

@ECWireless thanks for this pull request. One comment about the caching frequency.

@rickstaa it revalidates if more than 10 seconds have passed since the last user

@rickstaa
rickstaa self-requested a review July 1, 2026 13:05

@rickstaa rickstaa left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@ECWireless would it be possible to generalise to all tables. The gateway and ticket tables also benefit of keeping state.

@ECWireless

Copy link
Copy Markdown
Collaborator Author

@ECWireless would it be possible to generalise to all tables. The gateway and ticket tables also benefit of keeping state.

Added to the Performance Leaderboard, Transactions, and Gateways tables in e41452b. The only one I skipped was the Treasury Votes table, since the dynamic routing makes it a bit more complex.

@ECWireless
ECWireless requested a review from rickstaa July 3, 2026 23:13

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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 `@hooks/usePersistedExplorerListState.ts`:
- Around line 29-203: Add JSDoc to usePersistedExplorerListState and the helper
functions getScrollStorageKey, readStoredScrollY, and writeStoredScrollY.
Document the hook’s purpose, its params (listKey, routePath, optional
persistedState and setPersistedState overrides), and the returned shape
(handleTableStateChange, persistedState, saveCurrentScroll), plus brief one-line
intent for the scroll storage helpers so the restore/storage flow is clear.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: bc8b71e7-1bde-411c-8780-37a5130507d1

📥 Commits

Reviewing files that changed from the base of the PR and between f0747f4 and e41452b.

📒 Files selected for processing (10)
  • components/GatewayList/index.tsx
  • components/OrchestratorList/index.tsx
  • components/PerformanceList/index.tsx
  • components/TransactionsList/index.tsx
  • hooks/index.tsx
  • hooks/useExplorerStore.tsx
  • hooks/usePersistedExplorerListState.ts
  • pages/gateways.tsx
  • pages/index.tsx
  • pages/transactions.tsx

Comment thread hooks/usePersistedExplorerListState.ts
rickstaa
rickstaa previously approved these changes Jul 6, 2026

@rickstaa rickstaa left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@ECWireless very clean PR thanks. I left some minor comments. If you can respond or handle those and update the squash commit to include the fact that this now applies to all tables we can merge. 🙏🏻

Comment thread components/TransactionsList/index.tsx Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
components/TransactionsList/index.tsx (1)

567-582: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Clamp the restored page index before passing it to Table

  • Problem: persistedState.pageIndex is reused as-is with autoResetPage={false}, so a shorter events set can mount on an out-of-range page and render an empty table.
  • Why it matters: Back navigation or data changes can leave users stranded on a blank transactions page with no automatic recovery.
  • Suggested fix: Clamp the restored pageIndex to the current page count, or reset it when the current events length no longer supports the saved page.
🤖 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 `@components/TransactionsList/index.tsx` around lines 567 - 582, The restored
page index in TransactionsList is being passed directly into Table via the
initialState pageIndex, which can leave the table on an invalid page when the
events set is shorter. Update the TransactionsList component to clamp
persistedState.pageIndex against the current page count derived from events and
pageSize before building the Table initialState, or reset it when the saved page
is no longer valid. Keep the fix localized around the initialState passed to
Table and the related persistedState handling so back navigation and data
refreshes always land on a valid page.
🤖 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.

Outside diff comments:
In `@components/TransactionsList/index.tsx`:
- Around line 567-582: The restored page index in TransactionsList is being
passed directly into Table via the initialState pageIndex, which can leave the
table on an invalid page when the events set is shorter. Update the
TransactionsList component to clamp persistedState.pageIndex against the current
page count derived from events and pageSize before building the Table
initialState, or reset it when the saved page is no longer valid. Keep the fix
localized around the initialState passed to Table and the related persistedState
handling so back navigation and data refreshes always land on a valid page.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0aeb3f21-14b3-4af8-a705-e208fc5b4f89

📥 Commits

Reviewing files that changed from the base of the PR and between 4a1ff7e and 1358fb0.

📒 Files selected for processing (1)
  • components/TransactionsList/index.tsx

Comment thread components/PerformanceList/index.tsx Outdated
Comment thread components/GatewayList/index.tsx Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (2)
components/GatewayList/index.tsx (1)

310-341: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Missing bounds check on restored pageIndex.

persistedState.pageIndex is passed straight into initialState.pageIndex with no clamp against the current rows.length. If the gateway list is shorter on return (filtered/updated data) than when the page index was saved, react-table will render an out-of-range page (empty table) at mount, since initialState is only applied once. TransactionsList in this same PR already solves this by clamping (Math.min(persistedState.pageIndex, pageCount - 1)) — this file should do the same.

Problem: No bounds check on restored pageIndex against current row count.
Why it matters: Back-navigation with a shrunk gateway list can show a blank table instead of the intended restored view — directly undermines this PR's goal.
Suggested fix: Clamp pageIndex the same way TransactionsList does.

🐛 Proposed fix
   if (!rows?.length) {
     ...
   }

+  const pageCount = Math.max(1, Math.ceil(rows.length / pageSize));
+  const pageIndex = Math.min(persistedState.pageIndex, pageCount - 1);
+
   return (
     <Box onClickCapture={saveCurrentScroll}>
       <Table
         data={rows}
         columns={columns}
         autoResetPage={false}
         autoResetSortBy={false}
         onStateChange={handleTableStateChange}
         initialState={{
-          pageIndex: persistedState.pageIndex,
+          pageIndex,
           pageSize,
           sortBy: persistedState.sortBy.length
             ? (persistedState.sortBy as SortingRule<GatewayRow>[])
             : DEFAULT_SORT_BY,
         }}
       />
     </Box>
   );
🤖 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 `@components/GatewayList/index.tsx` around lines 310 - 341, The restored page
index in GatewayList should be clamped before being passed to Table
initialState, since persistedState.pageIndex can be out of range when
rows.length has changed. Update the GatewayList component to compute a bounded
pageIndex the same way TransactionsList does, using the current row count/page
count, and pass that safe value into initialState.pageIndex so react-table
mounts on a valid page.
components/PerformanceList/index.tsx (1)

51-80: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Same missing pageIndex clamp as GatewayList.

initialState.pageIndex uses persistedState.pageIndex directly, unbounded against mergedData.length. Since orchestratorIds/region/pipeline/model can change the effective row count between sessions (or on remount via key={listKey}), a stale, larger pageIndex will render an empty leaderboard page at mount instead of resuming correctly.

Problem: No clamp of restored pageIndex to the current data size.
Why it matters: Same blank-table regression risk described for GatewayList, here on the leaderboard.
Suggested fix: Clamp using mergedData.length (computed above initialState, since it's already declared later in the file — reorder if needed).

🐛 Proposed fix
+  const mergedData = useMemo(
+    () =>
+      orchestratorIds.map((o) => ({ ...o, ...performanceMetrics?.[o?.id] })),
+    [performanceMetrics, orchestratorIds]
+  );
+
+  const pageCount = Math.max(1, Math.ceil(mergedData.length / pageSize));
+  const clampedPageIndex = Math.min(persistedState.pageIndex, pageCount - 1);
+
   const initialState = {
-    pageIndex: persistedState.pageIndex,
+    pageIndex: clampedPageIndex,
     pageSize: pageSize,
     sortBy: persistedState.sortBy.length
       ? persistedState.sortBy
       : DEFAULT_SORT_BY,
     ...
   };
-
-  const mergedData = useMemo(
-    () =>
-      orchestratorIds.map((o) => ({ ...o, ...performanceMetrics?.[o?.id] })),
-    [performanceMetrics, orchestratorIds]
-  );
🤖 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 `@components/PerformanceList/index.tsx` around lines 51 - 80, The restored page
index in PerformanceList is used without bounds checking, which can leave the
leaderboard on an empty page when the data size changes. Update the initialState
setup in PerformanceList to clamp persistedState.pageIndex against the current
mergedData.length, similar to GatewayList, and reorder the mergedData
computation if needed so the clamp can be applied before initialState is built.
🤖 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.

Outside diff comments:
In `@components/GatewayList/index.tsx`:
- Around line 310-341: The restored page index in GatewayList should be clamped
before being passed to Table initialState, since persistedState.pageIndex can be
out of range when rows.length has changed. Update the GatewayList component to
compute a bounded pageIndex the same way TransactionsList does, using the
current row count/page count, and pass that safe value into
initialState.pageIndex so react-table mounts on a valid page.

In `@components/PerformanceList/index.tsx`:
- Around line 51-80: The restored page index in PerformanceList is used without
bounds checking, which can leave the leaderboard on an empty page when the data
size changes. Update the initialState setup in PerformanceList to clamp
persistedState.pageIndex against the current mergedData.length, similar to
GatewayList, and reorder the mergedData computation if needed so the clamp can
be applied before initialState is built.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0cb26b1e-9a60-4570-a9ae-32d8f8f286e4

📥 Commits

Reviewing files that changed from the base of the PR and between 1358fb0 and 8955564.

📒 Files selected for processing (3)
  • components/GatewayList/index.tsx
  • components/PerformanceList/index.tsx
  • components/TransactionsList/index.tsx

@ECWireless
ECWireless requested a review from rickstaa July 6, 2026 22:08

@rickstaa rickstaa left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for handling my commnents good to merge. 🔥

@ECWireless
ECWireless merged commit dc7d90f into main Jul 7, 2026
9 checks passed
@ECWireless
ECWireless deleted the 349-preserve-explorer-state-on-back-navigation branch July 7, 2026 18:26
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.

Preserve Explorer State on Back Navigation

3 participants