# Frontend FSD Market Pages Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Перевести market pages (`home`, `stock`, `bond`) и связанные market widgets в FSD-структуру без изменения пользовательского поведения и с сохранением совместимости через shim-файлы. **Architecture:** Route params и orchestration загрузки данных остаются в `pages/*`, а UI-композиция переносится в `widgets/*` с явным public API. Search query-hook выделяется в отдельный `entities/search` slice, старые entrypoints в `components/`, `hooks/` и плоских `pages/` превращаются в re-export shims, чтобы маршруты и тесты можно было переключать постепенно. **Tech Stack:** React 18, TypeScript, React Router v6, TanStack Query v5, lightweight-charts v4, Vitest, Testing Library, ESLint, Vite, Docusaurus. --- ## Базовое состояние - Feature branch: `codex/frontend-fsd-market-pages` - Baseline verification: - `npm test -w apps/frontend` — PASS (`36` test files, `198` tests) - `npm run lint -w apps/frontend` — PASS - `npm run build -w apps/frontend` — PASS ## Карта файлов ### Create - `apps/frontend/src/entities/search/index.ts` — public API search slice - `apps/frontend/src/entities/search/model/useSearch.ts` — query hook для поиска бумаг - `apps/frontend/src/entities/search/model/useSearch.test.tsx` — тесты query hook после переноса - `apps/frontend/src/widgets/search-bar/index.ts` — public API search widget - `apps/frontend/src/widgets/search-bar/ui/SearchBar.tsx` — UI поиска с локальным state/dropdown - `apps/frontend/src/widgets/search-bar/ui/SearchBar.test.tsx` — widget tests после переноса - `apps/frontend/src/widgets/price-chart/index.ts` — public API chart widget - `apps/frontend/src/widgets/price-chart/ui/PriceChart.tsx` — chart widget implementation - `apps/frontend/src/widgets/price-chart/ui/PriceChart.test.tsx` — chart tests после переноса - `apps/frontend/src/widgets/stock-details/index.ts` — public API stock details widget - `apps/frontend/src/widgets/stock-details/ui/StockDetails.tsx` — stock details widget - `apps/frontend/src/widgets/stock-details/ui/StockDetails.test.tsx` — widget tests после переноса - `apps/frontend/src/widgets/bond-details/index.ts` — public API bond details widget - `apps/frontend/src/widgets/bond-details/ui/BondDetails.tsx` — bond details widget - `apps/frontend/src/widgets/bond-details/ui/BondDetails.test.tsx` — widget tests после переноса - `apps/frontend/src/widgets/dividends-table/index.ts` — public API dividends widget - `apps/frontend/src/widgets/dividends-table/ui/DividendsTable.tsx` — dividends table extracted from StockPage - `apps/frontend/src/pages/home/index.ts` — public API home page - `apps/frontend/src/pages/home/ui/HomePage.tsx` — home page entrypoint - `apps/frontend/src/pages/stock/index.ts` — public API stock page - `apps/frontend/src/pages/stock/ui/StockPage.tsx` — stock page entrypoint/orchestration - `apps/frontend/src/pages/bond/index.ts` — public API bond page - `apps/frontend/src/pages/bond/ui/BondPage.tsx` — bond page entrypoint/orchestration ### Modify - `apps/frontend/src/app/routing/AppRoutes.tsx` — переключить route imports на `@/pages/home`, `@/pages/stock`, `@/pages/bond` - `apps/frontend/src/app/layouts/AppLayout.tsx` — переключить импорт SearchBar на `@/widgets/search-bar` - `apps/frontend/src/components/SearchBar.tsx` — re-export shim - `apps/frontend/src/components/SearchBar.test.tsx` — перенести/обновить импорт под новый widget path - `apps/frontend/src/components/PriceChart.tsx` — re-export shim - `apps/frontend/src/components/PriceChart.test.tsx` — перенести/обновить импорт под новый widget path - `apps/frontend/src/components/StockDetails.tsx` — re-export shim - `apps/frontend/src/components/StockDetails.test.tsx` — перенести/обновить импорт под новый widget path - `apps/frontend/src/components/BondDetails.tsx` — re-export shim - `apps/frontend/src/components/BondDetails.test.tsx` — перенести/обновить импорт под новый widget path - `apps/frontend/src/hooks/useSearch.ts` — re-export shim - `apps/frontend/src/hooks/useSearch.test.tsx` — перенести/обновить импорт под новый entity path - `apps/frontend/src/pages/HomePage.tsx` — re-export shim - `apps/frontend/src/pages/HomePage.test.tsx` — перенести/обновить импорт под новый page path - `apps/frontend/src/pages/StockPage.tsx` — re-export shim - `apps/frontend/src/pages/StockPage.test.tsx` — перенести/обновить импорт под новый page path - `apps/frontend/src/pages/BondPage.tsx` — re-export shim - `apps/frontend/src/pages/BondPage.test.tsx` — перенести/обновить импорт под новый page path - `apps/docs/docs/frontend/overview.md` — отразить FSD migration market pages - `apps/docs/docs/frontend/components.md` — обновить описание widget/component boundaries - `apps/docs/docs/frontend/hooks.md` — убрать market search из legacy hooks как primary location - `apps/docs/docs/adr/ADR-014-frontend-fsd-market-pages.md` — зафиксировать стратегию второй market-итерации FSD ## Целевая структура ```text apps/frontend/src/ ├── entities/ │ └── search/ │ ├── index.ts │ └── model/ │ ├── useSearch.ts │ └── useSearch.test.tsx ├── widgets/ │ ├── search-bar/ │ │ ├── index.ts │ │ └── ui/ │ │ ├── SearchBar.tsx │ │ └── SearchBar.test.tsx │ ├── price-chart/ │ ├── stock-details/ │ ├── bond-details/ │ └── dividends-table/ └── pages/ ├── home/ │ ├── index.ts │ └── ui/HomePage.tsx ├── stock/ │ ├── index.ts │ └── ui/StockPage.tsx └── bond/ ├── index.ts └── ui/BondPage.tsx ``` ## Потоки данных ### Stock page ```text Route /stocks/:secid → pages/stock/ui/StockPage.tsx → useStock(secid) + useStockCandles(secid, interval, from, till) + useStockDividends(secid) → widgets/stock-details + widgets/price-chart + widgets/dividends-table ``` ### Bond page ```text Route /bonds/:secid → pages/bond/ui/BondPage.tsx → useBond(secid) + useBondCandles(secid, interval, from, till) → widgets/bond-details + widgets/price-chart ``` ### Search flow ```text AppLayout → widgets/search-bar/ui/SearchBar.tsx → entities/search/model/useSearch(debouncedQuery) → shared/api/client.searchSecurities(query) → navigate('/stocks/:secid' | '/bonds/:secid') ``` ## Этапы реализации ### Phase 1: Search entity и SearchBar widget 1. Создать `entities/search/model/useSearch.ts`, перенеся текущий hook из `src/hooks/useSearch.ts` без изменения `queryKey`, `enabled`, `staleTime` и response mapping. 2. Создать `entities/search/index.ts` и экспортировать `useSearch` только через public API. 3. Перенести `SearchBar.tsx` в `widgets/search-bar/ui/SearchBar.tsx`, сохранив локальное состояние `query`, `debounced`, `open`, обработчик клика вне dropdown и navigate-логику. 4. Добавить `widgets/search-bar/index.ts`. 5. Старые `src/hooks/useSearch.ts` и `src/components/SearchBar.tsx` превратить в re-export shims. 6. Переместить и адаптировать тесты `useSearch.test.tsx` и `SearchBar.test.tsx` так, чтобы они проверяли новые public entrypoints. 7. Переключить `app/layouts/AppLayout.tsx` на импорт `@/widgets/search-bar`. ### Phase 2: Market widgets 1. Перенести `PriceChart.tsx` в `widgets/price-chart/ui/PriceChart.tsx` без изменения работы с `lightweight-charts`. 2. Перенести `StockDetails.tsx` и `BondDetails.tsx` в `widgets/stock-details/ui/StockDetails.tsx` и `widgets/bond-details/ui/BondDetails.tsx`. 3. Создать `widgets/dividends-table/ui/DividendsTable.tsx`, выделив таблицу дивидендов из `StockPage.tsx` без изменения разметки, заголовков и форматирования значений. 4. Добавить `index.ts` для каждого widget slice. 5. Старые `src/components/PriceChart.tsx`, `StockDetails.tsx`, `BondDetails.tsx` превратить в re-export shims. 6. Перенести widget tests рядом с новыми реализациями и оставить старые тестовые файлы либо как shim-import consumers, либо обновить их на новые public entrypoints без дублирования покрытия. ### Phase 3: Market pages и route imports 1. Создать `pages/home/ui/HomePage.tsx` и `pages/home/index.ts`, сохранив текущее статическое содержимое главной страницы. 2. Создать `pages/stock/ui/StockPage.tsx` и `pages/stock/index.ts`, оставив в page orchestration: - чтение `secid` из `useParams` - расчёт `from`/`till` - вызовы `useStock`, `useStockCandles`, `useStockDividends` - состояния loading/not-found - передачу готовых props в widgets 3. Создать `pages/bond/ui/BondPage.tsx` и `pages/bond/index.ts` по той же схеме для `useBond` и `useBondCandles`. 4. Переключить `app/routing/AppRoutes.tsx` на новые page public entrypoints. 5. Старые `src/pages/HomePage.tsx`, `StockPage.tsx`, `BondPage.tsx` превратить в re-export shims. 6. Обновить page tests, чтобы они импортировали новые page entrypoints и подтверждали эквивалентность поведения. ### Phase 4: Documentation, ADR и финальная verification 1. Создать `apps/docs/docs/adr/ADR-014-frontend-fsd-market-pages.md` с секциями Context / Options / Decision / Consequences. 2. Обновить `apps/docs/docs/frontend/overview.md`, `components.md`, `hooks.md`, чтобы опубликованная документация отражала market FSD slices и shim-стратегию coexistence. 3. Проверить, что новые public API не требуют deep imports из `model/` или `ui/` во внешнем коде. 4. Выполнить финальную verification: - `npm test -w apps/frontend` - `npm run lint -w apps/frontend` - `npm run build -w apps/frontend` - `npm run build -w apps/docs` ## Риски и контрольные точки - `PriceChart` чувствителен к ref/effect lifecycle. При переносе важно не менять cleanup и порядок инициализации chart series. - `SearchBar` используется в `AppLayout`, поэтому ошибки в импорт-пути сразу затронут почти все route-level tests. - `StockPage`/`BondPage` вычисляют даты inline. В этой фиче это остаётся в page-layer и не выносится в shared util, чтобы не расширять scope. - Legacy shims должны оставаться тонкими re-export файлами без дополнительной логики. ## Критерии завершения плана - Все acceptance criteria из `spec.md` сопоставлены задачам реализации. - Новая market-структура использует только public API между `pages`, `widgets` и `entities`. - Frontend tests, lint и build проходят после миграции. - ADR и frontend docs синхронизированы с новой структурой.