6.9 KiB
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, реализация — source of truth.
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 задаёт 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. 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 и Storybook.
Все SVG, их геометрия и анимации принадлежат private packages/icons.
Приложение импортирует компоненты из @wealthledger/icons и подключает
@wealthledger/icons/styles.css. В src нет SVG-файлов и компонентов,
единственная задача которых — рисовать иконку. Domain mapping категории в
параметры OperationIcon остаётся в entities/bank-card/model.
Примеры
- Общая busy-область: существующий LoadingSkeleton, а не domain widget в shared.
- Account-income state views: страница и её
components/; новый page-local filter остаётся здесь. Комплекты файлов группируются по UI convention. - Portfolio query: entity public API; новая 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 не доказывает правильный выбор слоя.