Skip to content

[codex] refine token routes workspace motion and hierarchy#394

Open
cita-777 wants to merge 5 commits intomainfrom
codex/route-ui-modern-motion
Open

[codex] refine token routes workspace motion and hierarchy#394
cita-777 wants to merge 5 commits intomainfrom
codex/route-ui-modern-motion

Conversation

@cita-777
Copy link
Copy Markdown
Owner

@cita-777 cita-777 commented Apr 2, 2026

Summary

This PR refines the /routes workspace so the page feels more modern and materially smoother during everyday route operations.

From a user perspective, the biggest problems were desktop route cards jumping between grid and full-width layouts, summary cards re-rendering during unrelated channel edits, and a general lack of hierarchy in the toolbar/filter/card surfaces. Those issues showed up as stutter, flicker, shape changes, and a page that felt heavier than the rest of the product.

Root Cause

The main animation and smoothness issues came from three interacting patterns:

  1. Desktop route cards expanded in place inside the responsive grid, which caused the surrounding layout to reflow when one card changed from collapsed to expanded.
  2. RouteCard was memoized too loosely for this page shape, so collapsed cards still re-rendered when expanded-only channel state changed elsewhere.
  3. The route workspace UI relied on flatter, more generic surface styling and broad transition rules around drag-adjacent elements, which made the page feel visually noisy without adding clarity.

Fix

This change addresses both the structural smoothness problems and the visual polish together:

  • keeps desktop summary cards stable and opens route details in a dedicated full-width detail panel below the summary row instead of morphing the card in place
  • adds a focused regression test for the separate desktop detail panel behavior
  • tightens RouteCard memo behavior so collapsed cards skip rerenders triggered by expanded-only channel draft/update state
  • narrows drag-adjacent route-card transitions away from all toward property-specific transitions
  • upgrades the route workspace header into a clearer operations surface with route totals, current view state, and active filter/batch indicators
  • refreshes the filter bar, guidance panel, route summary cards, detail panel, and batch action bar with more deliberate spacing, depth, and modernized state treatment while keeping the existing metapi language and workflow intact
  • adds dark-theme-aware styling for the new route workspace surfaces

Validation

I verified the change with:

  • npm test -- src/web/pages/token-routes/RouteCard.test.tsx src/web/pages/tokenRoutes.desktop-detail-panel.test.tsx src/web/pages/tokenRoutes.group-collapse.test.tsx src/web/pages/tokenRoutes.mobile-layout.test.tsx src/web/pages/tokenRoutes.mobile.test.tsx src/web/pages/tokenRoutes.routing-strategy.test.tsx
  • npm run typecheck:web
  • npm run typecheck:web:test
  • npm run repo:drift-check

All of the above passed in the isolated latest-main worktree used for this PR.

Summary by CodeRabbit

  • New Features

    • Paired summary/detail route cards with a collapsible desktop detail panel, keyboard-accessible summary toggles, and a brief collapse delay for smoother closing
    • Per-route caching to speed missing-token detail rendering
    • Improved sortable interactions using a translate-only strategy and refined drag handles
  • Style

    • Visual refresh: filter bar, filter chips, route cards, batch bar, dark-mode tweaks, gradients, shadows, and tighter transitions
  • Tests

    • Added comprehensive tests for desktop detail presence, collapse timing, sorting, layering, and render-skipping

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 2, 2026

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ca909798-7508-4e62-bc27-0f18d4faba11

📥 Commits

Reviewing files that changed from the base of the PR and between 0c40f02 and 8d4c7ab.

📒 Files selected for processing (7)
  • src/web/index.css
  • src/web/pages/TokenRoutes.tsx
  • src/web/pages/token-routes/RouteCard.test.tsx
  • src/web/pages/token-routes/RouteCard.tsx
  • src/web/pages/token-routes/SortableChannelRow.layering.test.tsx
  • src/web/pages/token-routes/SortableChannelRow.tsx
  • src/web/pages/tokenRoutes.desktop-detail-panel.test.tsx
 _____________________________
< Reviewing code like a boss. >
 -----------------------------
  \
   \   (\__/)
       (•ㅅ•)
       /   づ
📝 Walkthrough

Walkthrough

Splits route rendering into paired summary and detail panels with a desktop presence/closing delay, replaces animated-collapse hooks with custom presence state, introduces per-route memoized missing-token caches, updates drag/sort strategy and sortable channel wiring, large CSS restyling (dark-mode/responsive), and adds/updates tests for accessibility, sorting, and panel presence.

Changes

Cohort / File(s) Summary
Global styling
src/web/index.css
Extensive new/updated .route-* styles: .route-batch-bar, .route-filter-bar and summary sub-elements, .filter-chip, .route-card-* styles, detail-panel presence animation, dark-mode overrides, and responsive tweaks.
Page orchestration
src/web/pages/TokenRoutes.tsx
Render routes as paired summary + conditional detail; add desktop detail presence (closingDesktopDetailRouteIds + timers); memoized per-route missing-token caches; minor toolbar/batch-bar/layout adjustments.
Route card UI & behavior
src/web/pages/token-routes/RouteCard.tsx
Remove AnimatedCollapse dependency; add summaryExpanded? and detailPanel?; collapsed summary gains keyboard/button semantics and active state; detail panel rendering controlled by presence wrapper; introduce SortableChannelShell and custom prop comparator for memoization.
Filter presence & animation
src/web/pages/token-routes/RouteFilterBar.tsx
Replace useAnimatedVisibility with custom presence state: renderExpandedContent, presenceOpen, and delayed unmounting (FILTER_EXPANDED_CONTENT_UNMOUNT_MS); update wrapper classes to route-filter-bar-presence + is-open.
DnD / sorting strategy
src/web/pages/token-routes/sortingStrategies.ts, src/web/pages/token-routes/sortingStrategies.test.ts
Add translateOnlyRectSortingStrategy to compute rect-only transforms (no scale) for sortable items; include unit test validating vertical delta behavior.
Sortable row API & visuals
src/web/pages/token-routes/SortableChannelRow.tsx, src/web/pages/token-routes/types.ts
Remove internal useSortable; accept drag state/handle props (dragging, dragHandleProps, dragHandleRef, dragInProgress); update styling, transition lists, tooltip suppression during drag, and badge color to info palette; update exported props type.
Desktop detail presence & page tests
src/web/pages/tokenRoutes.desktop-detail-panel.test.tsx, src/web/pages/token-routes/RouteFilterBar.test.tsx
New tests exercising desktop detail open/close presence timing and RouteFilterBar mount/animation sequencing (fake timers & RAF stubs).
RouteCard tests & sortable-shell tests
src/web/pages/token-routes/RouteCard.test.tsx, src/web/pages/token-routes/RouteCard.sortable-shell.test.tsx, src/web/pages/token-routes/SortableChannelRow.layering.test.tsx
Update/extend tests: collapsed keyboard interaction, memoization proxy test, sortable shell transform assertions, layering/checks for grid/borderRadius and tooltip suppression.

Sequence Diagram(s)

sequenceDiagram
  participant User as User
  participant Page as TokenRoutes
  participant Card as RouteCard (summary)
  participant Panel as DesktopDetailPanelPresence
  participant Timer as Close Timer

  User->>Card: click summary (open)
  Card->>Page: notify toggleExpand(routeId)
  Page->>Panel: mount detailPanel for routeId
  Panel->>Card: mark summary as active
  User->>Panel: click "收起详情" (close)
  Panel->>Page: notify toggleExpand(routeId)
  Page->>Timer: start DESKTOP_DETAIL_COLLAPSE_MS (180ms)
  Timer-->>Panel: delay expiration
  Panel->>Card: keep summary marked active while closing
  Timer->>Page: unmount detailPanel, clear closing flag
  Page->>Card: remove active state
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 I hopped through CSS and JSX lanes,
Paired summaries with panels that gently close,
Cached tiny tokens in tidy chains,
Dragged channels with careful prose,
I twitched my nose — the UI softly glows. 🥕✨

🚥 Pre-merge checks | ✅ 2 | ❌ 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 (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title '[codex] refine token routes workspace motion and hierarchy' accurately summarizes the main changes: improving motion (animation/transitions) and visual hierarchy in the routes workspace through UI refinements and performance optimizations.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 codex/route-ui-modern-motion

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 and usage tips.

@github-actions github-actions bot added area: web Web UI changes size: XL 1000 to 1999 lines changed labels Apr 2, 2026
@cita-777 cita-777 marked this pull request as ready for review April 2, 2026 05:53
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (1)
src/web/pages/TokenRoutes.tsx (1)

1814-1886: ⚠️ Potential issue | 🟠 Major

The summary/detail split still eagerly computes detail-only props.

This loop still calls getRouteCandidateView() plus both missing-token builders for every visible route on every parent render. That keeps unrelated channel draft/update state on an O(visibleRoutes) hot path, and the fresh missing-token arrays also invalidate RouteCard's expanded-props equality check even when the underlying hints did not change.

⚡ Minimal fix for the collapsed summary path
           const summaryCard = (
             <RouteCard
               route={route}
               brand={routeBrandById.get(route.id) || null}
               expanded={false}
               summaryExpanded={isExpanded}
@@
-              candidateView={getRouteCandidateView(route.id)}
+              candidateView={EMPTY_ROUTE_CANDIDATE_VIEW}
@@
-              missingTokenSiteItems={getMissingTokenSiteItems(route.id)}
-              missingTokenGroupItems={getMissingTokenGroupItems(route.id)}
+              missingTokenSiteItems={EMPTY_MISSING_ITEMS}
+              missingTokenGroupItems={EMPTY_MISSING_GROUP_ITEMS}
               onCreateTokenForMissing={stableCreateTokenForMissing}
               onAddChannel={stableAddChannel}
               onSiteBlockModel={stableSiteBlockModel}
               expandedSourceGroupMap={expandedSourceGroupMap}
               onToggleSourceGroup={stableToggleSourceGroup}

If you want the expanded path to stay memo-friendly too, cache getMissingTokenSiteItems() / getMissingTokenGroupItems() per route the same way as getRouteCandidateView().

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/web/pages/TokenRoutes.tsx` around lines 1814 - 1886, The summary/detail
split is eagerly computing detail-only values (getRouteCandidateView,
getMissingTokenSiteItems, getMissingTokenGroupItems) for every route; change the
code so these calls are only invoked when detailPanel is rendered (i.e., inside
the isExpanded branch) and pass lightweight defaults to the summary RouteCard
(keep candidateView, missingTokenSiteItems and missingTokenGroupItems
undefined/null for the collapsed summary); for a stronger fix also memoize/cache
per-route results of getRouteCandidateView, getMissingTokenSiteItems and
getMissingTokenGroupItems (keyed by route.id) so RouteCard’s expanded prop
equality isn’t invalidated unnecessarily (refer to RouteCard props
candidateView, missingTokenSiteItems, missingTokenGroupItems and the helper
getRouteCandidateView).
🧹 Nitpick comments (1)
src/web/pages/tokenRoutes.desktop-detail-panel.test.tsx (1)

2-2: Import ReactTestRenderer explicitly in this new test.

WebTestRenderer makes this file depend on an ambient alias again. Pull the renderer type from react-test-renderer here so the test stays self-contained.

♻️ Suggested change
-import { act, create, type ReactTestInstance } from 'react-test-renderer';
+import { act, create, type ReactTestInstance, type ReactTestRenderer } from 'react-test-renderer';
@@
-    let root!: WebTestRenderer;
+    let root!: ReactTestRenderer;

Based on learnings, Prefer explicit imports of ReactTestRenderer from react-test-renderer in new test files (e.g., *.test.tsx) instead of relying on any globally-declared WebTestRenderer/WebTestRenderer type aliases. Keeping the dependency explicit makes tests self-contained and avoids ambiguity when checking/maintaining TypeScript types.

Also applies to: 142-142

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/web/pages/tokenRoutes.desktop-detail-panel.test.tsx` at line 2, Add an
explicit import for the ReactTestRenderer type from 'react-test-renderer' in
this test file so it doesn't rely on any ambient WebTestRenderer alias: update
the import statement that currently pulls in act, create, and ReactTestInstance
to also import the type ReactTestRenderer and use that type where needed
(references: ReactTestRenderer, ReactTestInstance, act, create) to keep the test
self-contained.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/web/index.css`:
- Around line 4829-4844: The .route-batch-bar is using top: 0 so it will stick
under the existing fixed topbar; change the rule for .route-batch-bar to use the
top offset variable (replace top: 0 with top: var(--topbar-height)) so the
sticky batch bar sits below the topbar (use the --topbar-height CSS custom
property as the offset and keep existing z-index/display styles).

In `@src/web/pages/token-routes/RouteCard.tsx`:
- Around line 273-277: The summary surface (the div with class "card
route-card-collapsed" that calls onToggleExpand(route.id)) is not keyboard
focusable or operable; make it accessible by either converting it to a semantic
<button> or adding tabIndex={0}, role="button", aria-expanded={summaryExpanded}
and an onKeyDown handler that triggers onToggleExpand(route.id) when Enter or
Space is pressed; ensure the existing onClick handler remains and that
aria-expanded reflects the summaryExpanded state so screen readers announce it.

---

Outside diff comments:
In `@src/web/pages/TokenRoutes.tsx`:
- Around line 1814-1886: The summary/detail split is eagerly computing
detail-only values (getRouteCandidateView, getMissingTokenSiteItems,
getMissingTokenGroupItems) for every route; change the code so these calls are
only invoked when detailPanel is rendered (i.e., inside the isExpanded branch)
and pass lightweight defaults to the summary RouteCard (keep candidateView,
missingTokenSiteItems and missingTokenGroupItems undefined/null for the
collapsed summary); for a stronger fix also memoize/cache per-route results of
getRouteCandidateView, getMissingTokenSiteItems and getMissingTokenGroupItems
(keyed by route.id) so RouteCard’s expanded prop equality isn’t invalidated
unnecessarily (refer to RouteCard props candidateView, missingTokenSiteItems,
missingTokenGroupItems and the helper getRouteCandidateView).

---

Nitpick comments:
In `@src/web/pages/tokenRoutes.desktop-detail-panel.test.tsx`:
- Line 2: Add an explicit import for the ReactTestRenderer type from
'react-test-renderer' in this test file so it doesn't rely on any ambient
WebTestRenderer alias: update the import statement that currently pulls in act,
create, and ReactTestInstance to also import the type ReactTestRenderer and use
that type where needed (references: ReactTestRenderer, ReactTestInstance, act,
create) to keep the test self-contained.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6d26086e-7f0d-4e09-80ed-5e31e169a345

📥 Commits

Reviewing files that changed from the base of the PR and between 1407f75 and 1c20ed5.

📒 Files selected for processing (6)
  • src/web/index.css
  • src/web/pages/TokenRoutes.tsx
  • src/web/pages/token-routes/RouteCard.test.tsx
  • src/web/pages/token-routes/RouteCard.tsx
  • src/web/pages/token-routes/RouteFilterBar.tsx
  • src/web/pages/tokenRoutes.desktop-detail-panel.test.tsx

@cita-777
Copy link
Copy Markdown
Owner Author

cita-777 commented Apr 2, 2026

Addressed the current review feedback in b3346e3:

  • moved the sticky batch bar below the fixed topbar
  • made the collapsed summary card keyboard-operable with role, tabIndex, aria-expanded, and Enter/Space handling
  • stopped the desktop summary path from eagerly computing detail-only candidate/missing-token props, and cached per-route missing-token site/group items for the expanded path
  • imported ReactTestRenderer explicitly in the new desktop detail-panel test

Verification rerun after the fixes:

  • npm test -- src/web/pages/token-routes/RouteCard.test.tsx src/web/pages/tokenRoutes.desktop-detail-panel.test.tsx src/web/pages/tokenRoutes.group-collapse.test.tsx src/web/pages/tokenRoutes.mobile-layout.test.tsx src/web/pages/tokenRoutes.mobile.test.tsx src/web/pages/tokenRoutes.routing-strategy.test.tsx
  • npm run typecheck:web
  • npm run typecheck:web:test
  • npm run repo:drift-check

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/web/pages/token-routes/RouteCard.tsx (1)

833-835: Consider documenting the early-return optimization.

The if (!next.expanded) return true short-circuit is the core memoization optimization that prevents collapsed cards from re-rendering on expanded-only state changes. A brief inline comment would help future maintainers understand this intentional behavior.

📝 Suggested documentation
+  // Collapsed cards don't render channel details, so skip re-renders
+  // triggered by expanded-only state (channels, drafts, decision, etc.)
   if (!next.expanded) {
     return true;
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/web/pages/token-routes/RouteCard.tsx` around lines 833 - 835, Add a brief
inline comment above the early-return "if (!next.expanded) { return true; }" in
RouteCard.tsx explaining that this short-circuit is an intentional memoization
optimization to prevent collapsed cards from re-rendering when only
expanded-state changes; reference the condition (next.expanded) and the intent
(skip updates for collapsed cards) so future maintainers understand why the
guard exists.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/web/pages/token-routes/RouteCard.tsx`:
- Around line 833-835: Add a brief inline comment above the early-return "if
(!next.expanded) { return true; }" in RouteCard.tsx explaining that this
short-circuit is an intentional memoization optimization to prevent collapsed
cards from re-rendering when only expanded-state changes; reference the
condition (next.expanded) and the intent (skip updates for collapsed cards) so
future maintainers understand why the guard exists.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f9be428a-aa8b-48b1-b7ac-f1aee0e21d25

📥 Commits

Reviewing files that changed from the base of the PR and between 1c20ed5 and b3346e3.

📒 Files selected for processing (5)
  • src/web/index.css
  • src/web/pages/TokenRoutes.tsx
  • src/web/pages/token-routes/RouteCard.test.tsx
  • src/web/pages/token-routes/RouteCard.tsx
  • src/web/pages/tokenRoutes.desktop-detail-panel.test.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/web/pages/token-routes/RouteCard.test.tsx
  • src/web/pages/TokenRoutes.tsx

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

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)
src/web/pages/token-routes/SortableChannelRow.tsx (1)

180-185: ⚠️ Potential issue | 🟠 Major

Use a theme token for the “当前生效” badge background.

color-mix(in srgb, var(--color-info) 14%, white) resolves to an almost-white pill in both themes. In dark mode that pairs the lighter --color-info text with a very light background, which hurts contrast and breaks the new dark-surface hierarchy. You already have --color-info-soft for this.

Suggested fix
-                  background: 'color-mix(in srgb, var(--color-info) 14%, white)',
+                  background: 'var(--color-info-soft)',

Also applies to: 387-392

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/web/pages/token-routes/SortableChannelRow.tsx` around lines 180 - 185,
The badge for the "当前生效" pill uses a hard-coded color-mix background that
produces poor contrast in dark mode; update the span with className "badge" in
the SortableChannelRow component to use the theme token var(--color-info-soft)
instead of color-mix(...), and make the same replacement for the second
occurrence around the other badge (the block referenced at the other
occurrence). Ensure the style object replaces background: 'color-mix(in srgb,
var(--color-info) 14%, white)' with background: 'var(--color-info-soft)' so the
badge respects the dark-surface hierarchy.
🧹 Nitpick comments (2)
src/web/pages/token-routes/SortableChannelRow.layering.test.tsx (1)

69-77: Make this regression test independent of styling tokens.

Matching the row by display === 'grid' and borderRadius === 16, then asserting borderLeft is absent, couples a layering test to presentation details rather than the zIndex contract. A stable hook on the row root would keep this from breaking on the next visual refresh.

Stable selector example
--- a/src/web/pages/token-routes/SortableChannelRow.tsx
+++ b/src/web/pages/token-routes/SortableChannelRow.tsx
@@
-    <div ref={setNodeRef} style={rowStyle}>
+    <div ref={setNodeRef} style={rowStyle} data-testid={`sortable-channel-row-${channel.id}`}>
--- a/src/web/pages/token-routes/SortableChannelRow.layering.test.tsx
+++ b/src/web/pages/token-routes/SortableChannelRow.layering.test.tsx
@@
-    const row = root.root.find((node) => (
-      node.type === 'div'
-      && node.props.style
-      && node.props.style.display === 'grid'
-      && node.props.style.borderRadius === 16
-    ));
+    const row = root.root.findByProps({ 'data-testid': `sortable-channel-row-${channel.id}` });

     expect(row.props.style.zIndex).toBeUndefined();
-    expect(row.props.style.borderLeft).toBeUndefined();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/web/pages/token-routes/SortableChannelRow.layering.test.tsx` around lines
69 - 77, The test is brittle because it locates the row element by styling
tokens (root.root.find with display === 'grid' && borderRadius === 16) and then
asserts layering props; instead, change the component under test
(SortableChannelRow) to expose a stable hook (e.g., a data attribute like
data-testid="sortable-channel-row" or data-layer-root) and update the test to
find the node by that stable attribute (replace the root.root.find predicate)
and then assert that row.props.style.zIndex and row.props.style.borderLeft are
undefined; reference SortableChannelRow and the test's row lookup to implement
this change so future visual style changes won't break the layering contract.
src/web/pages/token-routes/SortableChannelRow.tsx (1)

88-91: Scope will-change to active transforms only.

This keeps every idle row in the list promoted all the time. On larger route buckets that can add unnecessary compositing/memory overhead and eat into the smoothness win this PR is aiming for. Gate it on an active transform instead.

Example tweak
-    willChange: 'transform',
+    willChange: transform ? 'transform' : undefined,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/web/pages/token-routes/SortableChannelRow.tsx` around lines 88 - 91, The
inline style in SortableChannelRow.tsx currently sets willChange unconditionally
which promotes every row; change the style so willChange is only set when the
row is actively being transformed (use the existing isDragging flag), e.g. set
willChange to 'transform' when isDragging is true and to undefined/'auto' when
false; update the style object where boxShadow and willChange are defined (the
block referencing isDragging) so only active drags request the GPU compositing.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/web/pages/token-routes/SortableChannelRow.tsx`:
- Around line 180-185: The badge for the "当前生效" pill uses a hard-coded color-mix
background that produces poor contrast in dark mode; update the span with
className "badge" in the SortableChannelRow component to use the theme token
var(--color-info-soft) instead of color-mix(...), and make the same replacement
for the second occurrence around the other badge (the block referenced at the
other occurrence). Ensure the style object replaces background: 'color-mix(in
srgb, var(--color-info) 14%, white)' with background: 'var(--color-info-soft)'
so the badge respects the dark-surface hierarchy.

---

Nitpick comments:
In `@src/web/pages/token-routes/SortableChannelRow.layering.test.tsx`:
- Around line 69-77: The test is brittle because it locates the row element by
styling tokens (root.root.find with display === 'grid' && borderRadius === 16)
and then asserts layering props; instead, change the component under test
(SortableChannelRow) to expose a stable hook (e.g., a data attribute like
data-testid="sortable-channel-row" or data-layer-root) and update the test to
find the node by that stable attribute (replace the root.root.find predicate)
and then assert that row.props.style.zIndex and row.props.style.borderLeft are
undefined; reference SortableChannelRow and the test's row lookup to implement
this change so future visual style changes won't break the layering contract.

In `@src/web/pages/token-routes/SortableChannelRow.tsx`:
- Around line 88-91: The inline style in SortableChannelRow.tsx currently sets
willChange unconditionally which promotes every row; change the style so
willChange is only set when the row is actively being transformed (use the
existing isDragging flag), e.g. set willChange to 'transform' when isDragging is
true and to undefined/'auto' when false; update the style object where boxShadow
and willChange are defined (the block referencing isDragging) so only active
drags request the GPU compositing.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a34345a1-2357-4d09-b5f9-9041c4c94db2

📥 Commits

Reviewing files that changed from the base of the PR and between b3346e3 and 98ec4e7.

📒 Files selected for processing (3)
  • src/web/index.css
  • src/web/pages/token-routes/SortableChannelRow.layering.test.tsx
  • src/web/pages/token-routes/SortableChannelRow.tsx

@github-actions github-actions bot added size: XXL 2000 or more lines changed and removed size: XL 1000 to 1999 lines changed labels Apr 3, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (1)
src/web/pages/TokenRoutes.tsx (1)

1841-1959: ⚠️ Potential issue | 🟡 Minor

Grid layout will leave gaps and displace later summary cards without dense packing.

The detail panel's gridColumn: '1 / -1' span (confirmed at line 145 of TokenRoutes.tsx and line 5849 of index.css) combined with the grid's repeat(auto-fill, minmax(400px, 1fr)) auto-placement will push subsequent route cards below the full-width detail panels. Without grid-auto-flow: dense, the grid respects source order and does not backfill empty cells. Add grid-auto-flow: dense to .route-card-grid or switch to auto-fit with careful overflow handling.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/web/pages/TokenRoutes.tsx` around lines 1841 - 1959, The grid placement
is leaving gaps because detail panels span full width and the container uses
auto-placement without backfilling; update the CSS for the grid container
(selector .route-card-grid) to include grid-auto-flow: dense so
summaryCard/RouteCard items can backfill gaps created by the detailPanel
full-width span, or alternatively change the grid from repeat(auto-fill,
minmax(400px, 1fr)) to repeat(auto-fit, minmax(400px, 1fr)) with proper overflow
handling; apply this change alongside the TokenRoutes rendering of summaryCard
and detailPanel (DesktopDetailPanelPresence) to ensure subsequent route cards
aren’t pushed below full-width panels.
🧹 Nitpick comments (2)
src/web/pages/token-routes/RouteCard.tsx (2)

238-243: Scope willChange to active transforms only.

Leaving willChange: 'transform' on every channel shell all the time is a poor fit for long route lists; it can keep too many rows eligible for compositor promotion even when they are idle. Only opt in while a row is actually dragging or animating.

♻️ Minimal change
-        willChange: 'transform',
+        willChange: isDragging || translatedTransform ? 'transform' : undefined,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/web/pages/token-routes/RouteCard.tsx` around lines 238 - 243, The
component currently sets willChange: 'transform' unconditionally which keeps
rows on the compositor; change it to only opt-in when the row is actively being
dragged or animating by making willChange conditional (e.g. set willChange to
'transform' only when isDragging is true or shellTransition exists) in the style
object where translatedTransform, shellTransition and isDragging are used
(inside RouteCard's inline style block) so idle rows don't remain eligible for
compositor promotion.

1022-1051: Move the collapsed fast-path ahead of expanded-only callback checks.

Collapsed cards do not read onEdit, onSaveToken, onDeleteChannel, onRoutingStrategyChange, and the other expanded-only handlers, but those identities are still compared before the !next.expanded return. Any upstream callback churn will still invalidate memoization for the summary path and make the rerender win here fragile.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/web/pages/token-routes/RouteCard.tsx` around lines 1022 - 1051,
areRouteCardPropsEqual currently compares many expanded-only callback props
before checking if the card is collapsed; move the collapsed fast-path (the if
(!next.expanded) return true) to the top of the function (right after the simple
scalar comparisons like
route/brand/expanded/compact/summaryExpanded/detailPanel) so collapsed cards
bail out without comparing expanded-only handlers. Specifically, ensure that
handlers such as onEdit, onSaveToken, onDeleteChannel, onRoutingStrategyChange,
onCreateTokenForMissing, onAddChannel, onDelete, onToggleEnabled,
onClearCooldown, onToggleChannelEnabled, onChannelDragEnd, onTokenDraftChange,
onToggleExpand, onToggleSourceGroup, and onSiteBlockModel are not compared when
next.expanded is false by placing the !next.expanded short-circuit before those
comparisons in areRouteCardPropsEqual.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/web/pages/token-routes/RouteCard.tsx`:
- Around line 769-774: The compact-mode button rendering only checks
!exactRoute, which hides the group editor for explicitGroupRoute routes; update
the condition that renders the edit button (the block that calls onEdit(route)
and uses renderClearCooldownButton()) to allow explicitGroupRoute as well by
changing the guard to (explicitGroupRoute || !exactRoute) so explicit-group
routes keep the edit entry point in compact/mobile layouts; ensure
explicitGroupRoute is in scope where this JSX renders.

In `@src/web/pages/TokenRoutes.tsx`:
- Around line 108-139: The effect runs the "close" timeout on initial mount when
open is false; add a "hasEverOpened" flag (useRef or state) and set it true
inside the branch where open is true (e.g. in DesktopDetailPanelPresence when
open becomes true set hasEverOpened.current = true), then in the effect's false
branch only run the setIsOpen(false)/setIsClosing(true)/setTimeout(...) cleanup
sequence if hasEverOpened is true; if hasEverOpened is false, skip scheduling
the timeout and just ensure shouldRender stays false and isOpen is false to
avoid the initial unnecessary timer and state churn (keep existing RAF and timer
cleanup logic intact and refer to DESKTOP_DETAIL_COLLAPSE_MS).

---

Outside diff comments:
In `@src/web/pages/TokenRoutes.tsx`:
- Around line 1841-1959: The grid placement is leaving gaps because detail
panels span full width and the container uses auto-placement without
backfilling; update the CSS for the grid container (selector .route-card-grid)
to include grid-auto-flow: dense so summaryCard/RouteCard items can backfill
gaps created by the detailPanel full-width span, or alternatively change the
grid from repeat(auto-fill, minmax(400px, 1fr)) to repeat(auto-fit,
minmax(400px, 1fr)) with proper overflow handling; apply this change alongside
the TokenRoutes rendering of summaryCard and detailPanel
(DesktopDetailPanelPresence) to ensure subsequent route cards aren’t pushed
below full-width panels.

---

Nitpick comments:
In `@src/web/pages/token-routes/RouteCard.tsx`:
- Around line 238-243: The component currently sets willChange: 'transform'
unconditionally which keeps rows on the compositor; change it to only opt-in
when the row is actively being dragged or animating by making willChange
conditional (e.g. set willChange to 'transform' only when isDragging is true or
shellTransition exists) in the style object where translatedTransform,
shellTransition and isDragging are used (inside RouteCard's inline style block)
so idle rows don't remain eligible for compositor promotion.
- Around line 1022-1051: areRouteCardPropsEqual currently compares many
expanded-only callback props before checking if the card is collapsed; move the
collapsed fast-path (the if (!next.expanded) return true) to the top of the
function (right after the simple scalar comparisons like
route/brand/expanded/compact/summaryExpanded/detailPanel) so collapsed cards
bail out without comparing expanded-only handlers. Specifically, ensure that
handlers such as onEdit, onSaveToken, onDeleteChannel, onRoutingStrategyChange,
onCreateTokenForMissing, onAddChannel, onDelete, onToggleEnabled,
onClearCooldown, onToggleChannelEnabled, onChannelDragEnd, onTokenDraftChange,
onToggleExpand, onToggleSourceGroup, and onSiteBlockModel are not compared when
next.expanded is false by placing the !next.expanded short-circuit before those
comparisons in areRouteCardPropsEqual.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 622da77f-18f8-49ae-b292-f8f2cee2fada

📥 Commits

Reviewing files that changed from the base of the PR and between 98ec4e7 and 0c40f02.

📒 Files selected for processing (13)
  • src/web/index.css
  • src/web/pages/TokenRoutes.tsx
  • src/web/pages/token-routes/RouteCard.sortable-shell.test.tsx
  • src/web/pages/token-routes/RouteCard.test.tsx
  • src/web/pages/token-routes/RouteCard.tsx
  • src/web/pages/token-routes/RouteFilterBar.test.tsx
  • src/web/pages/token-routes/RouteFilterBar.tsx
  • src/web/pages/token-routes/SortableChannelRow.layering.test.tsx
  • src/web/pages/token-routes/SortableChannelRow.tsx
  • src/web/pages/token-routes/sortingStrategies.test.ts
  • src/web/pages/token-routes/sortingStrategies.ts
  • src/web/pages/token-routes/types.ts
  • src/web/pages/tokenRoutes.desktop-detail-panel.test.tsx
✅ Files skipped from review due to trivial changes (2)
  • src/web/pages/tokenRoutes.desktop-detail-panel.test.tsx
  • src/web/pages/token-routes/RouteFilterBar.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/web/pages/token-routes/SortableChannelRow.layering.test.tsx
  • src/web/pages/token-routes/RouteCard.test.tsx

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

Labels

area: web Web UI changes size: XXL 2000 or more lines changed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant