docs: add specification for frontend FSD market

This commit is contained in:
Sergey Krylov 2026-06-20 19:57:37 +03:00
parent 20f0efa083
commit 40d7792b9e
3 changed files with 364 additions and 0 deletions

View File

@ -0,0 +1,194 @@
# 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 синхронизированы с новой структурой.

View File

@ -0,0 +1,125 @@
# Frontend FSD Market Pages
Дата: 2026-06-20
Статус: спецификация
## Контекст
Предыдущие FSD-итерации мигрировали broker-домен (`frontend-fsd-broker-pilot`), shared-слой
(`frontend-fsd-shared-layer`), entities stock/bond/portfolio (`fsd-entities-migration`) и app-слой с
auth (`frontend-fsd-app-auth`).
Рыночные страницы (StockPage, BondPage, HomePage) и их компоненты (StockDetails, BondDetails,
PriceChart, SearchBar) остались в исторической технической структуре (`components/`, `hooks/`,
плоские `pages/`). Это единственный крупный блок кода вне FSD-структуры, который активно
используется пользователями.
## Иерархия источников
- Текущая задача пользователя: завершить FSD-миграцию рыночных страниц.
- `docs/inbox.md`: направление на постепенную FSD-миграцию.
- `docs/features/frontend-fsd-broker-pilot/spec.md`: эталонный FSD-шаблон.
## Цель
Перевести рыночные страницы (stock, bond, home) и связанные компоненты в FSD-структуру, следуя
паттерну, установленному broker-пилотом — через widgets, entities и page entrypoints с
совместимыми shim-файлами.
## Область изменений
Фича охватывает только frontend-код рыночных страниц:
- StockPage, BondPage, HomePage — миграция из плоских `pages/` в `pages/stock/`, `pages/bond/`,
`pages/home/` с FSD-структурой;
- StockDetails, BondDetails — миграция из `components/` в `widgets/stock-details/`,
`widgets/bond-details/`;
- PriceChart — миграция из `components/` в `widgets/price-chart/`;
- SearchBar + useSearch — миграция в `widgets/search-bar/` + новый entity `entities/search/`;
- таблица дивидендов — вынос из StockPage в новый виджет `widgets/dividends-table/`;
- shim-файлы для обратной совместимости;
- обновление роутинга и layout для импортов из новых FSD-точек входа.
## Требования
### 1. Все компоненты следуют FSD-слоям
- entities — data-слой (API + query hooks). Новый: `entities/search/`.
- widgets — бизнес-композиция UI. Новые: `widgets/stock-details/`, `widgets/bond-details/`,
`widgets/price-chart/`, `widgets/search-bar/`, `widgets/dividends-table/`.
- pages — тонкие entrypoints. Новые: `pages/stock/`, `pages/bond/`, `pages/home/`.
### 1.1 Ownership данных остаётся у pages и entities
- `pages/stock` и `pages/bond` владеют route params и orchestration загрузки данных через
существующие entity hooks (`useStock`, `useBond`, `useStockCandles`, `useBondCandles`,
`useStockDividends`).
- `widgets/stock-details`, `widgets/bond-details`, `widgets/price-chart`,
`widgets/dividends-table` остаются презентационно-композиционными и получают готовые данные
через props.
- `widgets/search-bar` владеет локальным UI-состоянием поиска (input, debounced query, dropdown),
а query-hook `useSearch` размещается в `entities/search/model/`.
- `pages/home` остаётся статическим page entrypoint без новой data-логики.
### 2. Границы срезов явные через public API
Каждый widget и entity имеет `index.ts` barrel с контролируемым экспортом.
Внешний код импортирует market pages/widgets/entities только через их public API
(`@/pages/stock`, `@/widgets/search-bar`, `@/entities/search` и т.д.). Deep imports во внутренние
`model/`, `api/`, `ui/` директории не считаются контрактом для внешних потребителей.
### 3. Поведение UI не меняется
Миграция не добавляет новую функциональность и не пересматривает UX.
### 4. Совместимость через shim-файлы
Старые пути (`components/StockDetails.tsx`, `hooks/useSearch.ts`, `pages/StockPage.tsx`) становятся
re-export shim-файлами, импортирующими из новых FSD-точек входа.
### 5. Тесты следуют новым границам
Тесты перемещаются вместе с компонентами в соответствующие widget/page/ui-директории.
### 6. Общая инфраструктура остаётся в shared
HTTP-клиент и типы остаются в `shared/api/`. PriceChart использует `lightweight-charts`
непосредственно, без дополнительных абстракций.
## Ограничения
- Изменения ограничены frontend-пакетом.
- Допускаются связанные обновления опубликованной frontend-документации и ADR, если они отражают
новую FSD-структуру market pages.
- Не вводятся ESLint import boundaries или import guards.
- Не мигрируются portfolio и screener страницы.
- Не удаляется мёртвый код (old `pages/broker/`, `api/broker.ts`).
- Не меняется backend API, Swagger, codegen.
## Acceptance Criteria
- StockDetails доступен через `widgets/stock-details`.
- BondDetails доступен через `widgets/bond-details`.
- PriceChart доступен через `widgets/price-chart`.
- SearchBar доступен через `widgets/search-bar` и использует query/public API из `entities/search`.
- DividendsTable вынесен из StockPage в `widgets/dividends-table`.
- StockPage доступен через `pages/stock`.
- BondPage доступен через `pages/bond`.
- HomePage доступен через `pages/home`.
- Все старые пути (components/*, hooks/useSearch, pages/StockPage/BondPage/HomePage) —
re-export shims, импортирующие из новых FSD-точек входа.
- `app/routing/AppRoutes.tsx` и `app/layouts/AppLayout.tsx` импортируют из новых FSD-точек входа.
- `pages/stock` и `pages/bond` сохраняют orchestration загрузки market data и передают widgets
готовые props без переноса route/query orchestration внутрь widgets.
- Frontend lint, tests и build проходят.
- Пользовательское поведение эквивалентно.
- ADR и документация обновлены.
## Не цели
- Миграция portfolio, screener и остальных доменов.
- Введение жёстких правил импортов.
- Удаление мёртвого кода (old `pages/broker/`, `api/broker.ts`).
- Переписывание стилей или визуального дизайна.
- Добавление новой функциональности.

View File

@ -0,0 +1,45 @@
# Frontend FSD Market Pages — задачи
Статус: выполнено
Подробные шаги, карта файлов и verification-команды находятся в [plan.md](plan.md).
Финальная verification:
- `npm test -w apps/frontend` — PASS (`42` files, `230` tests)
- `npm run lint -w apps/frontend` — PASS
- `npm run build -w apps/frontend` — PASS
- `npm run build -w apps/docs` — PASS
Deep import verification:
- `rg -n "@/entities/search/model|@/widgets/search-bar/ui|@/widgets/price-chart/ui|@/widgets/stock-details/ui|@/widgets/bond-details/ui|@/widgets/dividends-table/ui|@/pages/home/ui|@/pages/stock/ui|@/pages/bond/ui" apps/frontend/src apps/docs/docs` — no matches
## 1. Search slice
- [x] Создать `entities/search` с `useSearch` и public API.
- [x] Перенести `SearchBar` в `widgets/search-bar`.
- [x] Оставить `hooks/useSearch.ts` и `components/SearchBar.tsx` как re-export shims.
- [x] Переключить `AppLayout` на `@/widgets/search-bar`.
- [x] Прогнать `useSearch` и `SearchBar` тесты после переноса.
## 2. Market widgets
- [x] Перенести `PriceChart` в `widgets/price-chart`.
- [x] Перенести `StockDetails` в `widgets/stock-details`.
- [x] Перенести `BondDetails` в `widgets/bond-details`.
- [x] Вынести таблицу дивидендов в `widgets/dividends-table`.
- [x] Привязать widget tests к новым slice boundaries.
## 3. Market pages
- [x] Создать `pages/home`, `pages/stock`, `pages/bond` с public API.
- [x] Сохранить orchestration route params и data loading в `pages/stock` и `pages/bond`.
- [x] Переключить `AppRoutes` на новые page entrypoints.
- [x] Оставить `pages/HomePage.tsx`, `StockPage.tsx`, `BondPage.tsx` как re-export shims.
- [x] Прогнать page tests после переключения маршрутов.
## 4. Docs и verification
- [x] Добавить ADR по market FSD migration.
- [x] Обновить frontend docs (`overview.md`, `components.md`, `hooks.md`, `routes.md`).
- [x] Проверить отсутствие внешних deep imports в новые `model/` и `ui/` директории.
- [x] Выполнить финальную проверку: frontend `test`, `lint`, `build`, docs `build`.