- Sync blockedBrands local state after save to prevent stale chips
- Defer getBrandList from blocking Settings bootstrap
- Guard model management UI on brandGroups instead of availableModels
- Deduplicate single-model add in disabled models
- Skip read-only/zero-channel routes in enabledCounts and filter
- Add sequence counter to loadCandidates to prevent force race condition
- Use stableAddChannel for mobile add-channel button
Replace closure-captured editor check with loadingModelsSiteIdRef so the
stale-response guard works correctly across rapid site switches. Also
gate setDisabledModelsLoading(false) behind the same ref check.
- settings.ts: trigger route rebuild after globalBlockedBrands changes in both
import and PUT handler; reject non-array payloads with 400 instead of silently
defaulting to empty array
- sites.ts: trim model names before deduplication so whitespace variants merge
- brandMatcher.ts: align boundary regex with frontend delimiter class [/:_\-\s]
- Settings.tsx: add role="switch" and aria-checked to brand filter chips; make
getBrandList best-effort so its failure doesn't block page load
- Sites.tsx: build brandGroups from union of availableModels and disabledModels
so legacy/removed models remain visible; add stale-response guard and error
logging for disabled/available model fetches
- TokenRoutes.tsx: use baseFilteredRoutes.length for totalRouteCount to keep
enabled/disabled counts in sync with status filter; add candidatesVersionRef
to invalidate lazy caches on payload change; add per-channel in-flight guard
on toggle; invalidate channel cache after rebuild and site-block operations
- Escape modelName/siteName in innerHTML to prevent XSS
- Use content-based routePatternsKey for cache invalidation instead of
length-only keys that miss content changes
- Extract shared baseFilteredRoutes to eliminate duplicated filter logic
between enabledCounts and filteredRoutes
- Add candidatesPromiseRef to deduplicate concurrent loadCandidates calls
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