Files
frontend/.ai/conventions/frontend-ui.md
T

159 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Frontend UI
## Выбор компонента и state
Сначала переиспользуй подходящий component проекта; иначе используй готовый
Mantine component и общую тему. Для выпадающего выбора используй `Select` или
`MultiSelect`, а не браузерный `<select>` / `NativeSelect` по умолчанию.
Нативный control или собственная реализация допустимы, когда готовые components
не покрывают конкретное продуктовое требование или доступность; укажи причину
в результате работы. Краткость кода и удобство тестирования не являются причиной:
ограничения JSDOM решай в test setup, сохраняя production UI.
Семантический HTML для структуры страницы остаётся уместным.
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<Props>` и named props рядом/в types-файле; без inline
props typing и destructuring в параметрах. Принимай `props`, destructure в body.
Только children — `FC<PropsWithChildren>`; дополнительные 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.
## Domain literals and magic values
Finite business vocabulary must be modeled explicitly; incidental implementation
literals stay inline. Это относится к error codes, statuses, operation/account/
instrument/transaction types, permissions, roles, feature states и бизнес-режимам.
Перед новым определением найди existing owner/type/value. В branching и comparisons
используй символическое значение владельца; не повторяй raw domain literals.
Храни словарь у domain/module/entity с соблюдением границ слоёв, без глобальной
свалки `constants.ts`. Сохраняй принятый pattern; для нового project-owned словаря
без подходящего аналога используй `as const` с derived union:
```ts
const BankErrorCode = {
InvalidDate: "BANK_INVALID_DATE",
ImmutableOperation: "BANK_IMMUTABLE_OPERATION",
} as const;
type BankErrorCode = (typeof BankErrorCode)[keyof typeof BankErrorCode];
// Вместо code === "BANK_INVALID_DATE":
if (code === BankErrorCode.InvalidDate) { /* existing handling */ }
```
Значимые numeric policies (timeout, retry, page size, threshold, coefficient)
называй по смыслу и единицам: `REQUEST_TIMEOUT_MS`, `CACHE_TTL_SECONDS`,
`MAX_RETRY_COUNT`, `PAGE_SIZE`. Оставляй inline UI labels в принятом месте,
log messages, индексы, `index + 1`, `Math.max(value, 0)`, `page ?? 1`,
`percentage * 100` и очевидные локальные детали без semantic benefit.
При неясном owner/смысле оставь значение и укажи ограничение в review.
Не мигрируй корректные existing enum/union styles массово. Не добавляй ради
правила factories, registries, schemas, loaders, abstraction layers, dependencies,
custom lint/AST rules, generation/validation scripts или тесты существования
констант. Semantic review проверяет ownership, единое написание и неизменность
wire values; обычные behavior tests проверяют поведение.
Backend API vocabulary бери из generated OpenAPI types/client, runtime values —
когда генератор их предоставляет. Не создавай ручную копию backend enum/object.
Текущий `schema.d.ts` предоставляет только types: используй indexed-access type
из `paths`; typed literals в запросах/сравнениях допустимы при отсутствии
runtime values. `ProblemDetailsDto.code` сейчас `string`: не выдумывай закрытый
frontend error enum. Развитие API source — отдельное contract-решение.
Frontend `enum` по-прежнему запрещён локальным TS/style profile.
`noMagicNumbers` включён как error в Biome. Fixtures, tests и stories исключены
из этого правила; остальные проверки для них сохраняются. Используй имена
политик/единиц и existing values. Точечное подавление допустимо для очевидного
incidental значения с объяснением, например перевода доли в проценты; не подавляй
скрытые бизнес-лимиты. Semantic magic strings проверяются conventions/review: в
установленном Biome нет такого встроенного правила, новый инструмент не добавляем.
## Строки и валидация
Продуктовый текст хранится в `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.