4.2 KiB
Frontend architecture
Выбор владельца
Сначала найди владельца поведения и ближайший пример. Расширяй 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 не
импортирует внутренний файл напрямую. Направление зависимостей проверяет
npm run deps; Knip проверяет использование exports, а не смысл placement.
UI foundation
Общий visual foundation принадлежит shared/theme. tokens.json — переносимый
WealthLedger v1 source; theme.ts адаптирует доверенный JSON в Mantine без runtime
schema. App и Storybook используют public API shared/theme/index.ts: theme,
designTokens, color roles, CSSVariablesResolver и base styles. Внешний consumer
читает JSON напрямую. Mantine defaults и локальные domain styles остаются у владельцев.
shared/ui содержит StateView/LoadingSkeleton. Existing route-aware AppShell также
лежит в shared/ui, но не является общим catalog primitive; его расположение не
служит основанием переносить новую domain/navigation логику в shared.
Изменяя foundation, прочитай design system.
Примеры
- Общая busy-область: существующий LoadingSkeleton, а не domain widget в shared.
- Account-income state views: страница и её flat
components/*.view.tsx; новый page-local filter остаётся здесь. - Portfolio query: entity public API; новая UI-сводка с бизнес-смыслом не становится shared только из-за нескольких экранов.
Проверка
После placement/import change: npm run deps, npm run typecheck, npm run knip;
после собственного behavior — применимые tests. Финальный gate указан в AGENTS.
Review отдельно проверяет смысл owner: разрешённый dependency graph не доказывает
правильный выбор слоя.