93 lines
6.9 KiB
Markdown
93 lines
6.9 KiB
Markdown
# Frontend architecture
|
||
|
||
## Устройство и data flow
|
||
|
||
React + TypeScript + Vite SPA. Router задаёт navigation; providers в `app`
|
||
подключают auth, TanStack Query, i18n и Mantine. Vite использует React Compiler;
|
||
Vitest — отдельную JSDOM конфигурацию. Точные versions/scripts — в
|
||
[package.json](../package.json), реализация — source of truth.
|
||
|
||
```text
|
||
app → pages → features → entities → shared
|
||
page container → page VM → entity/feature query → shared/api → backend /api/v1
|
||
↓
|
||
props → state/section views
|
||
```
|
||
|
||
Server state и lifecycle запроса принадлежат TanStack Query; VM собирает данные
|
||
для view. Transport — `openapi-fetch`/`openapi-react-query`, типизированные из
|
||
предоставленного snapshot через `openapi-typescript`. Backend владеет wire
|
||
contract; frontend не читает его checkout для генерации и не вызывает T-Bank.
|
||
[Data convention](conventions/data.md) задаёт auth/cache границы.
|
||
|
||
## Выбор владельца
|
||
|
||
Сначала найди владельца поведения и ближайший пример. Расширяй existing slice,
|
||
если ответственность остаётся связной. Размер файла или возможный будущий reuse
|
||
не создают новую архитектурную границу.
|
||
|
||
| Ответственность | Размещение |
|
||
| --- | --- |
|
||
| Bootstrap, глобальные providers, routes | `app` |
|
||
| Композиция маршрута и поведение, нужное только этому экрану | `pages/<page>` |
|
||
| Самостоятельная пользовательская возможность с собственным смыслом | `features/<capability>` |
|
||
| Представление/поведение бизнес-сущности | `entities/<entity>` |
|
||
| Переиспользуемый код без WealthLedger domain meaning | `shared` |
|
||
|
||
Локальный FSD profile: `app → pages → features → entities → shared`.
|
||
Слой `widgets` не используется: композиция экрана и компонент для одной page
|
||
остаются у page. Form, mutation или click handler сами по себе не
|
||
требуют feature. Два callers не делают domain helper generic shared.
|
||
Slice именуется по ответственности, а не `helpers`, `hooks` или `types`.
|
||
|
||
**Безопасный путь:** при сомнении оставь код у ближайшего допустимого owner.
|
||
Извлекай его, когда появилась самостоятельная ответственность или реальный reuse.
|
||
Не вводи новый layer и не обходи public API, чтобы устранить неудобный import.
|
||
|
||
## Imports и public API
|
||
|
||
Cross-slice consumer использует `@/` и `index.ts` владельца, например
|
||
`@/entities/portfolio`. Alias Vite и TypeScript согласован. Другой slice не
|
||
импортирует внутренний файл напрямую. Slices `pages`, `features`, `entities` не зависят от соседнего slice своего слоя;
|
||
композиция принадлежит вышележащему owner. Existing меж-slice исключения
|
||
не являются разрешением добавлять новые.
|
||
|
||
Направление зависимостей, public imports и запрет соседних slices в
|
||
`pages`, `features`, `entities` проверяет `npm run deps`. Внутренние imports
|
||
одного slice и связи shared segments этот запрет не ограничивает.
|
||
Knip проверяет использование exports, а не смысл placement.
|
||
|
||
## UI foundation
|
||
|
||
Private workspace `packages/tokens` экспортирует переносимый source design tokens
|
||
как `@wealthledger/tokens/tokens.json`. Private workspace `packages/ui`
|
||
адаптирует доверенный JSON к Mantine без runtime schema. App и Storybook читают
|
||
`@wealthledger/ui/theme` и явно подключают `@wealthledger/ui/styles.css` после
|
||
Mantine CSS. Потребители используют public exports пакетов напрямую. Storybook показывает synthetic presentation states,
|
||
не использует application API. RTL/MSW проверяют поведение приложения отдельно.
|
||
|
||
Route-aware AppShell и его navigation styles принадлежат `app/ui`;
|
||
`ProtectedLayout` использует public API этого владельца. При изменении foundation прочитай
|
||
[UI](conventions/ui.md#визуальная-основа) и [Storybook](conventions/storybook.md).
|
||
|
||
Все SVG, их геометрия и анимации принадлежат private `packages/icons`.
|
||
Приложение импортирует компоненты из `@wealthledger/icons` и подключает
|
||
`@wealthledger/icons/styles.css`. В `src` нет SVG-файлов и компонентов,
|
||
единственная задача которых — рисовать иконку. Domain mapping категории в
|
||
параметры `OperationIcon` остаётся в `entities/bank-card/model`.
|
||
|
||
## Примеры
|
||
|
||
- Общая busy-область: существующий [LoadingSkeleton](../packages/ui/src/LoadingSkeleton/LoadingSkeleton.tsx), а не domain widget в shared.
|
||
- Account-income state views: [страница](../src/pages/account-income/ui/AccountIncomePage/AccountIncomePage.tsx) и её `components/`; новый page-local filter остаётся здесь. Комплекты файлов группируются по [UI convention](conventions/ui.md#размещение-файлов-компонента).
|
||
- Portfolio query: [entity public API](../src/entities/portfolio/index.ts); новая UI-сводка с бизнес-смыслом не становится shared только из-за нескольких экранов.
|
||
|
||
## Проверка
|
||
|
||
После placement/import change: `npm run deps`, `npm run typecheck`, `npm run knip`;
|
||
после собственного behavior — применимые tests. Финальный gate указан в AGENTS.
|
||
Graph `src` и `packages` проверяет текущий dependency-cruiser; зависимости
|
||
из packages в код приложения `src` запрещены. Generated `packages/*/dist`
|
||
не анализируется как исходник; все package sources остаются в graph. Review отдельно проверяет смысл
|
||
owner: разрешённый dependency graph не доказывает правильный выбор слоя.
|