57 lines
4.2 KiB
Markdown
57 lines
4.2 KiB
Markdown
# 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](../../../docs/development/design-system.md).
|
||
|
||
## Примеры
|
||
|
||
- Общая busy-область: существующий [LoadingSkeleton](../../src/shared/ui/LoadingSkeleton.tsx), а не domain widget в shared.
|
||
- Account-income state views: [страница](../../src/pages/account-income/ui/AccountIncomePage.tsx) и её flat `components/*.view.tsx`; новый page-local filter остаётся здесь.
|
||
- 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.
|
||
Review отдельно проверяет смысл owner: разрешённый dependency graph не доказывает
|
||
правильный выбор слоя.
|