# Frontend architecture ## Выбор владельца Сначала найди владельца поведения и ближайший пример. Расширяй existing slice, если ответственность остаётся связной. Размер файла или возможный будущий reuse не создают новую архитектурную границу. | Ответственность | Размещение | | --- | --- | | Bootstrap, глобальные providers, routes | `app` | | Композиция маршрута и поведение, нужное только этому экрану | `pages/` | | Самостоятельная пользовательская возможность с собственным смыслом | `features/` | | Представление/поведение бизнес-сущности | `entities/` | | Переиспользуемый код без 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 не доказывает правильный выбор слоя.