195 lines
12 KiB
Markdown
195 lines
12 KiB
Markdown
# 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 синхронизированы с новой структурой.
|