From 40d7792b9e78c3aafca8b76e93b1ef77f279ac04 Mon Sep 17 00:00:00 2001 From: Sergey Krylov Date: Sat, 20 Jun 2026 19:57:37 +0300 Subject: [PATCH] docs: add specification for frontend FSD market --- .../frontend-fsd-market-pages/plan.md | 194 ++++++++++++++++++ .../frontend-fsd-market-pages/spec.md | 125 +++++++++++ .../frontend-fsd-market-pages/tasks.md | 45 ++++ 3 files changed, 364 insertions(+) create mode 100644 docs/features/frontend-fsd-market-pages/plan.md create mode 100644 docs/features/frontend-fsd-market-pages/spec.md create mode 100644 docs/features/frontend-fsd-market-pages/tasks.md diff --git a/docs/features/frontend-fsd-market-pages/plan.md b/docs/features/frontend-fsd-market-pages/plan.md new file mode 100644 index 0000000..84bebff --- /dev/null +++ b/docs/features/frontend-fsd-market-pages/plan.md @@ -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 синхронизированы с новой структурой. diff --git a/docs/features/frontend-fsd-market-pages/spec.md b/docs/features/frontend-fsd-market-pages/spec.md new file mode 100644 index 0000000..da77348 --- /dev/null +++ b/docs/features/frontend-fsd-market-pages/spec.md @@ -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`). +- Переписывание стилей или визуального дизайна. +- Добавление новой функциональности. diff --git a/docs/features/frontend-fsd-market-pages/tasks.md b/docs/features/frontend-fsd-market-pages/tasks.md new file mode 100644 index 0000000..20e1351 --- /dev/null +++ b/docs/features/frontend-fsd-market-pages/tasks.md @@ -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`.