Skip to content

Fold custom views into Fleet top-level tab nav (#398)#478

Open
flesher wants to merge 5 commits into
mainfrom
custom-views
Open

Fold custom views into Fleet top-level tab nav (#398)#478
flesher wants to merge 5 commits into
mainfrom
custom-views

Conversation

@flesher

@flesher flesher commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Summary

Folds saved views into the Fleet top-level tab nav: the secondary ViewsBar is gone, and a right-aligned "My views" dropdown lives in the same tab strip as Sites/Buildings/Racks/Miners. A saved view now captures the active section tab plus that tab's filter+sort state (and the racks grid/list toggle), so clicking a view restores its full destination — not just a set of filters on the current page. Built-in views (All miners / Needs attention / Offline) are dropped; user-created views from v1 migrate to tab: "miners" on read.

Resolves #398.

How it works

  1. Storage shape (v2). A SavedView is now { id, name, tab, searchParams, createdAt }. tab is a FleetTabId (sites | buildings | racks | miners). searchParams is a canonicalized URL query — only the keys whitelisted for that tab survive canonicalizeSearchParams, so transient or unrelated query state never leaks into a saved view. Persistence lives in localStorage under proto-fleet-miner-views:<username>. normalizeSavedViewsRecord reads v1 records (no tab) and assigns them tab: "miners", which preserves every existing user view.

  2. One hook, one component. useMinerViews is renamed to useFleetViews and is mounted once at the FleetLayout level. FleetViewTabs (replacing ViewsBar) renders the right-aligned trigger, the views dropdown, the kebab actions (Reset / Update / Rename / Delete), and the create/update ViewModal. It compares the URL's canonicalized params against the active view's saved params to compute the "dirty" state, and tints the trigger text text-intent-warning-50 when they diverge.

  3. Activating a view. Clicking a view computes a new URL via buildUrlForView: the view's canonical params are merged with any current URL params that are not in the active tab's filter/sort whitelist, then view=<id> is appended. FleetViewTabs then navigate()s to /fleet/<view.tab>?<params>. Because tab is part of the view, switching from Miners to a racks-tab view changes both the route and the filter set in one navigation.

  4. Per-tab filter context. The saved-view modal needs human-readable labels for building= / site= / group= / rack= filter ids, but FleetViewTabs lives above the tab pages. To solve this without prop-drilling, FleetLayout exposes publishViewFilterContext(...) on its outlet context. Each tab pushes its known sources (Miners publishes availableGroups + availableRacks; Racks publishes availableBuildings + availableSites). Each call is a partial merge — a tab can only overwrite the keys it owns — and the merged context flows into FleetViewTabs as filterContext. summarizeFilters then dispatches on tab to render the right entries.

  5. Racks-tab URL migration. Zone and issues filters and the grid/list segmented control on RacksPage were previously component-local state (zone/issues) and Zustand-only (display). They now live in the URL (?zone=, ?issues=, ?display=) so they can be captured into a view. The grid/list setter still mirrors into Zustand so the operator's preference survives navigating away from a ?display=... URL; the URL is the source of truth, Zustand is the fallback. An Include display mode toggle is added to ViewModal alongside the existing Include sort order toggle.

  6. Responsive placement. TabStrip gains an optional trailing slot. On desktop (laptop: breakpoint and up), FleetViewTabs renders into that slot, right-aligned across from the section tabs. On mobile it docks next to the <h1>Fleet</h1> heading. Both mounts use the same component; visibility-class gates keep only one interactive at a time.

Diagrams

Components and data flow

flowchart TB
  LS["localStorage<br/>(proto-fleet-miner-views:user)"]
  Hook["useFleetViews(username)"]
  Layout["FleetLayout<br/>(outlet context + viewFilterContext state)"]
  Tabs["FleetViewTabs<br/>(trigger / dropdown / kebab / modal)"]
  TabStrip["TabStrip<br/>(trailing slot)"]
  Miners["Fleet (Miners page)"]
  Racks["RacksPage"]
  URL["URL search params"]

  LS <--> Hook
  Hook --> Layout
  Layout --> Tabs
  Layout --> TabStrip
  TabStrip --> Tabs
  Layout -- "Outlet context" --> Miners
  Layout -- "Outlet context" --> Racks
  Miners -- "publishViewFilterContext (groups, racks)" --> Layout
  Racks -- "publishViewFilterContext (buildings, sites)" --> Layout
  Layout -- "filterContext" --> Tabs
  Tabs -- "navigate / buildUrlForView" --> URL
  URL --> Miners
  URL --> Racks
Loading

Activating a saved view

sequenceDiagram
  participant U as Operator
  participant V as FleetViewTabs
  participant H as useFleetViews
  participant R as Router

  U->>V: Click view row
  V->>H: findView(id)
  H-->>V: SavedView { tab, searchParams }
  V->>V: buildUrlForView(view, currentParams)
  Note right of V: keeps non-filter URL keys, drops other tabs' filter keys, appends view=id
  V->>R: navigate /fleet/{tab}?{params}
  R-->>U: Section tab + filters + sort + display restored
Loading

Dirty-state evaluation on every URL change

stateDiagram-v2
  [*] --> NoActiveView
  NoActiveView --> ActiveClean: navigate to ?view=id (params match)
  ActiveClean --> ActiveDirty: user mutates a filter or sort
  ActiveDirty --> ActiveClean: Reset (rewrite URL from saved params)
  ActiveDirty --> ActiveClean: Update view (overwrite saved params from URL)
  ActiveClean --> NoActiveView: clear view param
  ActiveDirty --> NoActiveView: clear view param
Loading

Areas of the code involved

Area What changed Why it matters for review
views/savedViews.ts Modified. Schema bumped to v2: SavedView gains tab. Per-tab whitelist (FILTER_AND_SORT_KEYS_BY_TAB) gates canonicalization. v1 records normalize to tab: "miners". buildUrlForView preserves unrelated URL keys. Storage migration + canonicalization rules — the contract every other change depends on.
views/useFleetViews.ts (was useMinerViews.ts) Renamed + reshaped. addUserView now requires tab. updateUserViewParams re-canonicalizes against the view's own tab. reorderUserViews added. Hook surface used by FleetViewTabs; rename ripples through imports.
views/viewSummary.ts Modified. summarizeFilters dispatches on tab (miners vs racks). Adds summarizeDisplay, diffDisplaySummaries, and FilterSummaryContext with availableBuildings / availableSites. Drives the labels rendered inside the view modal.
components/FleetViewTabs/FleetViewTabs.tsx New. Replaces ViewsBar. Renders trigger + views popover + kebab popover, computes dirty state, owns the create/update modal flow. The primary UI surface — most of the visible behavior lives here.
components/FleetViewTabs/ViewModal.tsx Moved + extended. Migrated from ViewsBar/. Adds Include display mode toggle (mirrors Include sort order) and renders DisplaySummary diffs. Capture/update UX; small but contract-relevant.
components/FleetLayout/FleetLayout.tsx Modified. Mounts useFleetViews and FleetViewTabs. Owns viewFilterContext state and the publishViewFilterContext callback. Mounts the views control twice (mobile heading row + desktop TabStrip trailing slot) gated by laptop: classes. New cross-tab coordination point; check the partial-merge semantics.
components/FleetLayout/outletContext.ts New. Defines FleetOutletContext, including publishViewFilterContext, plus a useOptionalFleetOutletContext for pages that also mount outside the Fleet shell. Extracted from FleetLayout.tsx so non-Fleet routes can import the type without pulling the shell in.
pages/RacksPage.tsx Modified. zone, issues, and display are now URL-backed. setRacksViewMode mirrors to both URL and Zustand. Publishes buildings + sites up via outlet context. Most behavioral change outside of the views UI — verify the URL-vs-Zustand precedence for display.
components/Fleet/Fleet.tsx Modified. Publishes availableGroups + availableRacks up via outlet context. Tiny but required for view-modal labels on the Miners tab.
components/MinerList/MinerList.tsx Modified. Drops the inline ViewsBar mount and the per-page useMinerViews call; views are now chrome-level. Removal only; no new behavior in this file.
shared/components/Tab/TabStrip.tsx Modified. Adds an optional trailing slot and a per-item active prop override. Shared component change; the active override lets a view tab read as active alongside its section tab.
components/ViewsBar/* Deleted. ViewsBar.tsx, ViewsBar.test.tsx, index.ts. Replaced wholesale by FleetViewTabs.
views/useMinerViews.ts + tests Renamed to useFleetViews.ts / useFleetViews.test.tsx. Same hook, broader scope.
Tests in views/ and FleetLayout/ Modified / added. savedViews.test.ts, viewSummary.test.ts, useFleetViews.test.tsx, plus minor updates to Fleet.test.tsx and FleetLayout.test.tsx. Coverage for the new tab-aware semantics and the v1→v2 migration.

Key technical decisions & trade-offs

  • Tab is part of the view rather than a separate "scope". Activating a view becomes a single navigation; the dropdown can list mixed-tab views without a second hierarchy. Chosen over keeping views scoped per-page and exposing a separate tab switcher.
  • v2 migration is a read-time normalization in normalizeSavedViewsRecord. No write-on-read step; no version-bumping ceremony. Existing v1 records keep working until their next mutation, at which point they are persisted as v2. Chosen over an explicit migration pass that would have to run somewhere on app boot.
  • Per-tab URL whitelists drive both canonicalization and buildUrlForView. Saved views can never accidentally capture unrelated URL state (e.g. a transient modal-open param), and switching tabs while activating a view strips the source tab's filter keys cleanly. Chosen over a deny-list approach.
  • Drop built-in views. All miners / Needs attention / Offline are no longer special-cased — they're just bookmarks the operator can recreate. Simpler model; the trade-off is that fresh users see an empty views dropdown.
  • URL ↔ Zustand mirror for display. URL is the source of truth so a saved view can dictate grid vs. list; Zustand keeps the operator's last choice across non-?display=... navigations. The alternative (URL-only) regressed the "remembered density" UX.
  • Twin-mount the views dropdown for mobile vs. desktop instead of restructuring TabStrip's layout. Keeps the responsive surface visually right but means the component is mounted twice in the DOM; only one is interactive at a time thanks to laptop: visibility classes.
  • TabStripItem.active prop override is a small but worth flagging — it lets two items in the same strip read as active. Used to keep a section tab and its scoped view both visibly active. Chosen over forking the component.

Testing & validation

  • just lint — clean.
  • client/: 2731 unit tests pass via npm test -- --run. Tests updated for tab-aware canonicalization, v1→v2 migration, the FleetViewTabs flows, and the new display-summary diffing.
  • tsc --noEmit — clean.
  • Not covered: E2E suite (slow + docker-compose) was not run. Manual smoke on /fleet/miners and /fleet/racks is recommended since the top nav was restructured — confirm that creating a view on each tab, activating it from the other tab, dirtying it, and resetting it all behave as described.

@flesher flesher requested a review from a team as a code owner June 16, 2026 21:42
Copilot AI review requested due to automatic review settings June 16, 2026 21:42
@github-actions github-actions Bot added javascript Pull requests that update javascript code client labels Jun 16, 2026
Replace the secondary ViewsBar with a right-aligned "My views" dropdown
in the Fleet top tab row. Saved views now capture the active section tab
(sites/buildings/racks/miners) alongside filters + sort, so clicking a
view restores its full state.

- Drop built-in views (`All miners` / `Needs attention` / `Offline`);
  v1 saved-view records migrate to `tab: "miners"` on read so existing
  users keep their views.
- Rename `useMinerViews` -> `useFleetViews`; `summarizeFilters` becomes
  tab-aware so racks-tab views can render building/site labels.
- Move racks-tab filters (`zone`, `issues`) and the grid/list segmented
  control (`display`) into URL params so views can capture them.
  Child pages publish filter metadata up via the FleetLayout outlet
  context.
- Add an "Include display mode" toggle in the view modal mirroring
  "Include sort order".
- Mobile docks the views dropdown beside the Fleet heading; desktop
  keeps it in the TabStrip's trailing slot.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

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 re-homes ProtoFleet “saved views” into the Fleet top-level navigation by replacing the old ViewsBar with a FleetViewTabs dropdown in FleetLayout. It also evolves the saved-view schema so each view captures the owning Fleet tab and expands URL-driven state (notably racks filters and display mode) so views can fully restore UI state.

Changes:

  • Introduces tab-scoped saved views (tab: FleetTabId) with v1 localStorage migration to tab="miners" and removes built-in views.
  • Adds FleetViewTabs (dropdown + kebab actions) and wires it into FleetLayout, removing ViewsBar from the miners list.
  • Moves racks zone/issues filters and the grid/list display mode into URL params and updates view summarization/modal UX accordingly.

Reviewed changes

Copilot reviewed 21 out of 21 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
client/src/shared/components/Tab/TabStrip.tsx Adds an active override to TabStripItem active styling.
client/src/protoFleet/features/fleetManagement/views/viewSummary.ts Adds tab-aware filter summaries and new display-mode summary/diff helpers.
client/src/protoFleet/features/fleetManagement/views/viewSummary.test.ts Updates filter-summary tests for tab-aware API and new label sources.
client/src/protoFleet/features/fleetManagement/views/useFleetViews.ts Renames/generalizes miner views hook to fleet views; persists tab with views.
client/src/protoFleet/features/fleetManagement/views/useFleetViews.test.tsx Updates hook tests for tab-scoped views and v1 migration behavior.
client/src/protoFleet/features/fleetManagement/views/savedViews.ts Bumps schema v2, adds FleetTabId, per-tab param whitelists, removes built-ins.
client/src/protoFleet/features/fleetManagement/views/savedViews.test.ts Updates tests for v2 schema, per-tab canonicalization, and migration.
client/src/protoFleet/features/fleetManagement/pages/RacksPage.tsx Drives zone/issues and display mode via URL so views can capture/restore them; publishes filter label context.
client/src/protoFleet/features/fleetManagement/components/ViewsBar/ViewsBar.tsx Deletes the legacy secondary saved-views tab bar.
client/src/protoFleet/features/fleetManagement/components/ViewsBar/ViewsBar.test.tsx Removes tests for the deleted ViewsBar.
client/src/protoFleet/features/fleetManagement/components/ViewsBar/index.ts Removes ViewsBar re-export.
client/src/protoFleet/features/fleetManagement/components/MinerList/MinerList.tsx Removes mounting of ViewsBar/views hook from miners page.
client/src/protoFleet/features/fleetManagement/components/FleetViewTabs/ViewModal.tsx Extends view modal to support “include display mode” with display diffs.
client/src/protoFleet/features/fleetManagement/components/FleetViewTabs/index.ts Exports FleetViewTabs.
client/src/protoFleet/features/fleetManagement/components/FleetViewTabs/FleetViewTabs.tsx Adds the new dropdown-based views selector + kebab actions.
client/src/protoFleet/features/fleetManagement/components/FleetLayout/outletContext.ts Adds a publish API for child tabs to provide filter label metadata; adds optional outlet hook.
client/src/protoFleet/features/fleetManagement/components/FleetLayout/index.ts Re-exports useOptionalFleetOutletContext.
client/src/protoFleet/features/fleetManagement/components/FleetLayout/FleetLayout.tsx Wires FleetViewTabs into the top nav (desktop trailing slot + mobile header).
client/src/protoFleet/features/fleetManagement/components/FleetLayout/FleetLayout.test.tsx Updates store mock to provide useUsername.
client/src/protoFleet/features/fleetManagement/components/Fleet/Fleet.tsx Publishes group/rack label sources up to FleetLayout for modal summaries.
client/src/protoFleet/features/fleetManagement/components/Fleet/Fleet.test.tsx Updates FleetLayout outlet context mock with publishViewFilterContext.

Comment thread client/src/shared/components/Tab/TabStrip.tsx
Comment thread client/src/protoFleet/features/fleetManagement/views/savedViews.ts Outdated
Comment thread client/src/protoFleet/features/fleetManagement/views/viewSummary.ts Outdated
Comment thread client/src/protoFleet/features/fleetManagement/pages/RacksPage.tsx Outdated
Comment thread client/src/protoFleet/features/fleetManagement/pages/RacksPage.tsx Outdated
Comment thread client/src/protoFleet/features/fleetManagement/views/viewSummary.ts Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c49290f4bd

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread client/src/protoFleet/features/fleetManagement/pages/RacksPage.tsx Outdated
Comment thread client/src/protoFleet/features/fleetManagement/views/savedViews.ts
@github-actions

github-actions Bot commented Jun 16, 2026

Copy link
Copy Markdown

🔐 Codex Security Review

Note: This is an automated security-focused code review generated by Codex.
It should be used as a supplementary check alongside human review.
False positives are possible - use your judgment.

Scope summary

  • Reviewed pull request diff only (fa17796711fbf4bb2c5a2fb029367a9654e1efa6...c5c6fe510da84740ca19652f317a3242d28abce8, exact PR three-dot diff)
  • Model: gpt-5.5

💡 Click "edited" above to see previous reviews for this PR.


Review Summary

Overall Risk: NONE

Findings

No findings identified in the latest diff.

Notes

Reviewed .git/codex-review.diff as the authoritative scope. The PR is limited to ProtoFleet client saved-view/list UI changes and related tests. I did not find changes touching auth/JWT handling, SQL, gRPC handlers, shell execution, discovery, plugins, infrastructure, Rust, protobuf generation, or pool/wallet/stratum configuration. React-rendered device/view labels remain escaped; no dangerous HTML rendering or token exposure was introduced in the changed hunks.


Generated by Codex Security Review |
Triggered by: @flesher |
Review workflow run

The minersFiltersViews spec was written against the old per-tab ViewsBar
UI. This PR's switch to the FleetViewTabs dropdown left every saved-view
helper pointing at obsolete testids, causing all 5 saved-view tests to
timeout on `locator.click`.

Rewrites the page-object helpers against the new selectors
(`fleet-view-tabs-{trigger,views-popover,kebab,kebab-popover,
delete-dialog}`, action rows, and the empty-state `+ New view` button),
adds `clickClearActiveView` for the "no active view" flow, and uses
`:visible` filters so the mobile + desktop twin-mounts don't trip strict
mode.

Spec changes:
- Replace the now-removed "All miners" built-in view switch with
  `clickClearActiveView` in the update test.
- Drop the "deleted from the views bar" test: the new UI only exposes
  delete via the active-view kebab, so the inactive-delete path no
  longer exists. The active-delete path is already covered.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 203a04ba90

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread client/src/protoFleet/features/fleetManagement/pages/RacksPage.tsx Outdated
Substantive fixes:

- RacksPage: drop comma-split on zone/issues URL parsing — zone labels
  may legitimately contain commas (e.g. "DC1, Row A"). Read uses
  getAll() only; writes use repeated keys.
- RacksPage: remove the effect that auto-wrote `display=<stored>` into
  the URL on absence. The effect re-added `display=` immediately after
  activating a view that intentionally omitted it via the "Include
  display mode" toggle, leaving the view permanently dirty.
- savedViews: drop `sort`/`dir` from the racks whitelist. Rack sort is
  still local to useDeviceSetListState, so capturing those keys made
  saved views appear to restore sort while the list ignored them.
- FleetViewTabs/ViewModal: add `intent: "update" | "rename"` discriminator
  to the update mode. Rename now skips updateUserViewParams entirely, so
  renaming a dirty view no longer silently overwrites the saved filters
  with the dirty state. Rename modal renders as a name-only edit
  ("Rename view" title, "Rename" submit).
- FleetViewTabs: gate the dropdown `+ New view` row on
  `canSaveCurrentTab` (was already gated on the empty-state button).
- RacksPage: collapse the building-key and zones/issues effects into one
  `filterFetchKey` effect so navigations that touch multiple filters
  fire one refetch instead of two.

Comment / API cleanups:

- TabStrip: `active` prop only overrides when explicitly `true`; passing
  `false` (or omitting it) falls back to `id === activeId`. Avoids the
  footgun where `active={someBool}` could force an item inactive even
  when the context marked it active.
- Drop stale comments referring to zone/issues as component-local on
  RacksPage. Tighten the JSDoc on the views trigger to describe the
  text-style button it actually renders. Update the `handleClearActiveView`
  comment to match the full-reset behavior.

Tests:

- Add unit coverage for summarizeDisplay, diffDisplaySummaries, and
  stripDisplayFromSearchParams.
- Update the racks canonicalization assertion now that sort is no
  longer whitelisted on that tab.
- Update the rename e2e to expect the new "Rename view" modal title and
  "Rename" submit button.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b718e4126f

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread client/src/protoFleet/features/fleetManagement/pages/RacksPage.tsx Outdated
The previous `selectedZones.join(",")` / `selectedIssues.join(",")`
fold made distinct selections collide once values contained the
delimiter — e.g. `["DC1,Row A", "B"]` and `["DC1", "Row A,B"]` both
serialized to `"DC1,Row A,B"`. Because this key is the only refetch
trigger after URL-driven zone/issue changes, switching between
colliding saved views (or chip selections) would update the URL and
chips while skipping `resetAndFetch`, leaving the rack list filtered
by the previous selection.

Encode the tuple as JSON instead so the delimiter character can't
appear ambiguously in any value.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1429589c91

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Rack sort lived only in useDeviceSetListState's internal state, so
whitelisting `sort`/`dir` on the racks tab would have made saved views
appear to capture sort while the visible list ignored them. The prior
fix dropped sort from the racks whitelist; this commit promotes sort
to the URL and re-adds the whitelist entries so saved rack views
actually round-trip sort.

Wiring:

- useDeviceSetListState now accepts an optional `initialSort` callback
  used to seed sortConfig at mount, so a deep-link or saved-view
  activation lands with the right ordering on the very first fetch.
- RacksPage reads `?sort=&dir=` (validated against SORTABLE_COLUMNS)
  and passes a capture-once initializer to the hook.
- handleRackSort — shared by the grid dropdown and the list column
  headers — writes the URL; a sync effect propagates external URL
  changes (saved view activation, deep link) into the hook by calling
  handleSort when urlSort differs from currentSort. Same effect no-ops
  when the page itself drove the change, so there's no double-fetch.
- summarizeSort and summarizeDisplay are now tab-aware. Only tabs that
  own the param (miners + racks for sort, racks for display) surface
  a summary. This prevents the New/Update modal from offering an
  "Include sort"/"Include display" toggle for cross-tab URL residue
  that the tab's canonicalization whitelist would silently strip.
- savedViews: re-add SORT_KEYS to the racks whitelist. Docstring
  updated to describe the URL ownership now that sort is shared by
  grid + list mechanisms.

Tests:

- summarizeSort gains rack + cross-tab cases.
- summarizeDisplay gains miners/buildings/sites null cases.
- savedViews canonicalization for racks now preserves sort.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review


P2 Badge Include site in miner view keys

When a miner view is activated from a rack URL scoped by site=..., buildUrlForView treats site as unrelated to miners because it is absent from this whitelist, so it carries the rack site filter into /fleet/miners. The miners URL parser does consume site= into filter.siteIds, which means an unfiltered miner view can open site-filtered while still looking clean because canonicalization also ignores that key; include site in the miner-owned params (and its summary) so it is stripped or saved like the other miner filters.

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

client javascript Pull requests that update javascript code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Multi-site fleet page — fold custom views into the top-level tab nav

2 participants