add: refactor foundation

This commit is contained in:
2026-08-14 09:49:55 +03:00
parent e3cc3d540b
commit 972356ee69
6 changed files with 202 additions and 3 deletions
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-08-14
@@ -0,0 +1,80 @@
## Контекст
Frontend уже использует React, FSD, generated OpenAPI types и React Query, но получает OpenAPI непосредственно из работающего backend и вручную создаёт query/mutation hooks. Bearer header одновременно добавляется общим HTTP transport и передаётся отдельными вызовами. Локальная проверка состоит из разрозненных команд и не проверяет generated artifact, не изолирует API-тесты и не контролирует границы FSD-слоёв.
Причина изменения описана в `proposal.md`, а контрактные требования — в `specs/api-contract-generation/spec.md`.
## Цели и нецели
**Цели:**
- Сделать настройку frontend, генерацию API-контракта и локальную проверку воспроизводимыми без запущенного backend, кроме явной синхронизации контракта.
- Централизовать типизированную интеграцию React Query и auth transport без изменения существующих пользовательских сценариев и семантики cache.
- Создать чистый, проверяемый и ориентированный на агентов baseline разработки с API mocks только для тестов.
- Зафиксировать реализованный workflow в документации frontend и постоянной документации проекта.
**Нецели:**
- Не мигрируются routes, forms, state-views, view-model и ErrorBoundary; они относятся к `align-frontend-ui-patterns`.
- Не добавляются Storybook, Playwright, browser MSW worker, mock-режим разработки и CI configuration.
- Не меняются владение backend OpenAPI, API routes, authentication protocol и доменная терминология WealthLedger.
## Решения
### Воспроизводимый frontend baseline
Frontend будет объявлять Node 22 через `.nvmrc` и `package.json#engines`. Зависимости будут использовать точные версии, а lockfile будет перегенерирован. Toolchain перейдёт на совместимый baseline template, включая Mantine 9, строгие опции TypeScript, aliases Vite/TypeScript и tooling статического анализа.
Aliases будут одинаково определены в Vite и TypeScript. Существующие cross-slice imports будут использовать публичные API через `index.ts`; файлы внутри slice могут использовать относительные imports. `dependency-cruiser` будет проверять только направление FSD-слоёв, а использование public API останется документированным правилом review.
Точные версии выбраны вместо semver ranges, чтобы установки агентов и разработчиков воспроизводили один dependency graph. Собственный Node version checker отклонён: `.nvmrc` и `engines` сообщают ограничение без дополнительного пути к ошибке.
### Генерация OpenAPI из snapshot
`src/shared/api/spec/` будет хранить committed raw JSON snapshot из backend OpenAPI endpoint, а `src/shared/api/generated/schema.d.ts` останется committed generated artifact. Явная sync-команда получит endpoint, настроенный `VITE_API_BASE_URL`, провалидирует его генерацией и заменит оба artifacts только после успеха всех шагов. Временные файлы не позволят неудачному fetch или generation частично обновить контракт.
`openapi:generate` будет генерировать из committed snapshot. `openapi:check` независимо сгенерирует ожидаемую schema во временную директорию и сравнит её с committed artifact. Команда не обращается к backend, поэтому `verify` остаётся доступным offline.
Генерация напрямую из работающего backend отклонена, потому что обычные локальные проверки станут зависимы от его доступности. Преобразование snapshot в YAML отклонено, потому что сохранение опубликованного JSON исключает ненужную трансформацию.
### Типизированная интеграция queries и auth boundary
Shared OpenAPI client остаётся единственной HTTP entry point. `openapi-react-query` будет получать из него типизированные React Query operations; существующие query keys, `enabled`, stale times, retry policies, invalidations и отображение ошибок сохранятся. API request и response types продолжат выводиться из generated `paths`, а не дублировать DTOs.
Реализация authenticated fetch становится единственным владельцем Bearer header. Она исключает auth endpoints, делает ровно один retry только после `401` с `UNAUTHORIZED` и успешного refresh, а при неудачном refresh очищает session state. Query и mutation calls больше не передают Authorization самостоятельно. Initial session restoration должен обновлять и React state, и token reference, используемый transport; logout и неудачный refresh очищают оба и cache QueryClient.
Ручная передача headers отклонена: она дублирует cross-cutting security concern и может разойтись с поведением transport. Замена refresh semantics на library interceptor отклонена, потому что существующие refresh single-flight и browser lock уже являются корректным product behavior.
### MSW только для тестов и целевые regression tests
MSW будет запускаться только через Vitest setup и handlers для текущего WealthLedger API. Не будут добавлены service worker, команда `dev:mock`, mock build и application mock mode. Поэтому unit и hook tests смогут выполнять успешные и ошибочные API scenarios без backend.
Целевые transport tests проверят Bearer injection, исключение auth endpoints, один refresh/retry для `UNAUTHORIZED`, очистку session после неудачного refresh и авторизацию восстановленной session. Coverage формируется как report, но не получает числовой threshold до стабилизации UI patterns.
Browser mocking отклонён, потому что он создаёт незапрошенный второй application mode. Coverage threshold отклонён, потому что текущий baseline слишком мал для стабильной и содержательной цели.
### Единый локальный quality gate и постоянная документация
`npm run verify` будет запускать OpenAPI drift detection, typecheck, Biome, unit tests, coverage reporting, Knip, dependency-cruiser и production build. Все проверки должны проходить без baseline suppressions или allowlists. Husky устанавливается через `prepare`; pre-commit форматирует staged files и запускает contract и dependency checks, а commit-msg проверяет Conventional Commits.
Frontend `AGENTS.md` остаётся коротким индексом «задача → документ». Его локальные docs описывают API workflow, test setup, structure и conventions. После реализации `docs/development/` получит постоянное cross-repository описание frontend workflow: требуемую Node version, contract synchronization и quality-gate commands. Так знания о реализованном процессе не останутся только в frontend repository.
Запуск полного gate в каждом pre-commit hook отклонён, потому что tests и builds замедляют обычные commits; `verify` остаётся единственным явным полным gate. Storybook и e2e build steps исключены вместе с их tools.
## Риски и компромиссы
- [Обновления Mantine и совместимых зависимостей могут выявить несовместимости исходного кода] → Устранить все type, lint и build errors до принятия нового lockfile.
- [Committed snapshot может отставать от backend] → Backend остаётся источником истины; явный sync обновляет snapshot, а локальная проверка гарантирует только внутреннюю согласованность.
- [Централизация Bearer injection может сломать восстановленные sessions] → Синхронизировать token reference во время initial refresh и покрыть поведение transport tests.
- [Static analyzers могут сообщить о существующем коде] → Исправить код или точно настроить tool до получения зелёного baseline; не скрывать findings временными waivers.
- [Hooks запускаются не во всех окружениях разработчиков] → `npm run verify` остаётся независимо запускаемым и документированным completion gate.
## План миграции
1. Обновить и зафиксировать frontend toolchain, настроить aliases и static-analysis tools, привести текущий исходный код к новым checks.
2. Добавить OpenAPI snapshot, sync/generate/check commands, затем проверить generated artifact из committed snapshot без доступа к backend.
3. Мигрировать API queries и mutations на typed integration, централизовать Bearer handling и добавить transport regression coverage.
4. Добавить MSW только для тестов, coverage reporting, git hooks и единый скрипт `verify`.
5. Обновить frontend agent documentation и `docs/development/` описанием реализованного workflow, затем запустить полный quality gate.
Изменение локально для frontend и может быть отменено откатом его frontend и documentation commits. Backend contract не меняется.
@@ -0,0 +1,28 @@
## Why
Текущий WealthLedger frontend имеет базовый React/FSD-стек, но не содержит проверенный агентский workflow из `references/frontend-ai-template`: воспроизводимую среду, единый quality gate, проверку дрейфа OpenAPI и изолированные API-тесты. Это делает работу агентов зависимой от локального backend и ручной дисциплины.
## What Changes
- Выравнивается frontend toolchain с проверенным template: Node 22, фиксированные версии зависимостей, strict TypeScript, aliases, Mantine 9 и tooling для статического анализа.
- Frontend хранит версионируемый snapshot опубликованного backend OpenAPI, генерирует schema из него и проверяет отсутствие дрейфа generated artifact.
- Typed API layer переходит на `openapi-react-query`, сохраняя текущую auth-семантику Bearer token, refresh и retry.
- Добавляются MSW handlers только для unit-тестов, единый `verify`, coverage report, git hooks и Conventional Commits.
- Документация и `AGENTS.md` расширяются до agent-oriented структуры template, без переноса Todo-домена.
- Playwright, Storybook и CI не входят в это изменение.
## Capabilities
### New Capabilities
<!-- None. -->
### Modified Capabilities
- `api-contract-generation`: frontend хранит проверяемый snapshot backend OpenAPI и детерминированно генерирует из него transport types.
## Impact
- Затрагивает `frontend` package/configuration, API client, тестовую инфраструктуру, локальную документацию и git hooks.
- Backend остаётся источником истины для OpenAPI и используется только командой синхронизации snapshot.
- Требует обновления зависимостей и lockfile frontend-репозитория.
@@ -0,0 +1,40 @@
## MODIFIED Requirements
### Requirement: Frontend использует сгенерированный API-клиент
Frontend SHALL хранить в репозитории версионируемый snapshot опубликованного OpenAPI-документа и генерировать transport types из этого snapshot. Команда синхронизации MUST получать документ из backend, а затем обновлять snapshot и сгенерированный результат только при успешном завершении всего процесса. Сгенерированный результат MUST храниться в frontend-репозитории.
#### Scenario: API-контракт синхронизируется
- **WHEN** разработчик запускает команду синхронизации при доступном backend с валидным OpenAPI-документом
- **THEN** frontend обновляет versioned snapshot и сгенерированные transport types из опубликованного контракта
#### Scenario: API-контракт изменяется
- **WHEN** backend меняет опубликованный API-контракт
- **THEN** frontend может синхронизировать snapshot и повторно сгенерировать transport types до интеграции изменения
#### Scenario: Синхронизация контракта завершается ошибкой
- **WHEN** backend недоступен или публикует невалидный OpenAPI-документ
- **THEN** команда завершается с ошибкой и существующие snapshot и сгенерированные transport types не изменяются
### Requirement: Frontend не зависит от OpenAPI во время работы
Работающий frontend MUST использовать ранее сгенерированные transport types и MUST NOT требовать доступность Swagger или OpenAPI endpoint во время запуска, выполнения пользовательского сценария или локальной проверки актуальности generated artifact. Доступ к backend OpenAPI endpoint разрешён только команде синхронизации snapshot.
#### Scenario: OpenAPI endpoint недоступен после сборки frontend
- **WHEN** frontend уже собран, а endpoint OpenAPI временно недоступен
- **THEN** frontend продолжает использовать сгенерированный код для запросов к API
#### Scenario: Локальная проверка выполняется без backend
- **WHEN** разработчик запускает локальный quality gate без доступного backend
- **THEN** проверка актуальности generated artifact использует сохранённый snapshot и не обращается к OpenAPI endpoint
## ADDED Requirements
### Requirement: Frontend обнаруживает дрейф сгенерированного контракта
Frontend SHALL проверять, что committed generated transport types соответствуют versioned OpenAPI snapshot, и MUST завершать quality gate с ошибкой при расхождении.
#### Scenario: Generated artifact устарел
- **WHEN** OpenAPI snapshot изменён, а generated transport types не обновлены
- **THEN** проверка контракта завершается с ошибкой
#### Scenario: Generated artifact изменён вручную
- **WHEN** generated transport types отличаются от результата генерации из сохранённого snapshot
- **THEN** проверка контракта завершается с ошибкой
@@ -0,0 +1,26 @@
## 1. Воспроизводимый baseline
- [x] 1.1 Зафиксировать Node 22, exact dependency versions и обновлённый lockfile; обновить совместимые React/Mantine/TypeScript toolchain configuration.
- [x] 1.2 Настроить Vite/TypeScript aliases, Knip и `dependency-cruiser`; перевести существующие межслайсовые импорты на public `index.ts` APIs и устранить все findings новых checks.
## 2. Локальный OpenAPI contract
- [x] 2.1 Добавить committed raw OpenAPI JSON snapshot и явную атомарную sync-команду, получающую опубликованный backend contract только по запросу разработчика.
- [x] 2.2 Генерировать `schema.d.ts` из committed snapshot и добавить offline `openapi:check`, который обнаруживает устаревший или вручную изменённый generated artifact.
## 3. Типизированный API и тесты
- [x] 3.1 Подключить `openapi-react-query` к shared typed client и мигрировать текущие API queries и mutations, сохранив cache, retry и UI error semantics.
- [x] 3.2 Сделать authenticated fetch единственным владельцем Bearer header; сохранить refresh single-flight/retry semantics и синхронизацию token reference при initial session restoration.
- [x] 3.3 Добавить MSW только в Vitest и handlers текущего API; покрыть transport auth invariants и изолированные успешные и ошибочные API scenarios.
## 4. Единый quality gate
- [x] 4.1 Добавить coverage report и `npm run verify` с OpenAPI drift, typecheck, Biome, unit tests, coverage, Knip, FSD dependency checks и production build.
- [x] 4.2 Настроить Husky, lint-staged и Conventional Commits: быстрый pre-commit для staged formatting, contract/dependency checks и commit-msg validation.
## 5. Документация и проверка
- [x] 5.1 Обновить frontend `AGENTS.md` и тематические docs для agent-oriented workflow без UI, Storybook и e2e guidance.
- [x] 5.2 Добавить в `docs/development/` постоянную документацию реализованного frontend workflow: Node version, contract sync и quality gate.
- [x] 5.3 Выполнить `npm ci` и `npm run verify`; убедиться, что baseline проходит без suppressions и без доступного backend после завершения sync.
+26 -3
View File
@@ -14,15 +14,38 @@ Backend SHALL публиковать версионированный OpenAPI-д
- **THEN** разработчик может получить OpenAPI-документ для генерации frontend-клиента
### Requirement: Frontend использует сгенерированный API-клиент
Frontend SHALL генерировать типы и модуль HTTP-запросов из опубликованного OpenAPI-документа. Сгенерированный результат MUST храниться в frontend-репозитории и обновляться локальной воспроизводимой командой.
Frontend SHALL хранить в репозитории версионируемый snapshot опубликованного OpenAPI-документа и генерировать transport types из этого snapshot. Команда синхронизации MUST получать документ из backend, а затем обновлять snapshot и сгенерированный результат только при успешном завершении всего процесса. Сгенерированный результат MUST храниться в frontend-репозитории.
#### Scenario: API-контракт синхронизируется
- **WHEN** разработчик запускает команду синхронизации при доступном backend с валидным OpenAPI-документом
- **THEN** frontend обновляет versioned snapshot и сгенерированные transport types из опубликованного контракта
#### Scenario: API-контракт изменяется
- **WHEN** backend меняет опубликованный API-контракт
- **THEN** frontend может повторно сгенерировать типы и запросный модуль до интеграции изменения
- **THEN** frontend может синхронизировать snapshot и повторно сгенерировать transport types до интеграции изменения
#### Scenario: Синхронизация контракта завершается ошибкой
- **WHEN** backend недоступен или публикует невалидный OpenAPI-документ
- **THEN** команда завершается с ошибкой и существующие snapshot и сгенерированные transport types не изменяются
### Requirement: Frontend не зависит от OpenAPI во время работы
Работающий frontend MUST использовать ранее сгенерированный клиент и MUST NOT требовать доступность Swagger или OpenAPI endpoint во время запуска или выполнения пользовательского сценария.
Работающий frontend MUST использовать ранее сгенерированные transport types и MUST NOT требовать доступность Swagger или OpenAPI endpoint во время запуска, выполнения пользовательского сценария или локальной проверки актуальности generated artifact. Доступ к backend OpenAPI endpoint разрешён только команде синхронизации snapshot.
#### Scenario: OpenAPI endpoint недоступен после сборки frontend
- **WHEN** frontend уже собран, а endpoint OpenAPI временно недоступен
- **THEN** frontend продолжает использовать сгенерированный код для запросов к API
#### Scenario: Локальная проверка выполняется без backend
- **WHEN** разработчик запускает локальный quality gate без доступного backend
- **THEN** проверка актуальности generated artifact использует сохранённый snapshot и не обращается к OpenAPI endpoint
### Requirement: Frontend обнаруживает дрейф сгенерированного контракта
Frontend SHALL проверять, что committed generated transport types соответствуют versioned OpenAPI snapshot, и MUST завершать quality gate с ошибкой при расхождении.
#### Scenario: Generated artifact устарел
- **WHEN** OpenAPI snapshot изменён, а generated transport types не обновлены
- **THEN** проверка контракта завершается с ошибкой
#### Scenario: Generated artifact изменён вручную
- **WHEN** generated transport types отличаются от результата генерации из сохранённого snapshot
- **THEN** проверка контракта завершается с ошибкой