380 lines
17 KiB
Markdown
380 lines
17 KiB
Markdown
# 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"
|
||
```
|