380 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Frontend FSD Shared Layer — 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:** Создать FSD-слой `shared/` с API-инфраструктурой и truly generic UI-компонентами, используя стратегию coexistence (re-export shims) как в broker pilot.
**Architecture:** Копирование файлов из `api/``shared/api/` и `components/SkeletonBlock`, `components/TableSkeleton``shared/ui/`, превращение исходников в re-export shims, затем массовое переключение импортов на `@/shared/...`. Все изменения чисто структурные — поведение не меняется.
**Tech Stack:** React 18, TypeScript, Vite, Vitest
---
## Карта файлов
### Будут созданы
- `src/shared/api/index.ts` — barrel для shared/api
- `src/shared/api/client.ts` — базовый HTTP-клиент + доменные API-функции (копия api/client.ts)
- `src/shared/api/client.test.ts` — тесты клиента (копия)
- `src/shared/api/responses.ts` — все типы ответов (копия api/responses.ts)
- `src/shared/api/types.ts` — generated OpenAPI types (копия api/types.ts)
- `src/shared/ui/index.ts` — barrel для shared/ui
- `src/shared/ui/SkeletonBlock.tsx` — скелетон (копия components/SkeletonBlock.tsx)
- `src/shared/ui/TableSkeleton.tsx` — табличный скелетон (копия components/TableSkeleton.tsx)
### Будут изменены (становятся re-export shims)
- `src/api/client.ts`
- `src/api/responses.ts`
- `src/api/types.ts`
- `src/components/SkeletonBlock.tsx`
- `src/components/TableSkeleton.tsx`
### Будут изменены (импорты → @/shared/)
~50 файлов в `entities/`, `widgets/`, `pages/`, `hooks/`, `context/`, `components/`, `test/`
---
## Task 1: Создать shared/api/ + re-export shims
**Файлы:**
- Create: `src/shared/api/index.ts`
- Create: `src/shared/api/client.ts`
- Create: `src/shared/api/client.test.ts`
- Create: `src/shared/api/responses.ts`
- Create: `src/shared/api/types.ts`
- Modify: `src/api/client.ts` → re-export shim
- Modify: `src/api/responses.ts` → re-export shim
- Modify: `src/api/types.ts` → re-export shim
- [ ] **Step 1: Создать shared/api/ директорию**
```bash
mkdir -p apps/frontend/src/shared/api
```
- [ ] **Step 2: Скопировать api/client.ts в shared/api/client.ts**
```bash
cp apps/frontend/src/api/client.ts apps/frontend/src/shared/api/client.ts
cp apps/frontend/src/api/client.test.ts apps/frontend/src/shared/api/client.test.ts
```
- [ ] **Step 3: Скопировать api/responses.ts в shared/api/responses.ts**
```bash
cp apps/frontend/src/api/responses.ts apps/frontend/src/shared/api/responses.ts
```
- [ ] **Step 4: Скопировать api/types.ts в shared/api/types.ts**
```bash
cp apps/frontend/src/api/types.ts apps/frontend/src/shared/api/types.ts
```
- [ ] **Step 5: Создать shared/api/index.ts barrel**
```typescript
export { request, setAccessToken, getAccessToken, setOnUnauthorized, getHealth, searchSecurities, getShare, getShareMarketData, getShareDividends, getShareHistory, getBond, getBondMarketData, getBondHistory, getShareCandles, getBondCandles } from './client';
export type { ApiResponseMeta, ApiEnvelope, StockMarketData, ShareResponse, DividendItem, ShareHistoryItem, BondMarketData, BondResponse, BondHistoryItem, CandleItem, SearchResultItem, HealthResponse, UserResponse, AuthResponse, Portfolio, PositionWithPrice, PortfolioDetail, Position, PortfolioSummary, AnalyticsResponse, ScreenerItem, ScreenerResult, BrokerMoney, BrokerAccount, BrokerPosition, BrokerPortfolio, BrokerOperationCategory, BrokerOperation, BrokerOperationsPage, BrokerPositionsPage } from './responses';
```
- [ ] **Step 6: Превратить api/client.ts в re-export shim**
```typescript
export { request, setAccessToken, getAccessToken, setOnUnauthorized, getHealth, searchSecurities, getShare, getShareMarketData, getShareDividends, getShareHistory, getBond, getBondMarketData, getBondHistory, getShareCandles, getBondCandles } from '../shared/api/client';
```
- [ ] **Step 7: Превратить api/responses.ts в re-export shim**
```typescript
export type { ApiResponseMeta, ApiEnvelope, StockMarketData, ShareResponse, DividendItem, ShareHistoryItem, BondMarketData, BondResponse, BondHistoryItem, CandleItem, SearchResultItem, HealthResponse, UserResponse, AuthResponse, Portfolio, PositionWithPrice, PortfolioDetail, Position, PortfolioSummary, AnalyticsResponse, ScreenerItem, ScreenerResult, BrokerMoney, BrokerAccount, BrokerPosition, BrokerPortfolio, BrokerOperationCategory, BrokerOperation, BrokerOperationsPage, BrokerPositionsPage } from '../shared/api/responses';
```
- [ ] **Step 8: Превратить api/types.ts в re-export shim**
```typescript
export * from '../shared/api/types';
```
- [ ] **Step 9: Проверить, что тесты проходят**
Run: `npm test -w apps/frontend`
Expected: PASS
- [ ] **Step 10: Закоммитить**
```bash
git add apps/frontend/src/shared/api apps/frontend/src/api/client.ts apps/frontend/src/api/responses.ts apps/frontend/src/api/types.ts
git commit -m "refactor(frontend): create shared/api layer with re-export shims"
```
---
## Task 2: Создать shared/ui/ + re-export shims
**Файлы:**
- Create: `src/shared/ui/index.ts`
- Create: `src/shared/ui/SkeletonBlock.tsx`
- Create: `src/shared/ui/TableSkeleton.tsx`
- Modify: `src/components/SkeletonBlock.tsx` → re-export shim
- Modify: `src/components/TableSkeleton.tsx` → re-export shim
- [ ] **Step 1: Создать shared/ui/ директорию**
```bash
mkdir -p apps/frontend/src/shared/ui
```
- [ ] **Step 2: Скопировать SkeletonBlock и TableSkeleton в shared/ui/**
```bash
cp apps/frontend/src/components/SkeletonBlock.tsx apps/frontend/src/shared/ui/SkeletonBlock.tsx
cp apps/frontend/src/components/TableSkeleton.tsx apps/frontend/src/shared/ui/TableSkeleton.tsx
```
- [ ] **Step 3: Обновить shared/ui/TableSkeleton.tsx — импорт SkeletonBlock из shared/ui**
```typescript
// replace: import { SkeletonBlock } from './SkeletonBlock';
// with: import { SkeletonBlock } from '@/shared/ui/SkeletonBlock';
```
Edit `apps/frontend/src/shared/ui/TableSkeleton.tsx` line 1:
```
import { SkeletonBlock } from './SkeletonBlock'; → import { SkeletonBlock } from '@/shared/ui/SkeletonBlock';
```
- [ ] **Step 4: Создать shared/ui/index.ts barrel**
```typescript
export { SkeletonBlock } from './SkeletonBlock';
export { TableSkeleton } from './TableSkeleton';
```
- [ ] **Step 5: Превратить components/SkeletonBlock.tsx в re-export shim**
```typescript
export { SkeletonBlock } from '../shared/ui/SkeletonBlock';
```
- [ ] **Step 6: Превратить components/TableSkeleton.tsx в re-export shim**
```typescript
export { TableSkeleton } from '../shared/ui/TableSkeleton';
```
- [ ] **Step 7: Проверить, что тесты проходят**
Run: `npm test -w apps/frontend`
Expected: PASS
- [ ] **Step 8: Закоммитить**
```bash
git add apps/frontend/src/shared/ui apps/frontend/src/components/SkeletonBlock.tsx apps/frontend/src/components/TableSkeleton.tsx
git commit -m "refactor(frontend): create shared/ui layer with skeleton components"
```
---
## Task 3: Переключить импорты FSD entities/widgets/pages на @/shared/
**Файлы:** Все файлы в `entities/`, `widgets/`, `pages/` (FSD-части), которые импортируют из `api/client`, `api/responses`, `components/SkeletonBlock`, `components/TableSkeleton`.
Замена:
- `from '../../../api/responses'``from '@/shared/api/responses'`
- `from '../../../api/client'``from '@/shared/api/client'`
- `from '../../api/responses'``from '@/shared/api/responses'`
- `from '../../api/client'``from '@/shared/api/client'`
- `from '../../../components/SkeletonBlock'``from '@/shared/ui/SkeletonBlock'`
- `from '../../../components/TableSkeleton'``from '@/shared/ui/TableSkeleton'`
- `from '../../components/SkeletonBlock'``from '@/shared/ui/SkeletonBlock'`
- `from '../../components/TableSkeleton'``from '@/shared/ui/TableSkeleton'`
- [ ] **Step 1: Заменить импорты в entities/**
```bash
cd apps/frontend/src/entities
# api/responses → @/shared/api/responses (relative depth 3: ../../../)
find . -name '*.ts' -o -name '*.tsx' | xargs sed -i '' "s|from '\.\./\.\./\.\./api/responses'|from '@/shared/api/responses'|g"
find . -name '*.ts' -o -name '*.tsx' | xargs sed -i '' "s|from '\.\./\.\./\.\./api/client'|from '@/shared/api/client'|g"
```
- [ ] **Step 2: Заменить импорты в widgets/**
```bash
cd apps/frontend/src/widgets
# api/responses → @/shared/api/responses (relative depth 3: ../../../)
find . -name '*.ts' -o -name '*.tsx' | xargs sed -i '' "s|from '\.\./\.\./\.\./api/responses'|from '@/shared/api/responses'|g"
# components/SkeletonBlock → @/shared/ui/SkeletonBlock (relative depth 3)
find . -name '*.ts' -o -name '*.tsx' | xargs sed -i '' "s|from '\.\./\.\./\.\./components/SkeletonBlock'|from '@/shared/ui/SkeletonBlock'|g"
find . -name '*.ts' -o -name '*.tsx' | xargs sed -i '' "s|from '\.\./\.\./\.\./components/TableSkeleton'|from '@/shared/ui/TableSkeleton'|g"
```
- [ ] **Step 3: Заменить импорты в pages/ (FSD-страницы)**
```bash
cd apps/frontend/src/pages
# api/responses → @/shared/api/responses (from pages/broker-*/ and pages/broker-positions/)
find . -name '*.ts' -o -name '*.tsx' | xargs sed -i '' "s|from '\.\./\.\./\.\./api/responses'|from '@/shared/api/responses'|g"
# components/SkeletonBlock → @/shared/ui/SkeletonBlock
find . -name '*.ts' -o -name '*.tsx' | xargs sed -i '' "s|from '\.\./\.\./\.\./components/SkeletonBlock'|from '@/shared/ui/SkeletonBlock'|g"
find . -name '*.ts' -o -name '*.tsx' | xargs sed -i '' "s|from '\.\./\.\./\.\./components/TableSkeleton'|from '@/shared/ui/TableSkeleton'|g"
```
**Files matched and updated:**
- `entities/broker-account/api/brokerAccountApi.ts` — 2 импорта
- `entities/broker-account/model/brokerAccountsOverview.ts` — 1 импорт
- `entities/broker-account/model/brokerAccountsOverview.test.ts` — 1 импорт
- `entities/broker-account/model/useBrokerAccounts.ts` — 1 импорт
- `entities/broker-account/model/useBrokerAccountPortfolios.ts` — 1 импорт
- `entities/broker-account/model/useBrokerPortfolio.ts` — 1 импорт
- `entities/broker-position/model/useBrokerPositions.ts` — 1 импорт
- `entities/broker-position/model/brokerDisplay.ts` — 1 импорт
- `entities/broker-position/model/brokerDisplay.test.ts` — 1 импорт
- `entities/broker-position/model/brokerAllocation.ts` — 1 импорт
- `entities/broker-position/model/brokerAllocation.test.ts` — 1 импорт
- `entities/broker-operation/model/useBrokerOperations.ts` — 1 импорт
- `entities/broker-operation/model/operationFilters.ts` — 1 импорт
- `widgets/broker-account-card/ui/BrokerAccountCard.tsx` — 1 импорт + SkeletonBlock
- `widgets/broker-accounts-summary/ui/BrokerAccountsSummary.tsx` — SkeletonBlock
- `widgets/broker-allocation-chart/ui/BrokerAllocationChart.tsx` — 1 импорт
- `widgets/broker-operations-table/ui/BrokerOperationsTable.tsx` — 1 импорт + TableSkeleton
- `pages/broker-account/ui/BrokerAccountOverviewPage.tsx` — 1 импорт + SkeletonBlock
- `pages/broker-positions/ui/BrokerPositionsPage.tsx` — 1 импорт + TableSkeleton
- [ ] **Step 4: Проверить, что заменилось корректно**
```bash
cd apps/frontend/src
rg "from '\.\./\.\./\.\./api/(responses|client)'" --include '*.ts' --include '*.tsx' entities widgets pages
rg "from '\.\./\.\./\.\./components/(SkeletonBlock|TableSkeleton)'" --include '*.ts' --include '*.tsx' entities widgets pages
```
Expected: no matches (все импорты заменены)
- [ ] **Step 5: Проверить, что тесты проходят**
Run: `npm test -w apps/frontend`
Expected: PASS
- [ ] **Step 6: Закоммитить**
```bash
git add -u apps/frontend/src/entities apps/frontend/src/widgets apps/frontend/src/pages
git commit -m "refactor(frontend): update fsd entities imports to use @/shared/"
```
---
## Task 4: Переключить legacy импорты на @/shared/
**Файлы:** `hooks/`, `context/`, `components/` (legacy каталоги), `pages/` (legacy страницы), `test/`, которые импортируют из `api/client`, `api/responses`, `api/types` с относительными путями `../` или `../../`.
Замена:
- `from '../api/responses'``from '@/shared/api/responses'`
- `from '../api/client'``from '@/shared/api/client'`
- `from '../api/types'``from '@/shared/api/types'`
- `from '../../api/responses'``from '@/shared/api/responses'`
- `from '../../api/client'``from '@/shared/api/client'`
- [ ] **Step 1: Заменить импорты в hooks/**
```bash
cd apps/frontend/src/hooks
# ../api/ → @/shared/api/
find . -name '*.ts' -o -name '*.tsx' | xargs sed -i '' "s|from '\.\./api/responses'|from '@/shared/api/responses'|g"
find . -name '*.ts' -o -name '*.tsx' | xargs sed -i '' "s|from '\.\./api/client'|from '@/shared/api/client'|g"
```
- [ ] **Step 2: Заменить импорты в context/**
```bash
cd apps/frontend/src/context
find . -name '*.ts' -o -name '*.tsx' | xargs sed -i '' "s|from '\.\./api/responses'|from '@/shared/api/responses'|g"
find . -name '*.ts' -o -name '*.tsx' | xargs sed -i '' "s|from '\.\./api/client'|from '@/shared/api/client'|g"
```
- [ ] **Step 3: Заменить импорты в components/ (кроме shims)**
```bash
cd apps/frontend/src/components
# api/responses может быть на глубине ../ или ../../
find . -name '*.ts' -o -name '*.tsx' | xargs sed -i '' "s|from '\.\./api/responses'|from '@/shared/api/responses'|g"
find . -name '*.ts' -o -name '*.tsx' | xargs sed -i '' "s|from '\.\./\.\./api/responses'|from '@/shared/api/responses'|g"
find . -name '*.ts' -o -name '*.tsx' | xargs sed -i '' "s|from '\.\./\.\./api/client'|from '@/shared/api/client'|g"
```
- [ ] **Step 4: Заменить импорты в pages/ (legacy страницы, не FSD)**
Legacy страницы на глубине `../../` от `pages/broker/` и `../../` от `pages/portfolios/`:
```bash
cd apps/frontend/src/pages/broker
find . -name '*.ts' -o -name '*.tsx' | xargs sed -i '' "s|from '\.\./\.\./api/responses'|from '@/shared/api/responses'|g"
```
- [ ] **Step 5: Заменить импорты в test/**
```bash
cd apps/frontend/src/test
find . -name '*.ts' -o -name '*.tsx' | xargs sed -i '' "s|from '\.\./api/responses'|from '@/shared/api/responses'|g"
```
- [ ] **Step 6: Проверить, что не осталось относительных импортов из api/client или api/responses**
```bash
cd apps/frontend/src
rg "from '\.\./api/(responses|client|types)'" --include '*.ts' --include '*.tsx'
rg "from '\.\./\.\./api/(responses|client|types)'" --include '*.ts' --include '*.tsx'
rg "from '\.\./\.\./\.\./api/(responses|client|types)'" --include '*.ts' --include '*.tsx'
```
Expected: only shim files in `api/` itself may still have `../shared/api/` — that's correct.
- [ ] **Step 7: Проверить, что тесты проходят**
Run: `npm test -w apps/frontend`
Expected: PASS
- [ ] **Step 8: Закоммитить**
```bash
git add -u apps/frontend/src/hooks apps/frontend/src/context apps/frontend/src/components apps/frontend/src/pages apps/frontend/src/test
git commit -m "refactor(frontend): update legacy imports to use @/shared/"
```
---
## Task 5: Финальная верификация
- [ ] **Step 1: Полный прогон тестов**
Run: `npm test -w apps/frontend`
Expected: PASS
- [ ] **Step 2: Проверить lint**
Run: `npm run lint -w apps/frontend`
Expected: PASS
- [ ] **Step 3: Проверить build**
Run: `npm run build -w apps/frontend`
Expected: PASS
- [ ] **Step 4: Проверить, что все импорты корректны**
```bash
cd apps/frontend/src
# Должны остаться ТОЛЬКО импорты из api/auth, api/broker, api/portfolio, api/screener (не moved)
rg "from '\.\./(\.\./)*api/(responses|client|types)'" --include '*.ts' --include '*.tsx'
```
Expected: 0 matches (кроме shim-файлов в api/)
- [ ] **Step 5: Обновить tasks.md**
- [ ] **Step 6: Финальный коммит**
```bash
git add -A apps/frontend
git commit -m "refactor(frontend): complete shared layer migration"
```