The routes page loaded ~924KB of API data upfront (summary 366KB +
token-candidates 558KB), but token-candidates was only needed when
expanding cards, opening panels, or using filters. This caused a ~3s
element render delay blocking LCP.
Changes:
- Split load() so first paint only fetches routesSummary (366KB)
- Preload token-candidates in background via requestIdleCallback
- Wrap candidates state updates in startTransition to avoid UI jank
- Convert 3 heavy O(routes×models) useMemo indices to lazy per-route
computation with ref-based cache (candidateIndex, missingTokenIndex,
missingTokenGroupIndex)
- Guard 4 ManualRoutePanel-only useMemos with showManual check
- Convert 2 derived useMemos to per-route getter functions
- Add loadCandidates() calls at 7 interaction trigger points
- brandMatcher: port collectBrandCandidates + buildMatchContext from
frontend BrandIcon.tsx so matching is identical between route display
and rebuild filtering; segments now split on /,:,\s only (not - or _),
and candidates are recursively generated from /,:,, splits
- Sites.tsx: remove manual model name input and handleAddDisabledModel;
the checkbox-based batch selection UI fully replaces it
- brandMatcher: split keywords into includes/startsWith/segment/boundary
modes to align with frontend BrandIcon.tsx matching semantics, preventing
false-positive blocking from short keywords like o1, tts, command
- handleSiteBlockModel: reject wildcard route patterns (e.g. gpt-*) from
being written to site disabled list; only allow exact model names
- Sites.tsx: extract brand grouping to useMemo, convert disabledModels
to Set for O(1) lookup, remove duplicate IIFE computations in JSX
- handleSiteBlockModel: replace window.confirm with project-style DOM
dialog for visual consistency
- enabledCounts: compute from filtered scope (excluding enabled filter
itself) so counts reflect current brand/site/search filters
- Fix declaration order: move enabledCounts after sortedRoutes to avoid
ReferenceError
- Route status filter: add enabled/disabled filter chips to RouteFilterBar
- Channel site-block: add "站点屏蔽" button on channel rows to quickly
add a model to a site's disabled list and trigger rebuild
- Site batch selection: new GET /api/sites/:id/available-models endpoint;
Sites.tsx now shows a brand-grouped checkbox list for batch model disable
- Global brand filter: new brandMatcher.ts service; settings API and UI
for blocking entire brands from route generation
- Fix infinite scroll race condition when sentinel ref is null during
IntersectionObserver effect setup after filter changes