# Frontend UI ## Выбор компонента и state Используй существующий Mantine component и общую тему, если они выражают нужное поведение. Wrapper нужен для проектной ответственности, а не ради нового имени для Button. Локальный CSS module принадлежит component; общий token вводи для реально общей роли через [design system](../../../docs/development/design-system.md). | Состояние | Владелец | | --- | --- | | Ответ сервера / query lifecycle | TanStack Query, [data rules](frontend-data.md) | | Ввод и ошибки формы | React Hook Form + Zod, container/VM | | Параметр навигации, который должен переживать переход/ссылку | Router/URL | | Локальное открытие, focus, rendering fallback | React UI state | | Значение, вычисляемое из уже доступных inputs | Вычисление при render/в VM, без копирования через effect в state | Effect нужен для синхронизации с внешней системой; действие пользователя обрабатывай в handler. Не добавляй store, memoization или abstraction без текущей потребности. Inspect ближайший component до создания нового pattern. ## Container, VM и view Для data-driven finance page container вызывает один page VM и выбирает Loading/Error/Empty/Loaded view. VM владеет data/auth/route inputs, state selection, retry callbacks, domain grouping и derived presentation data. View получает props. | Файл | Ответственность | | --- | --- | | `Component.tsx` | Container или публичный component | | `Component.vm.ts` | Page VM, данные и действия | | `Component.view.tsx` | Presentation | | `Component.types.ts` | Переиспользуемые props/local types, когда отдельный owner полезен | | `Component.module.css` | Локальные стили | Во view нет data/auth/router/navigation/VM hooks и вычислений по domain collection. Допустимы `useTranslation`, form-context hooks, UI-only state (например failed image fallback) и formatter одного переданного значения. Группировка/доли коллекции готовятся в VM. Перенос fetch во view ради меньшего container нарушает границу. Отдельный state/section с самостоятельной responsibility выноси в flat `components/*.view.tsx`. Небольшой tightly-coupled renderer оставляй рядом. Не создавай folder/barrel/test/CSS для каждого view и не дроби по line count. Пример: [AccountIncomePage](../../src/pages/account-income/ui/AccountIncomePage.tsx) и [его VM](../../src/pages/account-income/ui/AccountIncomePage.vm.ts). ## Формы Используй `react-hook-form`, `zodResolver`, `FormProvider`. Container/VM создаёт form state, view получает `onSubmit`/`disabled`, fields используют `useFormContext`. Во время submit блокируй все действия формы. Zod errors показывай у поля; network error — отдельным Alert с сохранением ввода. Невалидная форма не отправляет request. ## Состояния и recovery Без успешных данных выбирай loading → error → empty → loaded. При наличии query данных сохраняй их во время refetch и его ошибки: progress, локальное предупреждение, retry. Это Client Cached Snapshot, отдельный от server stale/fallback. Known API errors принадлежат local view; непойманные route errors — route-keyed ErrorBoundary. Сброс query cache после смены session/connection задают data rules. Initial loading использует page-owned structural skeleton по semantic regions, не одиночный spinner/StateView. Partial loading принадлежит section. Busy-область доступна assistive technology, shapes декоративны, без данных, интерактивности и focus. Route fallback сохраняет shell; до auth ready — neutral shell skeleton. Button loading при mutation и компактный cached-snapshot progress остаются контекстными indicators. Проверяй keyboard access к actions и безопасный retry. ## Код и components Style/type automation задают configs. Используй `import type`; `enum` и `namespace` в frontend не применяются. Named components — const-valued; Biome проверяет declarations, no-default-export и exports-last в своём scope. Обязательный review: `FC` и named props рядом/в types-файле; без inline props typing и destructuring в параметрах. Принимай `props`, destructure в body. Только children — `FC`; дополнительные props — named type на основе `PropsWithChildren<{ ... }>`. Без props — `FC`. Своди обычные exports к читаемому нижнему named block; barrel делает прямой re-export без промежуточного import. CSF `*.stories.tsx` сохраняют default meta и прямые story exports для indexer. Vite/Vitest configs и `.storybook/main.ts`/`preview.tsx` имеют только конфигурационное default-export exception. Scope исключений принадлежит biome.json. ## Строки и валидация Продуктовый текст хранится в `src/shared/i18n/locales/ru.ts`, читается через `useTranslation()`. В Zod используй `validationMessage(key)` из i18n/zod, без пользовательских literal messages. Это mandatory semantic review: общего запрета непереведённых JSX-строк в Biome/TypeScript нет. Synthetic story fixtures и описания историй допустимы; они не заменяют продуктовую локализацию. ## Проверка Type/style: `npm run typecheck`, `npm run lint`. Для risk behavior/изменения тестов используй [Testing](frontend-testing.md). UI: keyboard, narrow viewport, loading/error/empty/loaded и recovery по применимости. [Storybook](../../../docs/development/ui-catalog.md) уже установлен; используй его как reference, но сверяй UI с задачей отдельно от build.