Перейти к основному содержимому

ТЗ: Страницы AI-аналитики токенов на фронтенде

Метаданные

ПараметрЗначение
Дата создания2026-07-14
Дата последнего изменения2026-07-14
Статус апрува✅ Одобрено
Дата апрува2026-07-14

1. Назначение

Бэкенд-ветка токен-аналитики готова (ТЗ tz/active/token-analytics.md, документация engineering/Сервисы/backend/ai-analytics.md): модуль aiAnalytics отдаёт восемь эндпоинтов — каталог токенов, поиск, trending, карточку токена, график, новости, глобальные метрики рынка и AI-разбор токена. На фронтенде из них не подключён ни один: src/api/ai-analytics/ знает только про старые market/outlook и pool/analytics.

Задача — построить раздел «Токены»: страницу каталога и страницу токена с графиком, новостями и AI-разбором. Референс интерфейса — coinmarketcap.com.

Frontend-only. Бэкенд в рамках этого ТЗ не меняется.


2. Функциональные требования

2.1. Пользовательские сценарии

  1. Пользователь открывает /tokens → видит шапку рынка (капитализация, объём 24 ч, доминанс BTC и ETH, индекс Fear & Greed), полосу трендовых токенов и таблицу токенов: иконка, название, тикер, цена, изменение за 1 ч / 24 ч / 7 д, капитализация, объём 24 ч, circulating supply, семидневный спарклайн.
  2. Пользователь сортирует таблицу кликом по заголовку — по капитализации, объёму или по изменению цены за 1 ч / 24 ч / 7 д — и листает страницы кнопками «назад» / «вперёд».
  3. Пользователь вводит запрос в поиск → видит подходящие токены с ценой и переходит в любой из них.
  4. Пользователь кликает по строке → открывается /tokens/{id}: слева цена и рыночные метрики, supply, ATH/ATL, ссылки, контракты и категории; по центру график цены с переключением периода; справа настроение сообщества, метрики сообщества и разработки, описание проекта.
  5. Пользователь нажимает «AI-разбор» → под графиком раскрывается карточка: вердикт (bullish / bearish / neutral), краткий вывод, рассчитанные метрики (RSI 14, волатильность 7 д, тренд 7 д, дистанция до ATH), сильные стороны, риски, сценарии и дисклеймер. Кнопка «Обновить» перезапрашивает разбор.
  6. Пользователь читает новости по токену внизу центральной колонки.

2.2. Бизнес-логика

Потребляемые эндпоинты

Все — под JwtAuthGuard, префикс /ai-analytics, авторизация уходит автоматически через интерцептор src/api/api.ts.

МетодПутьГде используетсяTTL бэкендаrefreshInterval SWR
GET/ai-analytics/market/globalшапка рынка на /tokens60 c60 000
GET/ai-analytics/tokensтаблица; добор цен для поиска60 c60 000
GET/ai-analytics/tokens/searchпоиск300 cбез поллинга
GET/ai-analytics/tokens/trendingполоса трендов на /tokens300 c300 000
GET/ai-analytics/tokens/:idлевая и правая колонки токена300 c300 000
GET/ai-analytics/tokens/:id/chartграфик цены300 c300 000
GET/ai-analytics/tokens/:id/newsновости600 cбез поллинга
POST/ai-analytics/tokens/:id/analyticsAI-разбор3600 cтолько по клику

refreshInterval не может быть короче TTL: более частый опрос не даст свежих данных, но разбудит цепочку походов к CoinGecko и повысит вероятность stale и 503. Везде revalidateOnFocus: false и keepPreviousData: true.

Ключевые правила

  1. Сортировка — один механизм: клик по заголовку колонки (TableSortLabel, как в entities/statistic/ui/PreprodUserTable.tsx). Никаких отдельных кнопок-переключателей над таблицей. Соответствие колонок и параметров запроса:

    КолонкаЧто уходит на бэкенд
    Капитализацияorder=market_cap_desc / market_cap_asc
    Объём 24 чorder=volume_desc / volume_asc
    1 ч %sort=change1h + dir=desc / asc
    24 ч %sort=change24h + dir=desc / asc
    7 д %sort=change7d + dir=desc / asc
    Название, цена, supply, спарклайнне сортируются (бэкенд не умеет)

    Дефолт — order=market_cap_desc. order и sort взаимоисключающи: sort заставляет бэкенд сортировать в памяти пул топ-250 и полностью игнорировать order, поэтому фронт всегда отправляет ровно один из двух параметров. Сортировки по изменению за 30 дней в v1 нет: соответствующей колонки в таблице нет, а сортировать по невидимой колонке бессмысленно.

  2. Пагинация — курсорного вида, счётчика страниц нет. TokenListResponseDto отдаёт только items, page, perPage — общего количества токенов бэкенд не возвращает (dto/token-analytics.dto.ts:170), а ТЗ frontend-only, добавить total нельзя. Следствия:

    • вместо <Pagination count={…}> — кнопки «назад» / «вперёд» и номер текущей страницы. «Вперёд» активна, пока items.length === perPage; пришло меньше — это последняя страница;
    • perPage по умолчанию 50, селектор значений — 25 / 50 / 100 (бэкенд принимает до 250, page — до 100);
    • в режиме sort страниц не больше ceil(250 / perPage) (при perPage=50 — пять): бэкенд режет пул топ-250 (pool.slice((page - 1) * perPage, …), token-market.service.ts:181), и на шестой странице вернёт 200 с пустым items. Фронт обрезает пагинацию до этого предела и сбрасывает page в 1 при любой смене сортировки или perPage — иначе пользователь, стоявший на шестой странице, увидит пустую таблицу.
  3. Фильтр по категориям в v1 не делаем. Бэкенд отдаёт 400 на паре sort + category (token-market.service.ts:154). Без фильтра категорий конфликт невозможен по построению; появится фильтр — %-сортировка должна блокироваться при выбранной категории.

  4. Поиск делается в два запроса. /tokens/search не возвращает цены и отдаёт максимум 25 монет. Фронт берёт их id, делает добор GET /tokens?ids=<csv> (лимит 50 — одного добора хватает всегда) и показывает выдачу с ценами. Ввод дебаунсится 500 мс через patronum/debounce. Результаты поиска заменяют таблицу каталога (не выпадающий список): те же колонки, но без пагинации и без сортировки — бэкенд ранжирует выдачу сам. Пустой запрос возвращает каталог. Ноль совпадений — пустое состояние «Ничего не найдено» внутри таблицы.

  5. cached — это норма, тревожный сигнал только stale. Свежий кеш даёт cached: true, stale: false — пользователю ничего не сообщаем. stale: true означает, что CoinGecko недоступен и данные отданы из просроченного кеша: показываем warning-чип «данные могли устареть, обновлено {fetchedAt}» — в шапке таблицы на каталоге и рядом с ценой на странице токена.

  6. Провенанс неоднороден — компонент чипа принимает поля опционально. stale и fetchedAt есть у списка, поиска, trending, карточки и графика; у новостей fetchedAt может быть null, а stale есть; у AI-разбора только cached и generatedAt; у market/global провенанса нет вообще.

  7. AI-разбор запрашивается только по явному клику. Это поход в LLM; кеш живёт час. Автозапрос при каждом открытии страницы жёг бы AI-прокси на хвостовых токенах. Кнопка «Обновить» внутри карточки перезапрашивает.

  8. Метрики в AI-карточке — слепок на момент генерации. analysis, metrics и generatedAt приходят одним конвертом из кеша и могут отставать от живой цены в левой колонке до часа. Карточка подписывается «метрики на момент генерации {generatedAt}»; живые числа берутся только из GET /tokens/:id. Индекс Fear & Greed внутри разбора (metrics.fearGreed) приходит числом без классификации — показываем как число, шкалу с подписью рисуем только в шапке каталога, где классификация есть.

  9. Все числа рассчитаны бэкендом. RSI, волатильность, тренд, дистанция до ATH — из metrics. Фронт ничего не пересчитывает и не округляет метрики самостоятельно, только форматирует.

  10. Пустые новости — не ошибка. При отсутствии CRYPTOCOMPARE_API_KEY или совпадений бэкенд отдаёт {items: [], matchedBySymbol: false, fetchedAt: null} с кодом 200 → рисуем пустое состояние. При matchedBySymbol: false и непустом списке заголовок блока обязан быть «Новости рынка», а не «Новости {SYMBOL}» — иначе мы выдаём общую ленту за новости токена. Рендерим все элементы, что пришли (бэкенд сам режет до 12).

  11. Обработка ошибок. 404 (token_not_found) → экран «токен не найден» со ссылкой назад в каталог. 502 / 503 (market_data_unavailable, market_data_upstream_error) → блок с текстом и кнопкой «Повторить». 400 (days_out_of_range) недостижим при UI-переключателе периодов, но обрабатывается тем же блоком. 401 не обрабатываем: интерцептор api.ts сам делает silent-refresh и, при провале, logout().

  12. Пустые поля правой колонки. У большинства токенов sentiment, community и developer частично или полностью null (CoinGecko их просто не знает). Карточка со всеми null не рендерится вовсе; внутри отрисованной карточки отдельное null-поле показывается как «—». Это же правило действует для Telegram, whitepaper и любых ссылок, которых нет.

  13. Язык контента. Интерфейс переведён на 9 локалей; тело AI-разбора приходит на русском (бэкенд промптит модель по-русски), новости CryptoCompare — на английском. Так и оставляем: передача локали в LLM потребовала бы правок бэкенда и кратно увеличила число вызовов модели.

2.3. UI/UX требования

Референс — coinmarketcap.com. Утверждённый макет лежит в репозитории: apps/docs/static/mockups/token-analytics-frontend.html (открывается в браузере как есть; в собранных доках доступен по /mockups/token-analytics-frontend.html). Макет — каркас раскладки, не пиксельный дизайн: цвета, шрифты и отступы берутся из MUI-темы проекта.

/tokens — каталог. Сверху пять карточек шапки рынка: капитализация с изменением за 24 ч, объём 24 ч, доминанс BTC, доминанс ETH, Fear & Greed (значение + классификация из valueClassification). Под ними — полоса трендовых токенов: иконка, имя, изменение за 24 ч; показываем первые 7 элементов ответа (бэкенд отдаёт всё, что вернул CoinGecko, — обычно 15). Далее поиск, затем таблица и пагинация.

Колонки таблицы: # (ранг), название с иконкой и тикером, цена, 1 ч %, 24 ч %, 7 д %, капитализация, объём 24 ч, circulating supply, спарклайн за 7 дней, кнопка «AI». Кликабельны заголовки капитализации, объёма и трёх %-колонок (см. правило 2.2.1). Клик по строке ведёт на /tokens/{id}; клик по кнопке «AI» ведёт туда же, но с ?ai=1 — страница токена при наличии этого параметра сразу запускает запрос разбора и скроллит к карточке (event.stopPropagation(), чтобы не сработал клик по строке).

/tokens/{id} — страница токена. Три колонки (300px | 1fr | 300px):

  • левая — иконка, имя, тикер, ранг, цена, изменение за 24 ч; плитки: капитализация, объём 24 ч, FDV, объём/капитализация, circulating supply, max supply; блок «динамика цены»: диапазон low/high за 24 ч, ATH и ATL с датами и процентами, строка изменений 1 ч / 7 д / 30 д / 1 г; ссылки (сайт, whitepaper, эксплореры, соцсети, GitHub), адреса контрактов из platforms с копированием, чипы категорий;
  • центральная — переключатель ряда, переключатель периода, график, карточка AI-разбора, список новостей;
  • правая — настроение сообщества (sentiment.upPercent / downPercent как Bullish/Bearish), метрики сообщества (Reddit, Telegram, watchlist), метрики разработки (коммиты за 4 недели, stars, forks, issues), описание проекта (первые ~3 строки + «показать полностью»).

Ряды и периоды графика. Один ответ GET /tokens/:id/chart содержит сразу три ряда — prices, marketCaps, volumes, все в формате [timestampMs, value][]. Переключатель ряда (цена / капитализация / объём) не делает нового запроса, а переключает ряд в уже полученных данных. Цена и капитализация рисуются как area, объём — как bar. Переключатель периода (24 ч, 7 д, 14 д, 1 М, 3 М, 6 М, 1 Г → days = 1, 7, 14, 30, 90, 180, 365) меняет ключ SWR и делает новый запрос; дефолт — 7 дней.

Внизу — плавающая кнопка «AI-разбор токена» (position: fixed, по центру снизу, поверх контента): скроллит к карточке разбора и запускает запрос, если он ещё не сделан; если разбор уже загружен — просто скроллит. Кнопка спроектирована так, чтобы вторым этапом (отдельное ТЗ) в неё добавилось поле ввода диалогового чата без переделки раскладки.

Скелетоны. Отдельные *Skeleton.tsx рядом с компонентами на MUI <Skeleton> — по образцу entities/statistic/ui/PreprodUserTableSkeleton.tsx. CircularProgress для таблиц и карточек не используем.

Адаптив. <1400px — правая колонка уезжает под центральную; <900px — одна колонка. Таблица каталога на мобильных горизонтально скроллится внутри TableContainer (как таблица пулов на главной), карточной альтернативы не делаем.

Чего не делаем (в отличие от CoinMarketCap), потому что данных нет: биржи и торговые пары, watchlist и избранное, кнопки покупки, low/high за 7 дней, число подписчиков в Twitter, реалтайм-обновление по WebSocket.


3. Техническая реализация

3.1. Архитектура

apps/frontend/src/
api/ai-analytics/ # дополняем существующий модуль, новый не создаём
ai-analytics.api.urls.ts # + 8 путей токен-ветки
ai-analytics.types.ts # + типы-зеркала DTO бэкенда
ai-analytics.api.service.ts # + static-методы

entities/ai-token/ # новая сущность (имя `token` занято платёжными токенами)
lib/
constants.ts # AI_TOKEN_I18N, периоды графика, режимы сортировки
useMarketGlobal.ts # SWR, auth-gated
useTokensList.ts # SWR: page, perPage, order | sort+dir
useTokenSearch.ts # SWR: /search → добор /tokens?ids=
useTokenTrending.ts # SWR
useTokenDetail.ts # SWR
useTokenChart.ts # SWR: id + days
useTokenNews.ts # SWR
model/
token-analytics.model.ts # Effector: AI-разбор (POST по клику)
ui/
MarketGlobalBar.tsx # + MarketGlobalBarSkeleton
TrendingStrip.tsx
TokenSearch.tsx
TokensTable.tsx # + TokensTableSkeleton
TokenSparkline.tsx # спарклайн 7 д из sparkline7d
TokenSummaryCard.tsx # левая колонка
TokenPriceChart.tsx # ApexCharts, datetime-ось
TokenAnalysisCard.tsx # AI-разбор
TokenAnalysisFab.tsx # плавающая кнопка
TokenNewsList.tsx
TokenCommunityCard.tsx # правая колонка
ProvenanceBadge.tsx # cached / stale / fetchedAt
TokenNotFound.tsx
index.ts

pages/tokens/index.tsx # каталог
pages/tokens/[id].tsx # страница токена

shared/translations/{EN,RU,CN,FR,HI,ID,PT,SR,VI}.json # блок aiToken.*
middleware.ts # /tokens в allowedPaths и pageToRedirect
app/components/VerticalNavigation/index.ts # пункт меню
app/components/HorizontalNavigation/index.ts # пункт меню

Разделение ответственности между Effector и SWR — как в уже написанных AI-фичах: серверные данные, которые просто читаются, живут в SWR; в Effector уходит только AI-разбор, потому что им управляют из двух мест (кнопка в строке таблицы каталога и кнопка на странице токена) и у него есть собственный жизненный цикл (запрошен / обновляется / ошибка).

3.2. Описание технической реализации

API-слой

ai-analytics.api.urls.ts дополняется восемью путями; пути с параметром — функциями вида tokenById(id), возвращающими путь /ai-analytics/tokens/{id}.

ai-analytics.types.ts — типы-зеркала DTO из apps/backend/src/aiAnalytics/dto/token-analytics.dto.ts. Кодогенерации из Swagger в проекте нет, типы пишутся руками. Имена, которые нельзя использовать: TokenMetrics — уже занят в этом же барреле (токен внутри пула ликвидности); метрики разбора называем TokenAnalyticsMetrics.

Провенанс: type Provenance = { cached: boolean; stale: boolean; fetchedAt: string }, у новостей — fetchedAt: string | null, у AI-разбора — только cached: boolean и generatedAt: string. ProvenanceBadge принимает { cached?: boolean; stale?: boolean; fetchedAt?: string | null }, поэтому подходит всем ответам.

Хуки данных

Все хуки auth-gated по образцу entities/ai-market-outlook/lib/useMarketOutlook.ts: пока user?.addr пуст, ключ SWR равен null и запрос не уходит. Ключи — массивы со всеми параметрами: ['ai/tokens', page, perPage, order, sort, dir], ['ai/token/chart', id, days].

useTokenSearch реализует правило 2.2.3: первый запрос /tokens/search, затем — при непустой выдаче — GET /tokens?ids=<csv> за ценами; ответы склеиваются по id. Дебаунс ввода — patronum/debounce (в проекте уже так сделано в entities/user/model/users-list.store.ts).

AI-разбор (Effector)

Модель повторяет entities/ai-chat/model/ai-chat.model.ts:

export const analysisRequested = createEvent<string>();   // tokenId
export const refreshRequested = createEvent();
export const tokenPageLeft = createEvent(); // размонтирование страницы токена

export const fetchTokenAnalyticsFx = createEffect((id: string) => AiAnalyticsApiService.getTokenAnalytics(id));

export const $tokenId = createStore<string | null>(null);
export const $analysis = createStore<TokenAnalyticsResponse | null>(null);
export const $errorKey = createStore<TokenAnalyticsErrorKey | null>(null);
export const $isLoading = fetchTokenAnalyticsFx.pending;

Ошибка маппится не в текст, а в ключ перевода (toErrorKey) — как в существующей модели.

$analysis и $errorKey сбрасываются при смене tokenId и при уходе со страницы: роут /tokens/[id] переиспользует один и тот же компонент при клиентской навигации между токенами, и без сброса пользователь увидит разбор предыдущего токена. Отдельного события «закрыть разбор» нет — карточка не закрывается, она часть страницы.

График

TokenPriceChart рендерит ReactApexcharts из @core/components/react-apexcharts/reactApexCharts (обёртка через next/dynamic с ssr: false — обязательна, иначе window is not defined).

В рантайме работает ApexCharts 4.7.0 — и в dev, и в проде. В next.config.js есть webpack-алиас apexcharts → ./node_modules/apexcharts-clevision (форк 3.28.5), но он мёртвый: dev запускается как next dev --turbo, а на Turbopack секция webpack() не применяется вообще, и resolveAlias в блоке turbopack не задан. Косвенное подтверждение — react-apexcharts@1.8.0 объявляет peer apexcharts >= 4.0.0. Практический вывод: опции пишем по документации v4, а фрагменты, скопированные из старых графиков проекта, проверяем глазами в браузере — они писались под форк v3 и часть опций могла молча перестать применяться.

prices приходят как [timestampMs, value][] — это готовый вход для xaxis.type: 'datetime', преобразование не нужно. Цвета берём из useTheme(); цвет ряда цены зависит от знака изменения за выбранный период. Период хранится в локальном useState (в проекте нет прецедента синхронизации периода с router.query); при смене периода — keepPreviousData: true, чтобы график не мигал. Тултип проверяем в обеих темах: глобальный CSS (shared/styles/globals.css) глушит тултип ApexCharts в светлой теме.

Спарклайн в строке таблицы отдельного запроса не требует: sparkline7d: number[] | null уже приходит в элементе списка.

Иконки токенов

Только <img> или MUI Avatar с fallback-инициалами при image: null. next/image использовать нельзя: в next.config.js нет блока images.remotePatterns, а логотипы приходят с coin-images.coingecko.com — компонент упадёт в рантайме. Правило @next/next/no-img-element в проекте отключено, весь остальной код так и делает.

Описание проекта

TokenDetailDto.description — сырое поле CoinGecko и содержит HTML-разметку (<a href=…>). Рендерим как обычный текст с обрезкой и кнопкой «показать полностью»; dangerouslySetInnerHTML не используем.

Ключи переводов

Ключи объявляются константой рядом с сущностью (entities/ai-token/lib/constants.ts, как AI_CHAT_I18N в entities/ai-chat/lib/constants.ts) и добавляются плоскими ключами во все девять файлов shared/translations/*.json. Состав блока aiToken.*:

  • навигация и заголовки: navTitle, listTitle, tokenTitle;
  • шапка рынка: marketCap, volume24h, btcDominance, ethDominance, fearGreed, trending;
  • таблица: search, colRank, colName, colPrice, colChange1h, colChange24h, colChange7d, colMarketCap, colVolume24h, colCirculatingSupply, colSparkline, sortHintTop250, perPage, prevPage, nextPage, emptySearch;
  • карточка токена: fdv, volumeToMarketCap, totalSupply, maxSupply, low24h, high24h, ath, atl, links, contracts, categories, about, showMore, genesisDate, community, developer, sentimentBullish, sentimentBearish, noData;
  • график: chartPrice, chartMarketCap, chartVolume, периоды period1dperiod1y;
  • AI-разбор: analysisTitle, analysisCta, analysisRefresh, analysisLoading, verdictBullish, verdictBearish, verdictNeutral, highlights, risks, scenarios, metricsAtGeneration, disclaimer;
  • новости: newsTitle, newsMarketTitle («Новости рынка» — при matchedBySymbol: false), newsEmpty;
  • состояния: staleWarning (с плейсхолдером {time}), errorUnavailable, errorRetry, tokenNotFound.

Изменения в существующем коде

  1. src/middleware.ts — добавить '/tokens' в оба списка: в allowedPaths (строка 5) и в pageToRedirect (строка 21). Только первого недостаточно: на productionPolygon неактивированного пользователя унесёт на /career, а по продуктовому решению раздел открыт любому залогиненному.
  2. src/app/components/VerticalNavigation/index.ts и HorizontalNavigation — пункт «Токены». Ключ перевода — Навигация.Токены; Навигация.Токен занят админским разделом /admin/token.
  3. src/shared/translations/*.json — блок aiToken.* во всех девяти локалях.
  4. apps/docs/sidebars.ts — регистрация этого ТЗ в tzSidebar.
  5. src/entities/ai-market-outlook/index.ts — расширить баррель: сейчас наружу торчит только MarketOutlookCard, а нам нужны FearGreedGauge, OutlookBiasBadge и OutlookScenarioList.

Переиспользование существующих компонентов

КомпонентКак используем
entities/ai-chat/ui/AiMarkdownКак есть — рендер поля markdown из AI-разбора.
ai-market-outlook/OutlookScenarioListКак есть — форма scenarios в токен-разборе совпадает (trigger, direction, range, note).
ai-market-outlook/OutlookBiasBadgeКак есть — verdict совпадает с bias (bullish|bearish|neutral).
ai-market-outlook/FearGreedGaugeТребует правки типа: принимает объект с обязательным timestamp, а market/global отдаёт только {value, valueClassification} → ослабить пропс до Pick<…,'value'|'valueClassification'>. Визуально это линейная шкала, а не циферблат; используем её как есть, циферблат из макета не делаем.
entities/ai-chat/ui/AiChatPanelНе используем: разбор живёт в карточке страницы, а не в плавающей панели. Архитектура модели — копируется.

Тестирование

Уровень покрытия — как у ai-chat и ai-market-outlook (там покрыто всё):

  • API-сервис: каждый метод — корректный URL и payload (axios замокан глобально).
  • Хуки: auth-gate (ключ null без пользователя), склейка поиска с добором цен.
  • Effector-модель: fork / allSettled — запрос, успех, ошибка → ключ перевода, refreshRequested.
  • UI: состояния loading / error + retry / empty / success для таблицы, карточки разбора и новостей; заголовок «Новости рынка» при matchedBySymbol: false; warning-чип при stale: true.

Инфраструктурные моки, которые надо завести заранее: react-apexcharts глобально не замокан (тест компонента с графиком упадёт), а глобальный мок next/router не содержит query и replace — тест страницы /tokens/[id] обязан перекрыть его локально.


4. Проблемы и компромиссы

4.1. Известные ограничения

  1. Сортировка по изменению цены — только внутри топ-250.

    • Ограничение бэкенда, унаследованное от CoinGecko: order по %-изменению там нет, сортировка делается в памяти по кешированному пулу топ-250.
    • Влияние: за пределами топ-250 бэкенд возвращает 200 с пустым items (не ошибку). В UI пагинация в %-режиме обрезается до ceil(250 / perPage) страниц (правило 2.2.2), под %-колонками — подсказка, что ранжирование идёт только внутри топ-250 по капитализации.
  2. Ответ списка не содержит общего числа токенов.

    • TokenListResponseDto отдаёт только items, page, perPage; добавить total нельзя — ТЗ frontend-only.
    • Влияние: нумерованная пагинация MUI (<Pagination count>) невозможна, делаем «назад/вперёд» (правило 2.2.2).
  3. Нет реалтайма.

    • WebSocket в модуле нет, обновление — опросом; интервалы упираются в TTL бэкенда (60 с для списка, 300 с для карточки и графика).
    • Влияние: цена «дышит» раз в минуту, а не тикает. Для аналитического раздела приемлемо.
  4. Метрики AI-разбора отстают от живых.

    • analysis и metrics лежат в кеше одним конвертом на час.
    • Влияние: цена в метриках разбора может расходиться с ценой в левой колонке. Лечится подписью «на момент генерации», а не синхронизацией.
  5. Фолбэк AI не помечен флагом.

    • Когда LLM недоступен, бэкенд отдаёт 200 с детерминированным разбором; отличить его можно только по тексту risks[0].
    • Влияние: отдельного состояния «AI недоступен» в UI не делаем — рендерим то, что пришло. Эвристика по строке хрупкая и сломается при первом же изменении промпта.
  6. График — максимум 365 дней.

    • days=max бэкенд не поддерживает.
    • Влияние: периода «All» в переключателе нет.
  7. Мёртвый webpack-алиас ApexCharts.

    • next.config.js подменяет apexcharts форком 3.28.5, но на Turbopack секция webpack() не применяется — в рантайме работает 4.7.0.
    • Влияние: опции графика пишем по v4; код существующих графиков проекта нельзя копировать без визуальной проверки.
  8. middleware.isStaticResource считает статикой любой путь, содержащий .js / .css и т. п.

    • TOKEN_ID_REGEX на бэкенде допускает точки в id, поэтому теоретический токен с id вида foo.js проскочит мимо развилки авторизации в middleware.
    • Влияние: ничтожное (таких id у CoinGecko не встречается), но зафиксировано.

4.2. Технический долг

  • Фильтр по категориям токенов (требует блокировки %-сортировки при активном фильтре).
  • Карточная раскладка таблицы на мобильных вместо горизонтального скролла.
  • Синхронизация периода графика и параметров сортировки с router.query (шарящиеся ссылки).
  • Диалоговый AI-чат по токену — отдельное ТЗ, требует нового эндпоинта на бэкенде.
  • Локализация тела AI-разбора (сейчас всегда русский при любой локали интерфейса).
  • Почистить мёртвый алиас apexcharts / react-apexcharts$ в next.config.js и выпилить зависимость apexcharts-clevision: на Turbopack они ни на что не влияют, но вводят в заблуждение.
  • Попросить у бэкенда total в ответе списка, чтобы вернуть нумерованную пагинацию.

4.3. Риски

РискМитигация
next/image на логотипах CoinGecko падает в рантайме (нет images.remotePatterns)Запрет на next/image зафиксирован в 3.2; используем <img> / Avatar с fallback-инициалами.
Мёртвый алиас apexcharts-clevision: код, написанный по докам v3 или скопированный из старых графиков проекта, молча не применится — в рантайме 4.7.0Опции пишем по v4; результат проверяем в браузере в светлой и тёмной теме (глобальный CSS глушит тултип в светлой).
Тесты падают по инфраструктуре: react-apexcharts не замокан, мок next/router без queryМоки заводятся первым шагом реализации, до написания компонентов.
Открытие карточки токена = до четырёх запросов (детали, график, новости, разбор) → упираемся в квоту CoinGeckorefreshInterval ≥ TTL, revalidateOnFocus: false, keepPreviousData: true, AI-разбор только по клику.
Наивная реализация сортировки отправит sort вместе с category и получит 400Фильтр категорий в v1 отсутствует; режимы order и sort взаимоисключающие по построению.

5. Вопросы на дополнительное обсуждение

  • AI-чат: диалоговый чат с историей или одноразовый разбор? → Одноразовый разбор (бэкенд другого не умеет). UI кнопки и карточки проектируется так, чтобы поле ввода добавилось вторым этапом без переделки.
  • Доступ: только активированные или любой залогиненный? → Любой залогиненный: /tokens добавляется и в allowedPaths, и в pageToRedirect.
  • Язык AI-разбора при нерусской локали? → Оставляем как есть (русский разбор, английские новости).
  • Загружать разбор автоматически при открытии страницы? → Нет, только по клику: каждый холодный вызов — поход в LLM.

6. План реализации

6.1. Этапы разработки

  • Этап 1: API-слой — пути, типы-зеркала DTO, методы сервиса, тесты сервиса.
  • Этап 2: Инфраструктура тестов — моки react-apexcharts и next/router.
  • Этап 3: SWR-хуки сущности ai-token (включая двухзапросный поиск), тесты auth-gate.
  • Этап 4: Каталог /tokens — шапка рынка, тренды, поиск, таблица с сортировкой и пагинацией, спарклайны, скелетоны.
  • Этап 5: Страница /tokens/[id] — три колонки, график, новости, состояния 404 / 5xx.
  • Этап 6: AI-разбор — Effector-модель, карточка, плавающая кнопка, вход из строки таблицы.
  • Этап 7: Интеграция — middleware, навигация, i18n во всех девяти локалях.
  • Этап 8: Тесты UI и моделей, прогон lint и prettier, ручная проверка в светлой и тёмной теме.

6.2. Критические зависимости

  • Бэкенд-ветка токен-аналитики — готова, но ещё не в main: живёт в feature/token-ai-analytics. Фронтенд разрабатывается в той же ветке (или в ветке от неё); в main нет ни модуля aiAnalytics, ни фронтового src/api/ai-analytics/, на который это ТЗ опирается.
  • CRYPTOCOMPARE_API_KEY в окружении — нужен для новостей; без него блок отдаёт пустое состояние (не ошибка).
  • AI-прокси (OPENAI_BASE_URL) — нужен для разбора; без него бэкенд отдаёт детерминированный фолбэк с кодом 200.

7. Тестирование

Пирамида — как у существующих AI-фич: юнит-тесты API-сервиса (URL и payload), тесты SWR-хуков (auth-gate, склейка поиска), тесты Effector-модели через fork/allSettled (запрос, успех, ошибка в ключ перевода, refresh), тесты UI-состояний (loading, error + retry, empty, success) для таблицы, карточки разбора и новостей.

Отдельно проверяются правила провенанса: warning-чип появляется только при stale: true и не появляется при cached: true; заголовок блока новостей меняется на «Новости рынка» при matchedBySymbol: false.

Запуск: pnpm --filter=frontend test (конфиг — jest.config.js, не jest.config.ts).


8. Документация

  • API документация (Swagger) — не требуется, бэкенд не меняется.
  • Обновление README — не требуется.
  • Обновление Engineering docs — раздел о фронтенде токен-аналитики в apps/docs/docs/engineering/.
  • Регистрация этого ТЗ в apps/docs/sidebars.ts (tzSidebar).

9. Ссылки

  • ТЗ бэкенда: apps/docs/docs/tz/active/token-analytics.md
  • ТЗ AI-аналитики (рынок и пулы): apps/docs/docs/tz/active/ai-analytics.md
  • Документация модуля: apps/docs/docs/engineering/Сервисы/backend/ai-analytics.md
  • DTO бэкенда (источник типов): apps/backend/src/aiAnalytics/dto/token-analytics.dto.ts
  • Референс интерфейса: https://coinmarketcap.com
  • Утверждённый макет: apps/docs/static/mockups/token-analytics-frontend.html