From bcc034f19839a1aefadad978c5bb3a89d81ebe63 Mon Sep 17 00:00:00 2001 From: Sergey Krylov Date: Wed, 9 Sep 2026 16:22:47 +0300 Subject: [PATCH] docs(ai): route frontend conventions by task --- .ai/CONVENTIONS.md | 23 ++++++ .ai/SOURCES.md | 18 +++++ .ai/conventions/frontend-architecture.md | 56 ++++++++++++++ .ai/conventions/frontend-data.md | 56 ++++++++++++++ .ai/conventions/frontend-testing.md | 50 ++++++++++++ .ai/conventions/frontend-ui.md | 99 ++++++++++++++++++++++++ AGENTS.md | 51 +++++------- README.md | 21 +++-- docs/API-LAYER.md | 19 ----- docs/CONVENTIONS.md | 53 ------------- docs/README.md | 13 ---- docs/STRUCTURE.md | 24 ------ docs/TESTING.md | 23 ------ docs/UI-PATTERN.md | 37 --------- 14 files changed, 333 insertions(+), 210 deletions(-) create mode 100644 .ai/CONVENTIONS.md create mode 100644 .ai/SOURCES.md create mode 100644 .ai/conventions/frontend-architecture.md create mode 100644 .ai/conventions/frontend-data.md create mode 100644 .ai/conventions/frontend-testing.md create mode 100644 .ai/conventions/frontend-ui.md delete mode 100644 docs/API-LAYER.md delete mode 100644 docs/CONVENTIONS.md delete mode 100644 docs/README.md delete mode 100644 docs/STRUCTURE.md delete mode 100644 docs/TESTING.md delete mode 100644 docs/UI-PATTERN.md diff --git a/.ai/CONVENTIONS.md b/.ai/CONVENTIONS.md new file mode 100644 index 0000000..6e5582c --- /dev/null +++ b/.ai/CONVENTIONS.md @@ -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. diff --git a/.ai/SOURCES.md b/.ai/SOURCES.md new file mode 100644 index 0000000..8e55914 --- /dev/null +++ b/.ai/SOURCES.md @@ -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 по догадке. diff --git a/.ai/conventions/frontend-architecture.md b/.ai/conventions/frontend-architecture.md new file mode 100644 index 0000000..d0d49a8 --- /dev/null +++ b/.ai/conventions/frontend-architecture.md @@ -0,0 +1,56 @@ +# 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 не доказывает +правильный выбор слоя. diff --git a/.ai/conventions/frontend-data.md b/.ai/conventions/frontend-data.md new file mode 100644 index 0000000..7488a81 --- /dev/null +++ b/.ai/conventions/frontend-data.md @@ -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. diff --git a/.ai/conventions/frontend-testing.md b/.ai/conventions/frontend-testing.md new file mode 100644 index 0000000..eddb0fd --- /dev/null +++ b/.ai/conventions/frontend-testing.md @@ -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 -- ` без coverage. Полный `npm run verify` +запускает Vitest один раз через `test:coverage`. Coverage floors — anti-regression +constraint, не повод тестировать декларации. После UI изменения отдельно +проверь доступность/визуальные состояния, которых JSDOM не доказывает. diff --git a/.ai/conventions/frontend-ui.md b/.ai/conventions/frontend-ui.md new file mode 100644 index 0000000..9b7e494 --- /dev/null +++ b/.ai/conventions/frontend-ui.md @@ -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` и 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. diff --git a/AGENTS.md b/AGENTS.md index 14b5bc6..495c493 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,39 +1,24 @@ # 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), -[`docs/STRUCTURE.md`](docs/STRUCTURE.md), [`docs/API-LAYER.md`](docs/API-LAYER.md), -[`docs/UI-PATTERN.md`](docs/UI-PATTERN.md) и [`docs/TESTING.md`](docs/TESTING.md). +Перед Node-командами: `nvm use --silent` по `.nvmrc`. +После lockfile install `npm ci` полный gate — `npm run verify`. +Он проверяет 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`; это выбирает -Node 22. Не запускать отдельную проверку версии, кроме диагностики runtime issue. -После lockfile install единственный completion gate: - -```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. +Code/style checks автоматические только в scope конфигурации. i18n, props, +semantic placement и выбор полезных тестов обязательны при review. +Storybook уже доступен как reference; ожидаемый UI и его состояния определяет +задача, а визуальное соответствие проверяется отдельно от сборки. diff --git a/README.md b/README.md index ab3f45e..f41a705 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ backend; прямые вызовы T-Bank из frontend не допускают ```bash cp .env.example .env +nvm use --silent npm ci npm run dev ``` @@ -25,23 +26,27 @@ npm run verify Команда проверяет, что generated OpenAPI types соответствуют committed snapshot, затем запускает 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 document и затем выполните `npm run openapi:generate`. ## Документация -- [Структура и FSD](docs/STRUCTURE.md) -- [API layer и OpenAPI](docs/API-LAYER.md) -- [UI-паттерны](docs/UI-PATTERN.md) -- [Conventions](docs/CONVENTIONS.md) и [тестирование](docs/TESTING.md) +- [Router conventions](.ai/CONVENTIONS.md) и [источники](.ai/SOURCES.md) +- [Выбор owner/FSD](.ai/conventions/frontend-architecture.md) +- [UI, state, формы и компоненты](.ai/conventions/frontend-ui.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) - [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) - [Remediation registry](../docs/development/audit-remediation.md) Runtime OpenAPI backend сверяется с frontend snapshot отдельной read-only backend командой `npm run test:openapi-contract`. -Полный multi-repository change проверяется из workspace командой -native OpenSpec validation and the canonical gates of each affected repository. +Разработка идёт напрямую по задаче пользователя. Каждый affected repo проходит +свой verify; при изменении wire contract дополнительно выполняется contract check. +Обязательного OpenSpec/SDD lifecycle нет. diff --git a/docs/API-LAYER.md b/docs/API-LAYER.md deleted file mode 100644 index c152cec..0000000 --- a/docs/API-LAYER.md +++ /dev/null @@ -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 по смыслу, а не повторяет его поля. diff --git a/docs/CONVENTIONS.md b/docs/CONVENTIONS.md deleted file mode 100644 index c3d1797..0000000 --- a/docs/CONVENTIONS.md +++ /dev/null @@ -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 нет — `const Component: FC = () => { ... }`. -- Props описывать named type рядом с component либо в `Component.types.ts`, если type переиспользуется или file становится крупным. -- Не использовать inline props typing в component parameters. -- Для components только с `children` использовать `FC` без отдельного 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 для каждого компонента. diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 7047f79..0000000 --- a/docs/README.md +++ /dev/null @@ -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. diff --git a/docs/STRUCTURE.md b/docs/STRUCTURE.md deleted file mode 100644 index 2fe7189..0000000 --- a/docs/STRUCTURE.md +++ /dev/null @@ -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). diff --git a/docs/TESTING.md b/docs/TESTING.md deleted file mode 100644 index 5d83b11..0000000 --- a/docs/TESTING.md +++ /dev/null @@ -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. Нельзя добавлять тест, -который только повторяет декларативную структуру или внутренности зависимости, -ради повышения процента. diff --git a/docs/UI-PATTERN.md b/docs/UI-PATTERN.md deleted file mode 100644 index bcdf6aa..0000000 --- a/docs/UI-PATTERN.md +++ /dev/null @@ -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.