docs(ai): route frontend conventions by task
This commit is contained in:
@@ -0,0 +1,23 @@
|
|||||||
|
# Frontend router
|
||||||
|
|
||||||
|
Читай строки, которые активирует задача, затронутый путь **или** обнаруженная
|
||||||
|
граница. Объединяй совпадения; якорь разрешает чтение только нужного раздела.
|
||||||
|
Не загружай все conventions из-за одного frontend-файла.
|
||||||
|
|
||||||
|
| Решение / сигнал | Читать до решения |
|
||||||
|
| --- | --- |
|
||||||
|
| Создать/перенести module/component/slice; выбрать page/entity/feature/shared; cross-slice imports | [Architecture](conventions/frontend-architecture.md) |
|
||||||
|
| Component/props, container/VM/view, form, local state/effect, loading/error/retry, Mantine/UI | [UI](conventions/frontend-ui.md) |
|
||||||
|
| Только продуктовая строка или Zod message | [Строки](conventions/frontend-ui.md#строки-и-валидация) |
|
||||||
|
| Только TS declaration/import/export | [Код](conventions/frontend-ui.md#код-и-components) |
|
||||||
|
| query/mutation, shared/api, server state/cache, generated types, Bearer/refresh | [Data](conventions/frontend-data.md) |
|
||||||
|
| Тесты или behavior с testing risk | [Testing](conventions/frontend-testing.md) |
|
||||||
|
| Новый abstraction/dependency, refactor или неоднозначный scope | [Общий выбор реализации](../../docs/development/engineering-governance.md#выбор-реализации) |
|
||||||
|
| Request/response field, route/status/error shape, OpenAPI snapshot/generated type | [Общий contract workflow](../../docs/development/runtime-openapi-verification.md) |
|
||||||
|
| Login/refresh/logout, token/cookie, auth ownership | Data + [security](../../backend/.ai/conventions/security-auth.md) + Testing |
|
||||||
|
| Storybook/theme/tokens/shared UI | UI + точные [источники](SOURCES.md) дизайн-системы/каталога |
|
||||||
|
|
||||||
|
UI-only props/presentation требуют UI, без API/structure/testing при отсутствии
|
||||||
|
их triggers. Показ query state не означает изменение data fetching.
|
||||||
|
Изменение тестов активирует Testing и без production diff. Нужные project facts
|
||||||
|
выбирай через SOURCES. Повтори routing при новой границе и по итоговому diff.
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Источники frontend
|
||||||
|
|
||||||
|
| Что нужно установить | Владелец |
|
||||||
|
| --- | --- |
|
||||||
|
| Какое implementation-решение принять | [CONVENTIONS](CONVENTIONS.md) |
|
||||||
|
| HTTP wire contract и направление генерации | Backend runtime OpenAPI → [snapshot/types workflow](../../docs/development/runtime-openapi-verification.md) |
|
||||||
|
| Error codes/validation/error envelope | [docs: HTTP API](../../docs/AI_INDEX.md#http-api) |
|
||||||
|
| Auth/session, token storage и refresh | [docs: Security](../../docs/AI_INDEX.md#security) |
|
||||||
|
| Значение Money, cached snapshot, account/position/income | [docs: Domain](../../docs/AI_INDEX.md#domain) |
|
||||||
|
| Mantine/theme/tokens и существующие stories | [docs: UI](../../docs/AI_INDEX.md#ui) |
|
||||||
|
| As-built behavior нужной feature | [docs: Features](../../docs/AI_INDEX.md#features) |
|
||||||
|
| Существующие behavioral requirements, если нужны по задаче | [specs index](../../specs/AI_INDEX.md) |
|
||||||
|
| Действующие versions/scripts/check scope | package.json, lockfile, Biome/TS/dependency configs |
|
||||||
|
|
||||||
|
Открывай точный source только по возникшему вопросу. Code examples показывают
|
||||||
|
pattern, но не переопределяют контракт. Specs не являются editable OpenAPI;
|
||||||
|
SDD-процедура для обычной работы не нужна. При конфликте источников сначала
|
||||||
|
установи факты, не меняй generated types или security policy по догадке.
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
# 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 не доказывает
|
||||||
|
правильный выбор слоя.
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
# Frontend data
|
||||||
|
|
||||||
|
## Выбор owner
|
||||||
|
|
||||||
|
Server state принадлежит TanStack Query, form state — React Hook Form, локальное
|
||||||
|
UI — React state. Не копируй успешный query result в useState/context для второго
|
||||||
|
кэша. Domain-specific queries, keys и policies принадлежат вызывающему slice;
|
||||||
|
общий transport — `shared/api`. Page VM выбирает inputs и собирает presentation data.
|
||||||
|
|
||||||
|
Используй `apiClient`/`apiQueryClient` из shared public API. Прямой fetch в
|
||||||
|
pages/features/entities и прямой T-Bank call запрещены. Сначала найди
|
||||||
|
существующий query того же endpoint; расширяй его owner, не создавай второй key
|
||||||
|
или глобальную query policy для одной страницы.
|
||||||
|
|
||||||
|
## Типы и contract
|
||||||
|
|
||||||
|
Backend runtime OpenAPI — source; committed frontend snapshot и generated
|
||||||
|
`schema.d.ts` производны. Тип request/response бери из generated `paths`, например:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type User = paths["/api/v1/auth/me"]["get"]["responses"][200]["content"]["application/json"];
|
||||||
|
```
|
||||||
|
|
||||||
|
Domain/UI type допустим, когда он отличается по смыслу, а не копирует transport DTO.
|
||||||
|
Generated TS вручную не исправляется. Изменение wire shape/status/route проходит
|
||||||
|
[общий contract workflow](../../../docs/development/runtime-openapi-verification.md).
|
||||||
|
Одна UI-модель не требует менять API. `openapi:generate` работает offline от snapshot;
|
||||||
|
`openapi:check` генерирует во временный каталог и сравнивает, не меняя tracked files.
|
||||||
|
|
||||||
|
## Auth и invalidation
|
||||||
|
|
||||||
|
Bearer header принадлежит shared authenticated fetch; query/mutation не задают
|
||||||
|
Authorization. Auth endpoints исключены из Bearer/retry middleware. Refresh
|
||||||
|
координируется single-flight; после `401` с `UNAUTHORIZED` возможен один retry.
|
||||||
|
Не добавляй собственный refresh/retry loop в caller.
|
||||||
|
|
||||||
|
Protected queries используют existing session-aware boundary; logout очищает
|
||||||
|
session-scoped cache. При auth/token change читай [security](../../../backend/.ai/conventions/security-auth.md)
|
||||||
|
и [authentication](../../../docs/features/authentication.md).
|
||||||
|
Успешная connection mutation коммитит подтверждённый status и сбрасывает financial
|
||||||
|
GET subtree; ошибка mutation не меняет generation/cache. Это отдельная граница
|
||||||
|
от обычного фонового refetch: [connection policy](../../../docs/features/tbank-connection.md#граница-финансового-кэша).
|
||||||
|
|
||||||
|
Default focus refetch выключен; freshness и retry определяются существующей
|
||||||
|
[query policy](../../../docs/adr/007-react-query-fetch-policy.md) и feature source.
|
||||||
|
При обычном refetch сохраняй Client Cached Snapshot, при session/connection
|
||||||
|
смене не показывай данные прежнего владельца. UI presentation — в UI convention.
|
||||||
|
|
||||||
|
## Примеры и проверка
|
||||||
|
|
||||||
|
[usePortfolio](../../src/entities/portfolio/api/usePortfolio.ts) — entity query;
|
||||||
|
[shared client](../../src/shared/api/client.ts) — transport/auth boundary.
|
||||||
|
Изменяя header/retry/invalidation, проверяй наблюдаемое поведение через MSW и
|
||||||
|
[Testing](frontend-testing.md); не обращайся к реальному T-Bank.
|
||||||
|
`npm run openapi:check`, `npm run typecheck` и targeted tests проверяют разные
|
||||||
|
части решения. Для API semantics нужны также exact docs и contract gate.
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
# Frontend testing
|
||||||
|
|
||||||
|
## Выбор проверки
|
||||||
|
|
||||||
|
Читай при изменении тестов или behavior с [testing risk](../../../docs/development/engineering-governance.md#risk-based-testing).
|
||||||
|
Выбирай boundary по наблюдаемому исходу, а не внутреннему helper/component state.
|
||||||
|
|
||||||
|
| Изменение | Дешёвая надёжная проверка |
|
||||||
|
| --- | --- |
|
||||||
|
| Чистое domain преобразование/денежный расчёт | Unit case с граничными значениями |
|
||||||
|
| Форма, route access, recovery, UI-state selection | RTL component/route test через видимое поведение |
|
||||||
|
| Headers, refresh/retry, query invalidation | MSW на transport boundary |
|
||||||
|
| Только declarative props/Storybook fixture | Type/build + semantic/visual review; unit test без собственной логики не нужен |
|
||||||
|
|
||||||
|
Tests colocated с source (`*.test.tsx` для UI, существующий `*.test.ts` для pure code).
|
||||||
|
Arrange/Act/Assert, semantic queries (`getByRole`, label); test id — когда
|
||||||
|
пользовательский selector не выражает нужный элемент. Не тестируй Mantine/React
|
||||||
|
internals, private calls или passthrough wrapper ради coverage.
|
||||||
|
|
||||||
|
## Инструменты и isolation
|
||||||
|
|
||||||
|
Используй `fireEvent` из объявленного `@testing-library/react` и ожидай async
|
||||||
|
outcome через `findBy*`/`waitFor`. `user-event` доступен транзитивно через Storybook,
|
||||||
|
но не принят как direct dependency; не добавляй его ради старого Markdown.
|
||||||
|
MSW запускается только Vitest setup в `src/test/`, без browser worker/mock dev mode.
|
||||||
|
Проверяй сеть MSW handlers, а не mock внутренностей query client или реальный backend.
|
||||||
|
|
||||||
|
Независимый case получает свой `createTestQueryClient`; rerender сохраняет
|
||||||
|
экземпляр и seeded cache. Providers выбирай явно. Existing teardown выполняет
|
||||||
|
RTL cleanup, auth reset и MSW reset: [test client](../../src/test/query.tsx).
|
||||||
|
|
||||||
|
## Важные случаи
|
||||||
|
|
||||||
|
Форма: field errors, отсутствие request при invalid input, submit blocking,
|
||||||
|
сохранение ввода при network error. Routes: доступ/redirect и neutral auth bootstrap.
|
||||||
|
Finance: loading/error/empty/loaded, cached snapshot при refetch failure, retry,
|
||||||
|
отсутствие старых данных после session/connection switch. ErrorBoundary: test-only
|
||||||
|
throwing route, безопасная ссылка и reset после navigation.
|
||||||
|
|
||||||
|
Примеры: [AuthForm test](../../src/features/auth/ui/AuthForm.test.tsx),
|
||||||
|
[client test](../../src/shared/api/client.test.ts),
|
||||||
|
[usePortfolio test](../../src/entities/portfolio/api/usePortfolio.test.tsx).
|
||||||
|
Не копируй fixtures/сценарии целиком, если задача затрагивает один исход.
|
||||||
|
|
||||||
|
## Команды
|
||||||
|
|
||||||
|
Итерация: `npm run test:run -- <path>` без coverage. Полный `npm run verify`
|
||||||
|
запускает Vitest один раз через `test:coverage`. Coverage floors — anti-regression
|
||||||
|
constraint, не повод тестировать декларации. После UI изменения отдельно
|
||||||
|
проверь доступность/визуальные состояния, которых JSDOM не доказывает.
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
# 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<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.
|
||||||
|
|
||||||
|
## Строки и валидация
|
||||||
|
|
||||||
|
Продуктовый текст хранится в `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.
|
||||||
@@ -1,39 +1,24 @@
|
|||||||
# Frontend
|
# Frontend
|
||||||
|
|
||||||
## Назначение и authority
|
React/Vite-клиент; общие правила работы — в root `AGENTS.md`.
|
||||||
|
До frontend-решения прочитай [router](.ai/CONVENTIONS.md) и
|
||||||
|
[sources](.ai/SOURCES.md), затем только применимые документы/разделы.
|
||||||
|
|
||||||
React/Vite-клиент WealthLedger. Workspace layout и reference policy —
|
## Проверка
|
||||||
в root `AGENTS.md`. Для planning, implementation, validation и завершения
|
|
||||||
читай [canonical lifecycle](../docs/development/openspec-authority-and-lifecycle.md);
|
|
||||||
для recovery и выбора relevant evidence —
|
|
||||||
[context discipline](../docs/development/agent-token-efficiency.md).
|
|
||||||
Они доступны без optional agent skills или commands.
|
|
||||||
|
|
||||||
Frontend-specific conventions: [`docs/CONVENTIONS.md`](docs/CONVENTIONS.md),
|
Перед Node-командами: `nvm use --silent` по `.nvmrc`.
|
||||||
[`docs/STRUCTURE.md`](docs/STRUCTURE.md), [`docs/API-LAYER.md`](docs/API-LAYER.md),
|
После lockfile install `npm ci` полный gate — `npm run verify`.
|
||||||
[`docs/UI-PATTERN.md`](docs/UI-PATTERN.md) и [`docs/TESTING.md`](docs/TESTING.md).
|
Он проверяет OpenAPI drift, TypeScript, Biome, Vitest/coverage, Knip,
|
||||||
|
dependency boundaries и сборки приложения/Storybook без backend.
|
||||||
|
Точные scripts принадлежат `package.json`; targeted checks — у выбранного convention.
|
||||||
|
|
||||||
## Canonical gate
|
HTTP contract changes дополнительно проходят общий
|
||||||
|
[contract workflow](../docs/development/runtime-openapi-verification.md).
|
||||||
|
Generated types редактируются через их source; T-Bank доступен только через backend.
|
||||||
|
Frontend dependencies используют exact versions. Pre-commit запускает staged
|
||||||
|
formatting и FSD checks; сообщения commits — Conventional Commits.
|
||||||
|
|
||||||
Перед Node-командами активировать `.nvmrc` через `nvm use --silent`; это выбирает
|
Code/style checks автоматические только в scope конфигурации. i18n, props,
|
||||||
Node 22. Не запускать отдельную проверку версии, кроме диагностики runtime issue.
|
semantic placement и выбор полезных тестов обязательны при review.
|
||||||
После lockfile install единственный completion gate:
|
Storybook уже доступен как reference; ожидаемый UI и его состояния определяет
|
||||||
|
задача, а визуальное соответствие проверяется отдельно от сборки.
|
||||||
```bash
|
|
||||||
npm ci
|
|
||||||
npm run verify
|
|
||||||
```
|
|
||||||
|
|
||||||
Gate non-mutating и проверяет strict TypeScript, Biome, risk-based tests/coverage ratchet, Knip, dependency boundaries и build. Если backend API contract меняется, committed frontend snapshot `src/shared/api/spec/openapi.json` обновляется явным копированием backend OpenAPI document, затем `npm run openapi:generate` регенерирует committed types; обе стороны API contract сверяются отдельной backend командой `npm run test:openapi-contract`.
|
|
||||||
Актуальные scripts проверять в `package.json`/`npm run`.
|
|
||||||
|
|
||||||
## Enforceable boundaries
|
|
||||||
|
|
||||||
- Code style и i18n: [`docs/CONVENTIONS.md`](docs/CONVENTIONS.md), исполняются Biome и TypeScript.
|
|
||||||
- React component declaration, props и export style: [`docs/CONVENTIONS.md`](docs/CONVENTIONS.md), часть исполняется Biome, часть является mandatory review convention.
|
|
||||||
- FSD и public API: [`docs/STRUCTURE.md`](docs/STRUCTURE.md), исполняются dependency-cruiser и Knip; направление `app → pages → features → entities → shared`.
|
|
||||||
- HTTP и types: [`docs/API-LAYER.md`](docs/API-LAYER.md); только typed client и generated OpenAPI `paths[...]`, без ручных transport DTO и прямых T-Bank calls.
|
|
||||||
- UI states и VM/view boundary: [`docs/UI-PATTERN.md`](docs/UI-PATTERN.md); container вызывает page VM hook, VM владеет data/state/derived presentation data, `*.view.tsx` получает props и рендерит. View files дробить по semantic responsibility, когда section/state можно назвать отдельно.
|
|
||||||
- Тесты: [`docs/TESTING.md`](docs/TESTING.md); новый тест обязателен по risk trigger, а не из-за появления файла или coverage процента.
|
|
||||||
|
|
||||||
Непроверяемые эстетические предпочтения считаются guidance. Не добавлять Storybook, Playwright, CI, browser MSW worker или mock development mode без утверждённого change.
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ backend; прямые вызовы T-Bank из frontend не допускают
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
cp .env.example .env
|
cp .env.example .env
|
||||||
|
nvm use --silent
|
||||||
npm ci
|
npm ci
|
||||||
npm run dev
|
npm run dev
|
||||||
```
|
```
|
||||||
@@ -25,23 +26,27 @@ npm run verify
|
|||||||
|
|
||||||
Команда проверяет, что generated OpenAPI types соответствуют committed snapshot,
|
Команда проверяет, что generated OpenAPI types соответствуют committed snapshot,
|
||||||
затем запускает strict TypeScript, Biome, один coverage-backed запуск Vitest,
|
затем запускает strict TypeScript, Biome, один coverage-backed запуск Vitest,
|
||||||
Knip, dependency rules и production build. Если backend-контракт изменился,
|
Knip, dependency rules приложения и stories, сборки приложения и Storybook.
|
||||||
|
Если backend-контракт изменился,
|
||||||
обновите `src/shared/api/spec/openapi.json` копированием backend OpenAPI
|
обновите `src/shared/api/spec/openapi.json` копированием backend OpenAPI
|
||||||
document и затем выполните `npm run openapi:generate`.
|
document и затем выполните `npm run openapi:generate`.
|
||||||
|
|
||||||
## Документация
|
## Документация
|
||||||
|
|
||||||
- [Структура и FSD](docs/STRUCTURE.md)
|
- [Router conventions](.ai/CONVENTIONS.md) и [источники](.ai/SOURCES.md)
|
||||||
- [API layer и OpenAPI](docs/API-LAYER.md)
|
- [Выбор owner/FSD](.ai/conventions/frontend-architecture.md)
|
||||||
- [UI-паттерны](docs/UI-PATTERN.md)
|
- [UI, state, формы и компоненты](.ai/conventions/frontend-ui.md)
|
||||||
- [Conventions](docs/CONVENTIONS.md) и [тестирование](docs/TESTING.md)
|
- [Data/API/cache](.ai/conventions/frontend-data.md)
|
||||||
|
- [Testing](.ai/conventions/frontend-testing.md)
|
||||||
|
- [Поставленный UI-каталог Storybook](../docs/development/ui-catalog.md)
|
||||||
- [Обзор поставленной системы](../docs/architecture/system-overview.md)
|
- [Обзор поставленной системы](../docs/architecture/system-overview.md)
|
||||||
- [Engineering governance и risk-based testing](../docs/development/engineering-governance.md)
|
- [Engineering governance и risk-based testing](../docs/development/engineering-governance.md)
|
||||||
- [OpenSpec authority и lifecycle](../docs/development/openspec-authority-and-lifecycle.md)
|
- [Карта проектных фактов](../docs/AI_INDEX.md)
|
||||||
- [Frontend workflow](../docs/development/frontend-workflow.md)
|
- [Frontend workflow](../docs/development/frontend-workflow.md)
|
||||||
- [Remediation registry](../docs/development/audit-remediation.md)
|
- [Remediation registry](../docs/development/audit-remediation.md)
|
||||||
|
|
||||||
Runtime OpenAPI backend сверяется с frontend snapshot отдельной read-only
|
Runtime OpenAPI backend сверяется с frontend snapshot отдельной read-only
|
||||||
backend командой `npm run test:openapi-contract`.
|
backend командой `npm run test:openapi-contract`.
|
||||||
Полный multi-repository change проверяется из workspace командой
|
Разработка идёт напрямую по задаче пользователя. Каждый affected repo проходит
|
||||||
native OpenSpec validation and the canonical gates of each affected repository.
|
свой verify; при изменении wire contract дополнительно выполняется contract check.
|
||||||
|
Обязательного OpenSpec/SDD lifecycle нет.
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
# API Layer
|
|
||||||
|
|
||||||
OpenAPI backend является источником истины. После изменения backend-контракта committed snapshot `src/shared/api/spec/openapi.json` обновляют простым копированием опубликованного документа backend, а затем регенерируют `src/shared/api/generated/schema.d.ts` командой `npm run openapi:generate`. Оба файла committed; generated файл не редактируется вручную.
|
|
||||||
|
|
||||||
`npm run openapi:generate` использует committed snapshot offline. `verify`
|
|
||||||
повторно генерирует types во временную директорию и сравнивает их с committed
|
|
||||||
`schema.d.ts`; backend для этого не требуется, а tracked files gate не меняет.
|
|
||||||
|
|
||||||
Все HTTP-вызовы используют `apiClient` и `apiQueryClient` из `src/shared/api`. Прямой `fetch` в features, pages и entities не используется. Query options берутся из `apiQueryClient`, а сохранённые cache keys и policies задаются вызывающим slice.
|
|
||||||
|
|
||||||
Bearer header принадлежит только shared authenticated fetch. Auth endpoints исключены; refresh сохраняет single-flight и один retry `UNAUTHORIZED`. Не передавайте `Authorization` из query или mutation.
|
|
||||||
|
|
||||||
Request и response types выводятся из generated `paths`, например:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
type User = paths["/api/v1/auth/me"]["get"]["responses"][200]["content"]["application/json"];
|
|
||||||
```
|
|
||||||
|
|
||||||
Ручной domain/UI type допустим только если он отличается от transport DTO по смыслу, а не повторяет его поля.
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
# Conventions
|
|
||||||
|
|
||||||
## Must
|
|
||||||
|
|
||||||
- Использовать Biome: `npm run lint`; табы и двойные кавычки определяет `biome.json`.
|
|
||||||
- Для type-only импортов использовать `import type`; не использовать `enum` и `namespace`.
|
|
||||||
- UI-строки хранить в `src/shared/i18n/locales/ru.ts` и получать через `useTranslation()`.
|
|
||||||
- Zod-схемы не содержат пользовательских строк: использовать `validationMessage(key)` из `src/shared/i18n/zod.ts`.
|
|
||||||
- Публичный импорт слайса идёт через его `index.ts`.
|
|
||||||
- Тесты лежат рядом с исходником как `*.test.tsx`.
|
|
||||||
- Зависимости используют exact versions; не добавлять semver ranges.
|
|
||||||
- Полный локальный gate: `npm run verify`. Pre-commit запускает staged formatting
|
|
||||||
и FSD dependency checks; commit message должен следовать Conventional Commits.
|
|
||||||
|
|
||||||
## React component style
|
|
||||||
|
|
||||||
Biome-enforced guardrails:
|
|
||||||
|
|
||||||
- Named React components объявлять как const-valued components, а не function declarations.
|
|
||||||
- Не использовать default exports.
|
|
||||||
- Export statements держать после declarations тела module.
|
|
||||||
|
|
||||||
Storybook CSF-файлы `*.stories.tsx` используют штатный `export default meta`
|
|
||||||
и прямые `export const` для stories: статический indexer должен видеть их
|
|
||||||
метаданные и названия. Biome разрешает эти два исключения только для stories;
|
|
||||||
`.storybook/main.ts` и `preview.tsx` разрешают только обязательный default export.
|
|
||||||
Synthetic fixtures и Storybook descriptions являются данными/документацией
|
|
||||||
истории; продуктовые UI-строки по-прежнему принадлежат i18n.
|
|
||||||
|
|
||||||
Mandatory review conventions:
|
|
||||||
|
|
||||||
- React components типизировать через `FC`: `const Component: FC<Props> = (props) => { ... }`; если props нет — `const Component: FC = () => { ... }`.
|
|
||||||
- Props описывать named type рядом с component либо в `Component.types.ts`, если type переиспользуется или file становится крупным.
|
|
||||||
- Не использовать inline props typing в component parameters.
|
|
||||||
- Для components только с `children` использовать `FC<PropsWithChildren>` без отдельного props alias. Если кроме `children` есть другие props, использовать named props type на основе `PropsWithChildren<{ ... }>`.
|
|
||||||
- Не destructure props в component parameters; принимать `props` и destructure внутри component body.
|
|
||||||
- Обычный module export surface сводить к одному нижнему named export block, когда это выражается читаемо без потери public API.
|
|
||||||
- Чистые barrel files должны использовать прямой re-export без промежуточного import: `export { Component } from "./Component";`.
|
|
||||||
|
|
||||||
## File roles
|
|
||||||
|
|
||||||
- `Component.tsx` - контейнер или публичный компонент; для data-driven pages вызывает page VM hook и выбирает top-level state view.
|
|
||||||
- `Component.vm.ts` - hook с данными, действиями, state selection и derived presentation data.
|
|
||||||
- `Component.view.tsx` - чистое представление: получает props, рендерит JSX и не вызывает data/auth/router/navigation/VM hooks.
|
|
||||||
- `Component.types.ts` - props и локальные типы.
|
|
||||||
- `Component.module.css` - локальные стили.
|
|
||||||
|
|
||||||
Самостоятельный top-level state или именуемый section обычно выносится в
|
|
||||||
соседний `*.view.tsx`, когда у него есть отдельная responsibility, reuse либо
|
|
||||||
change cadence или он скрывает principal component. Небольшой tightly-coupled
|
|
||||||
renderer остаётся рядом с parent. Предпочтительный flat pattern —
|
|
||||||
`components/AccountOperationsPageLoaded.view.tsx` без отдельной директории,
|
|
||||||
barrel, test и stylesheet для каждого компонента.
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
# Frontend Documentation
|
|
||||||
|
|
||||||
Эти документы задают frontend-local conventions и дополняют `AGENTS.md`.
|
|
||||||
Общая engineering policy находится в
|
|
||||||
[`../../docs/development/engineering-governance.md`](../../docs/development/engineering-governance.md),
|
|
||||||
а фактические версии зависимостей и scripts всегда определяются конфигурацией
|
|
||||||
проекта.
|
|
||||||
|
|
||||||
- `CONVENTIONS.md` - обязательные правила кода и i18n.
|
|
||||||
- `API-LAYER.md` - OpenAPI, generated types и typed client.
|
|
||||||
- `UI-PATTERN.md` - формы и UI-состояния.
|
|
||||||
- `STRUCTURE.md` - FSD-слои, импорты и public API слайсов.
|
|
||||||
- `TESTING.md` - Vitest и Testing Library.
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
# Structure
|
|
||||||
|
|
||||||
Проект использует FSD-направление импортов: `app -> pages -> features -> entities -> shared`.
|
|
||||||
|
|
||||||
- `app` - providers и routes; общий визуальный фундамент принадлежит shared/theme.
|
|
||||||
- `pages` - страницы маршрутов.
|
|
||||||
- `features` - пользовательские сценарии, включая authentication.
|
|
||||||
- `entities` - доменные модели и их UI/API.
|
|
||||||
- `shared` - переиспользуемый код без доменной логики: API, i18n и UI.
|
|
||||||
|
|
||||||
Каждый слайс предоставляет наружу только public API через `index.ts`; внутренние файлы соседний слой не импортирует напрямую.
|
|
||||||
|
|
||||||
Для cross-slice imports используйте alias `@/` и public API, например `@/entities/portfolio`. Vite и TypeScript используют одинаковый alias.
|
|
||||||
|
|
||||||
`shared/theme/tokens.json` владеет опубликованными токенами и описаниями ролей
|
|
||||||
в переносимом формате WealthLedger v1. `theme.ts` напрямую адаптирует доверенный
|
|
||||||
локальный JSON в Mantine без runtime-схемы. `shared/theme/index.ts` экспортирует theme,
|
|
||||||
designTokens, семантические color roles и CSSVariablesResolver и подключает
|
|
||||||
базовые styles. App и Storybook подключают свои MantineProvider к этому public API.
|
|
||||||
Внешние системы читают raw JSON напрямую, без frontend barrel и его runtime.
|
|
||||||
Неопубликованные defaults остаются у Mantine, локальные domain styles — у владельцев.
|
|
||||||
`shared/ui` предоставляет
|
|
||||||
StateView и LoadingSkeleton; их каталог не включает route-aware AppShell.
|
|
||||||
Подробнее: [дизайн-система](../../docs/development/design-system.md).
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
# Testing
|
|
||||||
|
|
||||||
Canonical completion gate — `npm run verify`; он запускает Vitest один раз
|
|
||||||
через `npm run test:coverage`. `npm run test:run` остаётся короткой локальной
|
|
||||||
командой без coverage report.
|
|
||||||
|
|
||||||
Выбор нового теста определяется риском поведения, а не фактом изменения файла.
|
|
||||||
Автоматический тест обязателен для security/privacy, денежных расчётов,
|
|
||||||
целостности данных, concurrency, публичного контракта, критического journey и
|
|
||||||
регрессии. DTO без логики, декларативная конфигурация, тонкая orchestration и
|
|
||||||
поведение зависимости отдельного unit test не требуют, пока у них нет
|
|
||||||
собственного branching, transformation, invariant или regression. Полная
|
|
||||||
policy находится в [engineering governance](../../docs/development/engineering-governance.md).
|
|
||||||
|
|
||||||
MSW запускается только Vitest setup в `src/test/`; handlers покрывают текущий API. Не добавлять service worker, browser mocking или mock development mode. Для transport проверять заголовки и retry через MSW, не через реальный backend.
|
|
||||||
|
|
||||||
Проверяйте наблюдаемое поведение, а не детали реализации. UI-тест строится как Arrange, Act, Assert; для интерактивных действий предпочитайте `userEvent`. Форма должна проверять валидацию и отсутствие запроса при невалидных данных; маршруты - редиректы и доступность экрана.
|
|
||||||
|
|
||||||
Colocated tests finance views фиксируют выбор loading/error/empty/loaded и доступные recovery actions. Route boundary tests используют test-only throwing route и проверяют безопасную ссылку и reset после navigation.
|
|
||||||
|
|
||||||
Coverage floors остаются anti-regression constraint. Нельзя добавлять тест,
|
|
||||||
который только повторяет декларативную структуру или внутренности зависимости,
|
|
||||||
ради повышения процента.
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
# UI Pattern
|
|
||||||
|
|
||||||
Формы используют `react-hook-form`, `zodResolver` и `FormProvider`. Form state создаётся в контейнере или VM, чистое представление получает `onSubmit` и `disabled`, а поля подключает через `useFormContext`.
|
|
||||||
|
|
||||||
Во время submit все действия формы блокируются. Ошибки Zod отображаются у поля; ошибки сети сохраняют введённые значения и показываются отдельным `Alert`.
|
|
||||||
|
|
||||||
Запросные экраны без успешных данных выбирают состояние в порядке loading, error, empty, loaded. Если query уже содержит данные, они остаются видимыми при фоновом refetch и его ошибке: рядом показываются неблокирующий progress и локальное предупреждение с retry. Такое состояние называется Client Cached Snapshot; оно не заменяет доменное server-side stale/fallback состояние.
|
|
||||||
|
|
||||||
Initial loading без доступного результата использует page-owned structural skeleton, повторяющий semantic regions экрана (heading, cards, filters, summaries или rows), а не `StateView` или отдельный spinner. Partial loading принадлежит section view и занимает только ещё не загруженный блок. Skeleton region имеет доступное busy-обозначение, но его декоративные shapes не содержат данных, не интерактивны и не получают фокус. Route fallback сохраняет доступный application shell и skeleton-изирует content area; до auth `ready` public и protected guards показывают neutral shell skeleton. Button `loading` для mutation и compact progress при Client Cached Snapshot остаются контекстными indicators и не заменяются skeleton.
|
|
||||||
|
|
||||||
Для finance-экранов используется граница `container -> ViewModel -> view`.
|
|
||||||
`*.tsx` без суффикса — container: он вызывает единственный page VM hook и
|
|
||||||
выбирает top-level Loading/Error/Empty/Loaded view. `*.vm.ts` владеет data
|
|
||||||
fetching, auth/route inputs, state selection, retry callbacks, domain grouping
|
|
||||||
и derived presentation data. `*.view.tsx` и соседние presentation components
|
|
||||||
получают props и рендерят JSX.
|
|
||||||
|
|
||||||
View modules не вызывают data/auth/router/navigation/VM hooks: никаких
|
|
||||||
`useBrokerage*`, `usePortfolio`, `useAuth`, `useParams`, `useNavigate` или
|
|
||||||
page VM hooks внутри `*.view.tsx` и `components/*.view.tsx`. View modules также
|
|
||||||
не группируют domain collections и не считают derived collection values вроде
|
|
||||||
долей Portfolio Position. Эти значения готовит VM.
|
|
||||||
|
|
||||||
Допустимые hooks во view: `useTranslation()` для labels, form-context hooks в
|
|
||||||
form views и локальный UI-only state для rendering fallback, например failed
|
|
||||||
image state. Простое форматирование одного переданного значения через
|
|
||||||
существующий formatter остаётся presentation concern; вычисления из всей
|
|
||||||
коллекции остаются во VM.
|
|
||||||
|
|
||||||
View files дробятся по ownership и смысловой нагрузке, а не по количеству строк.
|
|
||||||
Самостоятельные top-level states и sections выносятся в flat `components/*.view.tsx`,
|
|
||||||
если это делает principal component и границы изменений яснее. Маленькие
|
|
||||||
tightly-coupled renderers остаются рядом с parent; механическое правило «один
|
|
||||||
component — один file» и line-count gate не используются.
|
|
||||||
|
|
||||||
Known API errors, retry и stale snapshot остаются local views, а непойманные
|
|
||||||
route errors обрабатывает общий route-keyed ErrorBoundary.
|
|
||||||
Reference in New Issue
Block a user