docs: document frontend audit remediation delivery
This commit is contained in:
@@ -0,0 +1,27 @@
|
|||||||
|
# Frontend audits: август 2026
|
||||||
|
|
||||||
|
Неизменяемые копии трёх исходных reports, сохранённые 06.09.2026. Claims
|
||||||
|
отражают состояние на дату соответствующего аудита и не являются текущим
|
||||||
|
verdict или нормативными требованиями. Исходные тексты, включая старые пути
|
||||||
|
и результаты проверок, сохранены byte-for-byte.
|
||||||
|
|
||||||
|
| Source | Дата аудита | SHA-256 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| [Общий repository audit](frontend-repository-audit.md) | 31.08.2026 | `1071171cfc987113ea209383c687b0b54932e5a390726ca62650b0de90a722b6` |
|
||||||
|
| [Repository audit 28 августа](frontend-repository-audit-2026-08-28.md) | 28.08.2026 | `6b7968ebeb7eac1017e2c75e7e7888486d8a232bed7730f760960d77c5343e24` |
|
||||||
|
| [Structural maintainability audit](frontend-structural-maintainability-audit.md) | 31.08.2026 | `7b5de5c82951f70b8ec7431468ed5cffee3bcf98ba09b029d327a8bb72c1fc36` |
|
||||||
|
|
||||||
|
Исходные locations относительно workspace: `frontend-repository-audit.md`,
|
||||||
|
`.audits/frontend-repository-audit-2026-08-28.md` и
|
||||||
|
`frontend-structural-maintainability-audit.md` соответственно.
|
||||||
|
|
||||||
|
Все 22 source findings сопоставлены 18 нормализованным записям FE26-01–FE26-18
|
||||||
|
в [едином remediation registry](../../development/audit-remediation.md#frontend-audits-август-2026).
|
||||||
|
Повторяющиеся номера F относятся к разным reports; source ID сохраняется
|
||||||
|
в каждой записи. Текущие статусы и последующее evidence обновляются только
|
||||||
|
в registry, не в snapshots.
|
||||||
|
|
||||||
|
Snapshot от 28 августа содержит исходное форматирование таблиц, несовместимое
|
||||||
|
с MD060. Он исключён из style gate тем же способом, что и другие raw audit
|
||||||
|
sources; byte-for-byte сохранность проверяется отдельно. Этот index и два
|
||||||
|
остальных snapshots остаются в Markdown gate.
|
||||||
@@ -0,0 +1,151 @@
|
|||||||
|
# Repository Audit — frontend
|
||||||
|
|
||||||
|
Дата аудита: 2026-08-28. Аудит выполнен только для чтения; этот файл — единственная запись, созданная в рамках работы. Эталонный каталог использовался как ориентир, а не как источник достоверной информации.
|
||||||
|
|
||||||
|
## Scope and Confidence
|
||||||
|
|
||||||
|
**Целевой репозиторий:** /Users/ksv741/Project/vibe/WealthLedger/frontend
|
||||||
|
**Эталон:** /Users/ksv741/Project/vibe/WealthLedger/references/frontend-ai-template
|
||||||
|
|
||||||
|
Цель — React 19 / TypeScript / Vite SPA WealthLedger с typed OpenAPI-клиентом, TanStack Query и FSD-направлением зависимостей. Проверены AGENTS.md, README, локальные docs, package и lock-конфигурация, hooks, Biome, dependency-cruiser, Knip, Vitest/Vite/TypeScript-конфигурация, основной app/auth/API путь, entity query hooks, финансовые страницы, настройки T-Bank connection и их тесты. В эталоне проверены эквивалентные scripts, hook, OpenAPI guardrail, тестовая/документационная структура и representative source tree.
|
||||||
|
|
||||||
|
Исходное и финальное состояние target: ветка main без tracked или untracked изменений; git diff и staged diff пусты. Эталон — read-only каталог без собственного Git-репозитория, поэтому его рабочее состояние не верифицировалось.
|
||||||
|
|
||||||
|
Запущены только заведомо read-only проверки:
|
||||||
|
|
||||||
|
- npm run lint — прошло; Biome проверил 128 файлов, без исправлений.
|
||||||
|
- npm run knip — прошло.
|
||||||
|
- npm run deps — прошло; dependency-cruiser проверил 136 модулей и 484 зависимости, нарушений нет.
|
||||||
|
|
||||||
|
В audit-terminal команда nvm use --silent недоступна, поэтому Node 22 через .nvmrc не был активирован этой оболочкой; version mismatch не диагностировался. Не запускались npm ci, verify, build, coverage-тесты, typecheck и генерация OpenAPI: они могут создавать или обновлять install, coverage, dist, tsbuildinfo либо generated artifacts. Реальный backend, browser E2E и runtime OpenAPI contract также не запускались. Эти ограничения снижают уверенность только в runtime-свойствах, не в трёх статических findings ниже.
|
||||||
|
|
||||||
|
### Сравнение с эталоном
|
||||||
|
|
||||||
|
| Эквивалентная обязанность | Вердикт | Причина |
|
||||||
|
|---|---|---|
|
||||||
|
| Контроль синхронности OpenAPI source и generated types | **adopt from reference** | В эталоне есть небольшой temp-file check, включённый в verify и pre-commit; target хранит такие же два артефакта, но не имеет проверки. |
|
||||||
|
| Coverage anti-regression | **target is already better** | Target задаёт явные coverage thresholds в Vitest; в эталонной конфигурации их нет. |
|
||||||
|
| Storybook, Playwright, browser MSW и mock development mode | **reference approach is unnecessary** | Эталон — демонстрационный SPA-шаблон. Target AGENTS.md прямо запрещает эти добавления без approved change, а проверенный scope не показал конкретной проблемы, которую оправдывает весь этот lifecycle. |
|
||||||
|
|
||||||
|
## Executive Summary
|
||||||
|
|
||||||
|
Репозиторий находится в хорошем состоянии по типизации, зависимостям, локальному quality gate и навигации для агента. Biome, Knip и dependency-cruiser проходят, а auth, query retry, error states и display-logic имеют целевые тесты.
|
||||||
|
|
||||||
|
Два высокоприоритетных пробела делают эту базу менее надёжной, чем обещают её инварианты:
|
||||||
|
|
||||||
|
1. Смена или удаление T-Bank connection оставляет connection-scoped финансовые данные в React Query cache.
|
||||||
|
2. Committed OpenAPI snapshot и generated types могут разойтись, а verify всё равно остаться зелёным.
|
||||||
|
|
||||||
|
Отдельно, обязательная VM/view и state-view конвенция уже расходится с несколькими production screens. Это создаёт агентам и разработчикам два конкурирующих образца вместо одного канонического.
|
||||||
|
|
||||||
|
## Priority Findings
|
||||||
|
|
||||||
|
| Severity | Finding | Category | Action | Effort |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| high | F1 — Нет единого invalidation boundary для данных T-Bank connection | Architecture / Data correctness | change | medium |
|
||||||
|
| high | F2 — Generated OpenAPI type artifact не проверяется на синхронность | Tooling / Contract safety | add | small |
|
||||||
|
| medium | F3 — Обязательная VM/view конвенция расходится с production code | Architecture / AI Readiness | change | medium |
|
||||||
|
|
||||||
|
## Findings
|
||||||
|
|
||||||
|
### F1 — Нет единого invalidation boundary для данных T-Bank connection
|
||||||
|
|
||||||
|
**Category:** Architecture / Data correctness
|
||||||
|
**Severity:** high
|
||||||
|
**Confidence:** high
|
||||||
|
**Action:** change
|
||||||
|
**Effort:** medium
|
||||||
|
|
||||||
|
**Observed:** Успешные PUT и DELETE T-Bank connection обновляют status и инвалидируют только query списка brokerage accounts. Portfolio, operations, future events, income summary и income analytics продолжают жить в cache по accountId.
|
||||||
|
|
||||||
|
**Evidence:** src/features/tbank-connection/ui/TbankConnectionSection.tsx:60-79 вызывает invalidateQueries только с brokerageAccountsQueryKey. Все перечисленные data hooks создают самостоятельные account-scoped query keys и оставляют данные видимыми: src/entities/portfolio/api/usePortfolio.ts:12-21, src/entities/broker-operation/api/useBrokerageOperations.ts:35-49, src/entities/brokerage-event/api/useBrokerageEvents.ts:9-19, src/entities/income/api/useIncome.ts:9-41. UI pattern намеренно оставляет Client Cached Snapshot на экране во время background refresh; для замены либо удаления source connection это старое состояние уже не является допустимым snapshot. Тесты settings-сценария проверяют только invalidation account-list: src/features/tbank-connection/ui/TbankConnectionSection.test.tsx:52-98.
|
||||||
|
|
||||||
|
**Counter-evidence:** Logout и неуспешный refresh очищают весь QueryClient в src/features/auth/model/auth.tsx:57-62, а account-list действительно инвалидируется при смене connection. Эта защита не срабатывает, когда пользователь остаётся в той же authenticated session и только заменяет или удаляет connection.
|
||||||
|
|
||||||
|
**Why it matters:** После замены token или отключения connection пользователь может увидеть cache старого portfolio, ledger, events либо income до следующего рефетча. Для финансовых данных это ошибочная принадлежность данных к уже изменившемуся upstream connection; на активном экране она особенно заметна, потому что UI специально отображает cached result при обновлении.
|
||||||
|
|
||||||
|
**Reference repository:** Нет эквивалентного authenticated external-connection domain в todo-template; сравнение неприменимо.
|
||||||
|
|
||||||
|
**Recommended direction:** Ввести один явно названный ownership boundary для всех данных, производных от T-Bank connection, и при replace/delete очищать либо корректно инвалидировать весь этот scope, а не только список счетов. Предпочтение следует отдать удалению snapshot, который больше нельзя показывать. Добавить один интеграционный тест, seed-ящий representative query каждого класса и подтверждающий, что после обоих mutation paths эти данные недоступны для рендера.
|
||||||
|
|
||||||
|
### F2 — Generated OpenAPI type artifact не проверяется на синхронность
|
||||||
|
|
||||||
|
**Category:** Tooling / Contract safety
|
||||||
|
**Severity:** high
|
||||||
|
**Confidence:** high
|
||||||
|
**Action:** add
|
||||||
|
**Effort:** small
|
||||||
|
|
||||||
|
**Observed:** Target хранит committed OpenAPI snapshot и committed generated schema.d.ts, но package scripts и pre-commit не имеют команды, которая пересоздаёт schema во временном месте и сравнивает результат. Поэтому source snapshot можно изменить без regeneration, а TypeScript продолжит проверять устаревший generated contract.
|
||||||
|
|
||||||
|
**Evidence:** package.json:9-25 содержит openapi:generate, но не openapi:check; verify на строке 20 его не вызывает. .husky/pre-commit:1-2 запускает только lint-staged и deps. При этом AGENTS.md:42 утверждает, что openapi:check есть в verify и pre-commit, а docs/CONVENTIONS.md:12-14 называет OpenAPI check обязательным pre-commit step. Поиск по target не нашёл openapi:check. docs/API-LAYER.md:1-5 подтверждает, что snapshot и generated types должны быть committed и обновляться отдельно.
|
||||||
|
|
||||||
|
**Counter-evidence:** Документация предписывает ручной порядок «скопировать snapshot → сгенерировать types», а backend располагает отдельной runtime contract-проверкой. Ни один из этих механизмов не делает локальный frontend gate самодостаточным: ошибка ручной regeneration остаётся незамеченной до более поздней стадии.
|
||||||
|
|
||||||
|
**Why it matters:** Зелёный verify может закрепить stale request/response types после изменения backend contract. Это подрывает заявленную compile-time безопасность API и способно привести к неверно сформированному запросу либо к коду, опирающемуся на устаревшую response shape.
|
||||||
|
|
||||||
|
**Reference repository:** **adopt from reference.** references/frontend-ai-template/scripts/check-openapi.mjs:1-32 генерирует во временный файл и байтово сравнивает его с committed schema; package.json:13-28 и .husky/pre-commit:1 включают проверку в verify и hook. Это небольшой fitting guardrail, не требующий нового dependency или runtime backend.
|
||||||
|
|
||||||
|
**Recommended direction:** Добавить target-specific offline check для openapi.json → schema.d.ts, подключить его к verify и pre-commit, затем исправить AGENTS.md/README/conventions так, чтобы они описывали фактический gate. Не включать backend fetch или автоматическую генерацию в verify: gate должен оставаться детерминированным и read-only относительно tracked artifacts.
|
||||||
|
|
||||||
|
### F3 — Обязательная VM/view конвенция расходится с production code
|
||||||
|
|
||||||
|
**Category:** Architecture / AI Readiness
|
||||||
|
**Severity:** medium
|
||||||
|
**Confidence:** high
|
||||||
|
**Action:** change
|
||||||
|
**Effort:** medium
|
||||||
|
|
||||||
|
**Observed:** Документация требует, чтобы data-driven finance page была container → VM → view, а top-level states/sections находились в отдельных files. Несколько текущих screens демонстрируют обратное: AccountEventsPage одновременно читает route params, вызывает query hook и объявляет Loading/Error/Empty/Loaded components в одном file; AccountPage и AccountsPage держат четыре top-level state components в одном .view.tsx; AccountIncomePage держит три.
|
||||||
|
|
||||||
|
**Evidence:** docs/CONVENTIONS.md:12-14 и AGENTS.md:36 описывают правило как обязательное; docs/UI-PATTERN.md описывает владение data/state VM и отдельные state views. Прямой контрпример — src/pages/account-events/ui/AccountEventsPage.tsx:1-116. Остальные representative deviations: src/pages/account/ui/AccountPage.view.tsx:54-147, src/pages/accounts/ui/AccountsPage.view.tsx:42-124 и src/pages/account-income/ui/AccountIncomePage.view.tsx:29-52. Для contrast src/pages/account-operations/ui уже использует VM и отдельные component state views. Lint и dependency check проходят, поскольку правило не имеет machine enforcement.
|
||||||
|
|
||||||
|
**Counter-evidence:** FSD direction, public APIs и circular imports успешно проверяются dependency-cruiser; большинство finance data derivation действительно вынесено в VM. Это ограничивает scope finding до понятности и consistency page-level pattern, а не до общей архитектурной целостности.
|
||||||
|
|
||||||
|
**Why it matters:** Новый агент получает обязательную инструкцию, которую production tree нарушает в нескольких наиболее заметных screens. Это увеличивает navigation cost и делает review субъективным: один и тот же UI change можно «правильно» продолжить по двум несовместимым образцам.
|
||||||
|
|
||||||
|
**Reference repository:** **both are acceptable в части принципа**, но более мелкая per-state организация эталона подтверждает полезность разделения там, где data/state branches уже самостоятельны. Переносить его Storybook/e2e scaffolding или создавать новый page framework не требуется.
|
||||||
|
|
||||||
|
**Recommended direction:** Выбрать один честный canonical pattern: привести существующие named pages к заявленной VM/state-view границе там, где branches самостоятельны, либо сузить документацию, прямо разрешив компактные colocated views для небольших screens. После выбора оставить FSD проверки как есть; новый dependency или тяжёлый linter ради одного file-layout правила не оправдан.
|
||||||
|
|
||||||
|
## Missing Practices
|
||||||
|
|
||||||
|
- **F1:** отсутствует connection-scoped cache ownership и test, проверяющий его очистку при trust-boundary change.
|
||||||
|
- **F2:** отсутствует детерминированная offline-проверка committed OpenAPI source и generated type artifact.
|
||||||
|
|
||||||
|
## Over-engineering / Simplification Opportunities
|
||||||
|
|
||||||
|
Материальной production over-engineering проблемы не найдено. Компоненты LoadingSkeleton и StateView повторно используют доступные state regions; query/auth seams обслуживают реальные trust и concurrency requirements. Самая выгодная simplification — не добавлять параллельные способы описания page architecture: закрыть F3 одним каноническим соглашением.
|
||||||
|
|
||||||
|
## AI Readiness
|
||||||
|
|
||||||
|
Оценка: **хорошая, с одним существенным navigation risk**. AGENTS.md кратко направляет к специализированной документации, scripts узнаваемы, FSD-boundary gate и generated-code exclusion ясны. F2 делает API change небезопасным несмотря на эти подсказки, а F3 заставляет агент выбирать между текстовыми правилами и существующим кодом. Устранение этих двух findings улучшит predictability без расширения toolchain.
|
||||||
|
|
||||||
|
## What Is Already Good
|
||||||
|
|
||||||
|
- Строгая TypeScript-конфигурация, exact dependency versions, package-lock, Biome, Husky/commitlint и Node 22 intent хорошо определяют baseline.
|
||||||
|
- Dependency-cruiser проверяет FSD direction, отсутствие circular imports и public slice API; текущий graph проходит (136 modules, 484 dependencies).
|
||||||
|
- API transport отделён в shared typed client, auth request исключены из Bearer attachment, refresh имеет single-flight путь и tests на retry/failure.
|
||||||
|
- Query hooks задают явные staleTime и retry policy; UI/tests покрывают cached snapshot, refresh error, route boundary, portfolio display и operation filtering.
|
||||||
|
- Target coverage thresholds — более практичный anti-regression guard, чем эталонный template без порогов.
|
||||||
|
|
||||||
|
## Recommended Order
|
||||||
|
|
||||||
|
### Do now
|
||||||
|
|
||||||
|
1. Закрыть F1: сделать T-Bank connection change полноценной границей очистки data cache и добавить regression test.
|
||||||
|
2. Закрыть F2: добавить offline OpenAPI synchronization check в verify/pre-commit, затем синхронизировать документацию.
|
||||||
|
|
||||||
|
### Do next
|
||||||
|
|
||||||
|
1. Закрыть F3: согласовать page-level VM/state-view rule с production tree и обновить только выбранный source of truth.
|
||||||
|
|
||||||
|
### Consider later
|
||||||
|
|
||||||
|
Переоценить browser-level critical journeys только при approved change, который меняет соответствующий пользовательский риск; текущая статическая evidence не оправдывает широкий test-stack перенос из template.
|
||||||
|
|
||||||
|
### Do not add
|
||||||
|
|
||||||
|
- Storybook, Playwright, browser MSW worker или mock development mode только потому, что они есть в эталоне.
|
||||||
|
- CI/CD, deployment tooling или cloud configuration: system scope остаётся локальным.
|
||||||
|
- Ещё один FSD/framework linter для VM/view file layout: сначала нужно устранить противоречие F3, а текущий dependency-cruiser уже покрывает доказанные dependency risks.
|
||||||
@@ -0,0 +1,510 @@
|
|||||||
|
# Repository Audit: WealthLedger Frontend
|
||||||
|
|
||||||
|
**Дата:** 2026-08-31
|
||||||
|
**Session ID:** `01a05590-9243-7df0-9664-508acc5fc3e9`
|
||||||
|
**Invoked skills:** `repository-audit`
|
||||||
|
**Supporting skills invoked:** none
|
||||||
|
**External research performed:** no
|
||||||
|
|
||||||
|
## Scope and Confidence
|
||||||
|
|
||||||
|
**Target:** `frontend/`, Git branch `main`.
|
||||||
|
**Target commit:** `7ab043dce63a3fbacbfc9ac7d15b00dd714a541f`
|
||||||
|
(`docs: remove useless`).
|
||||||
|
**Initial target state:** `git status --porcelain=v1 --untracked-files=all` был
|
||||||
|
пуст; целевой репозиторий не содержал tracked или untracked изменений.
|
||||||
|
|
||||||
|
**Reference:** `references/frontend-ai-template/`. Директория не является
|
||||||
|
Git-репозиторием, поэтому commit identity недоступна. Идентичность
|
||||||
|
зафиксирована SHA-256 checksum аудируемого содержимого:
|
||||||
|
`7fced9c6906bdc846847eec40e05aac7c2c59f9fe593acc298eaab4bb29e2a9a`.
|
||||||
|
Checksum получен как SHA-256 отсортированного списка per-file SHA-256 и
|
||||||
|
относительных путей. Из scope исключены `node_modules` на любой глубине,
|
||||||
|
`.idea`, `.playwright-mcp`, cache-, build- и coverage-директории. Эталон
|
||||||
|
использовался только для сравнения эквивалентных обязанностей, не как authority.
|
||||||
|
|
||||||
|
Аудит standalone и read-only: OpenSpec/SDD workflow не запускался. Единственная
|
||||||
|
запись — этот отчёт вне `frontend/` и reference. В inventory вошли все 145
|
||||||
|
tracked-файлов target (117 под `src`, 22 test-файла) и 220 релевантных файлов
|
||||||
|
reference после исключения локальных зависимостей и ephemeral artifacts.
|
||||||
|
Углублённо проверены manifests/lockfile, TypeScript/Vite/Vitest/Biome/Knip/
|
||||||
|
dependency-cruiser/Husky configuration, repository instructions и frontend
|
||||||
|
docs, auth/session и API seams, routing/error boundary, representative finance
|
||||||
|
pages, entities, ViewModels, tests и эквивалентные reference responsibilities.
|
||||||
|
По всему `src` выполнены searches импортов, network calls, generated types,
|
||||||
|
tests и крупных modules; не каждый CSS-файл и не весь generated OpenAPI файл
|
||||||
|
читались построчно.
|
||||||
|
|
||||||
|
Выполненные проверки:
|
||||||
|
|
||||||
|
- `npm run deps` — exit 0; 136 modules и 484 dependency edges, нарушений нет;
|
||||||
|
- `npm run knip` — exit 0, findings отсутствуют;
|
||||||
|
- offline generation `openapi-typescript` во временный файл — текущий
|
||||||
|
`schema.d.ts` байтово совпадает со snapshot;
|
||||||
|
- controlled invalid-date probe под Node 22 —
|
||||||
|
`RangeError: Invalid time value`, подтверждающий F-03.
|
||||||
|
|
||||||
|
Полный `npm run verify`, `test:coverage`, build, Storybook и E2E не запускались:
|
||||||
|
они создают или могут изменять `coverage`, `dist`, reports и caches, что не
|
||||||
|
соответствует read-only контракту аудита. Также не выполнялись install,
|
||||||
|
dependency advisory scan, backend runtime contract test и browser-based visual/
|
||||||
|
accessibility checks. Поэтому выводы о текущем полном gate, supply-chain
|
||||||
|
уязвимостях, реальном backend и визуальном поведении не заявляются.
|
||||||
|
|
||||||
|
**Общая confidence:** high для статической архитектуры, tooling и найденных
|
||||||
|
расхождений; medium для production/release risk из-за локальной границы системы
|
||||||
|
и отсутствия runtime/browser evidence.
|
||||||
|
|
||||||
|
**Preservation check:** после всех inspection/validation commands target
|
||||||
|
остался на том же commit с пустым porcelain status; повторный reference checksum
|
||||||
|
совпал с исходным. Временный generated artifact удалён. Единственное
|
||||||
|
сохранённое изменение сессии — этот Markdown report.
|
||||||
|
|
||||||
|
## Executive Summary
|
||||||
|
|
||||||
|
Репозиторий в целом находится в хорошем состоянии: небольшой React/Vite client
|
||||||
|
имеет строгий TypeScript, один typed HTTP boundary, risk-oriented Vitest/MSW
|
||||||
|
tests, coverage floors, dead-code analysis и более сильные dependency rules,
|
||||||
|
чем reference. Критических findings нет.
|
||||||
|
|
||||||
|
Наибольшую ценность дадут F-01 и F-02: восстановить детерминированную проверку
|
||||||
|
generated OpenAPI artifact и закрыть реальный login journey существующим test
|
||||||
|
stack. F-03 — небольшой, но уже воспроизводимый trust-boundary defect в URL date
|
||||||
|
validation. F-04 уточняет существенное ограничение заявленного public-API gate:
|
||||||
|
он не видит type-only dependencies и уже пропускает семь внутренних imports.
|
||||||
|
Остальная архитектурная стоимость сосредоточена в двух ownership seams (F-05,
|
||||||
|
F-08) и в документации, которая местами описывает удалённые либо избыточно
|
||||||
|
жёсткие guardrails (F-06, F-07).
|
||||||
|
|
||||||
|
Эталон полезен как подтверждение ценности OpenAPI drift check, но его Storybook,
|
||||||
|
browser MSW, Playwright harness, локальный OpenSpec и 17 agent skills не должны
|
||||||
|
переноситься в target: текущая система локальна, а эти поверхности не решают
|
||||||
|
наблюдаемую проблему пропорционально своей стоимости.
|
||||||
|
|
||||||
|
## Priority Findings
|
||||||
|
|
||||||
|
| Severity | Finding | Category | Action | Effort |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| high | F-01. Generated OpenAPI types не защищены от drift | Tooling / API Contract | add | small |
|
||||||
|
| high | F-02. Основной auth journey не проверяется | Testing / Security | add | small |
|
||||||
|
| medium | F-03. Некорректная календарная дата бросает исключение | Correctness / Validation | change | small |
|
||||||
|
| medium | F-04. Type-only dependencies обходят public-API guardrail | Architecture / Tooling | change | small |
|
||||||
|
| medium | F-05. Ubiquitous Money formatting принадлежит Portfolio | Architecture / Ownership | change | medium |
|
||||||
|
| medium | F-06. Entry-point docs обещают отсутствующие проверки | AI Readiness / Documentation | change | small |
|
||||||
|
| low | F-07. File-splitting rule создаёт ложный hard invariant | Complexity / AI Readiness | simplify | small |
|
||||||
|
| low | F-08. T-Bank status обходит общий authenticated-query seam | Architecture / Maintainability | change | small |
|
||||||
|
|
||||||
|
## Findings
|
||||||
|
|
||||||
|
### F-01. Generated OpenAPI types не защищены от drift
|
||||||
|
|
||||||
|
**Category:** Tooling / API Contract
|
||||||
|
**Severity:** high
|
||||||
|
**Confidence:** high
|
||||||
|
**Action:** add
|
||||||
|
**Effort:** small
|
||||||
|
|
||||||
|
**Observed:** Frontend хранит и OpenAPI snapshot, и generated
|
||||||
|
`schema.d.ts`, но canonical `verify` не проверяет, что второй детерминированно
|
||||||
|
получен из первого. Workflow полагается на ручное выполнение
|
||||||
|
`npm run openapi:generate` после копирования snapshot.
|
||||||
|
|
||||||
|
**Evidence:** `frontend/package.json:17-20` содержит `openapi:generate`, но
|
||||||
|
`verify` состоит только из typecheck, Biome, coverage, Knip,
|
||||||
|
dependency-cruiser и build. `frontend/docs/API-LAYER.md:3-5` прямо описывает
|
||||||
|
ручные два шага. Backend `test:openapi-contract` сравнивает runtime contract с
|
||||||
|
frontend snapshot, но не snapshot с `frontend/src/shared/api/generated/schema.d.ts`.
|
||||||
|
Текущий controlled check с генерацией в `/private/tmp` показал
|
||||||
|
`OPENAPI_GENERATED_MATCH yes`, то есть finding относится к отсутствующему
|
||||||
|
guardrail, а не к текущему рассинхрону. TypeScript и build являются
|
||||||
|
counter-evidence только для внутренней согласованности кода со stale generated
|
||||||
|
типами; происхождение этих типов они доказать не могут.
|
||||||
|
|
||||||
|
**Why it matters:** Изменение snapshot без генерации может одновременно пройти
|
||||||
|
frontend `verify` и backend runtime-contract check, оставив приложение
|
||||||
|
скомпилированным против старого публичного контракта. Для auth и финансовых
|
||||||
|
response shapes это даёт silent contract risk в главном typed boundary.
|
||||||
|
|
||||||
|
**Reference repository:** **adopt from reference — capability only.** Reference
|
||||||
|
имеет `scripts/check-openapi.mjs`, `openapi:check` и включает его в `verify` и
|
||||||
|
pre-commit (`reference/package.json:13-28`,
|
||||||
|
`reference/scripts/check-openapi.mjs:1-32`). Это узкий guardrail с прямой
|
||||||
|
проблемой и без новой dependency. Остальной reference toolchain для него не
|
||||||
|
нужен.
|
||||||
|
|
||||||
|
**Recommended direction:** Добавить один non-mutating offline check: с помощью
|
||||||
|
уже установленного `openapi-typescript` генерировать temp artifact, сравнивать
|
||||||
|
его с committed `schema.d.ts` и включить сравнение в canonical `verify`.
|
||||||
|
Не синхронизировать backend и не переписывать tracked files внутри gate.
|
||||||
|
|
||||||
|
### F-02. Основной auth journey не проверяется
|
||||||
|
|
||||||
|
**Category:** Testing / Security
|
||||||
|
**Severity:** high
|
||||||
|
**Confidence:** high
|
||||||
|
**Action:** add
|
||||||
|
**Effort:** small
|
||||||
|
|
||||||
|
**Observed:** Test suite хорошо покрывает восстановление session, Bearer retry,
|
||||||
|
очистку cache, anonymous redirects и одну отрицательную registration validation,
|
||||||
|
но ни один тест не выполняет успешный login/register/logout через public UI/
|
||||||
|
provider boundary. В частности, untested остаётся связка `AuthForm` →
|
||||||
|
`AuthProvider.authenticate` → auth endpoint → in-memory access token → переход
|
||||||
|
на protected route.
|
||||||
|
|
||||||
|
**Evidence:** Поиск по всем 22 test-файлам нашёл auth assertions только в
|
||||||
|
`src/shared/api/client.test.ts`, `src/shared/api/authenticated-query.test.tsx`,
|
||||||
|
`src/features/auth/model/auth.test.tsx`, `src/features/auth/ui/AuthForm.test.tsx`
|
||||||
|
и `src/app/routes/AppRoutes.test.tsx`. `auth.test.tsx:11-102` проверяет три
|
||||||
|
refresh/401 сценария; `AuthForm.test.tsx:16-41` — только несовпадающее
|
||||||
|
подтверждение пароля; route test восстанавливает session через refresh.
|
||||||
|
Tests не вызывают `login`, `register` или `logout`, не отправляют валидную
|
||||||
|
форму и не проверяют `LoginForm`/`RegisterForm` navigation. Это проверено
|
||||||
|
repository-wide search, а не выводом из отсутствия отдельного filename.
|
||||||
|
|
||||||
|
**Why it matters:** Auth — security-sensitive и critical user journey по
|
||||||
|
действующей risk-based testing policy. Независимо покрытые низкоуровневые seams
|
||||||
|
не обнаружат поломку их композиции: неверный endpoint/body, потерянное session
|
||||||
|
state update, неработающий `returnTo`, navigation до завершения auth или
|
||||||
|
неочищенный UI после logout.
|
||||||
|
|
||||||
|
**Reference repository:** **reference approach is unnecessary.** Reference
|
||||||
|
имеет Playwright и browser MSW, но его E2E покрывает Todo demo, не auth. Перенос
|
||||||
|
всего browser harness ради этого риска добавит больше lifecycle cost, чем
|
||||||
|
существующий Vitest + Testing Library + MSW boundary.
|
||||||
|
|
||||||
|
**Recommended direction:** Добавить минимальный risk-focused integration test
|
||||||
|
на существующем stack, который вводит валидные credentials, проходит реальный
|
||||||
|
`AuthProvider`/MSW login и наблюдает protected landing/session outcome. Logout
|
||||||
|
или failure следует добавлять только там, где их риск не покрыт уже имеющимися
|
||||||
|
provider/client tests; Playwright для этого finding не требуется.
|
||||||
|
|
||||||
|
### F-03. Некорректная календарная дата бросает исключение
|
||||||
|
|
||||||
|
**Category:** Correctness / Validation
|
||||||
|
**Severity:** medium
|
||||||
|
**Confidence:** high
|
||||||
|
**Action:** change
|
||||||
|
**Effort:** small
|
||||||
|
|
||||||
|
**Observed:** `isDate` сначала проверяет только форму `YYYY-MM-DD`, затем без
|
||||||
|
проверки validity вызывает `toISOString()`. Строка вроде `2026-99-99` проходит
|
||||||
|
regex и приводит к `RangeError`, а не к validation result `false`.
|
||||||
|
|
||||||
|
**Evidence:** `src/pages/account-income/ui/AccountIncomePage.vm.ts:35-46` подаёт
|
||||||
|
`isDate` в Zod refinements; `:51-71` читает даты из untrusted URL search params;
|
||||||
|
`:114-132` использует `new Date(...).toISOString()` в `isDate` и затем снова в
|
||||||
|
`validPeriod`. Controlled Node 22 probe над этой же операцией вернул
|
||||||
|
`INVALID_DATE_THROW RangeError: Invalid time value`. Existing income tests
|
||||||
|
покрывают analytics, empty bucket, chart и recoverable server error, но не
|
||||||
|
malformed URL/form dates. Общий `RouteErrorBoundary` ограничивает blast radius,
|
||||||
|
но превращает validation error в аварийный route state.
|
||||||
|
|
||||||
|
**Why it matters:** Пользователь или внешняя ссылка может сломать income route
|
||||||
|
одним syntactically-shaped query parameter. Это доступность и корректность на
|
||||||
|
trust boundary; exception bypasses предусмотренное восстановление default
|
||||||
|
period и field-level validation.
|
||||||
|
|
||||||
|
**Reference repository:** **neither reflects a directly comparable case.**
|
||||||
|
Reference валидирует numeric route params через Zod `safeParse`, но не содержит
|
||||||
|
эквивалентной календарной логики, поэтому переносить его hook не нужно.
|
||||||
|
|
||||||
|
**Recommended direction:** Сделать date predicate total: проверить parsed date
|
||||||
|
до `toISOString` либо валидировать год/месяц/день безопасным стандартным
|
||||||
|
преобразованием, затем зафиксировать malformed query/form date регрессионным
|
||||||
|
тестом. Новая date library не нужна.
|
||||||
|
|
||||||
|
### F-04. Type-only dependencies обходят public-API guardrail
|
||||||
|
|
||||||
|
**Category:** Architecture / Tooling / AI Readiness
|
||||||
|
**Severity:** medium
|
||||||
|
**Confidence:** high
|
||||||
|
**Action:** change
|
||||||
|
**Effort:** small
|
||||||
|
|
||||||
|
**Observed:** Dependency-cruiser проверяет cycles, layer direction и public
|
||||||
|
entrypoints только для dependencies, существующих после TypeScript compilation.
|
||||||
|
Type-only edges не входят в graph, хотя repository docs формулируют public-API
|
||||||
|
правило без такого исключения. В результате семь consumers импортируют
|
||||||
|
внутренний generated module `@/shared/api/generated/schema` напрямую.
|
||||||
|
|
||||||
|
**Evidence:** В `frontend/dependency-cruiser.cjs:45-48` задан `tsConfig`, но нет
|
||||||
|
`tsPreCompilationDeps`; установленный dependency-cruiser 18.2.0 устанавливает
|
||||||
|
его default в `false` (`node_modules/dependency-cruiser/src/main/options/defaults.mjs:9`).
|
||||||
|
`frontend/docs/CONVENTIONS.md:9` и `frontend/docs/STRUCTURE.md:11-13` требуют
|
||||||
|
cross-slice imports через `index.ts`, а `frontend/AGENTS.md:34` называет это
|
||||||
|
dependency-cruiser-enforced boundary. При этом прямые type-only imports
|
||||||
|
`@/shared/api/generated/schema` находятся в пяти entity API hooks,
|
||||||
|
`features/auth/model/auth.tsx` и
|
||||||
|
`features/tbank-connection/ui/TbankConnectionSection.tsx`;
|
||||||
|
`frontend/src/shared/api/index.ts` не реэкспортирует `paths`. Успешный
|
||||||
|
`npm run deps` не является counter-evidence: эти edges отсутствуют в
|
||||||
|
анализируемом graph. Counter-evidence по масштабу риска — остальные найденные
|
||||||
|
type-only imports идут через public APIs в разрешённом направлении, а текущие
|
||||||
|
семь generated imports не создают runtime coupling.
|
||||||
|
|
||||||
|
**Why it matters:** Заявленный machine-enforced invariant фактически не
|
||||||
|
распространяется на compile-time architecture. Новый type-only upward import,
|
||||||
|
type cycle или обращение к внутренностям другого slice сможет пройти canonical
|
||||||
|
gate; существующий generated seam уже демонстрирует расхождение code и policy.
|
||||||
|
Severity остаётся medium, потому что текущие edges стираются при compilation и
|
||||||
|
не создают runtime cycle или bundle dependency.
|
||||||
|
|
||||||
|
**Reference repository:** **adopt from reference — capability, not exception.**
|
||||||
|
Reference включает `tsPreCompilationDeps: true`, но исключает generated subtree
|
||||||
|
из dependency analysis. Target может сохранить более строгий и ясный public API,
|
||||||
|
не копируя это исключение.
|
||||||
|
|
||||||
|
**Recommended direction:** Реэкспортировать generated `paths` как type из
|
||||||
|
`shared/api/index.ts`, перевести семь consumers на `@/shared/api` и включить
|
||||||
|
`tsPreCompilationDeps: true` в существующем dependency-cruiser config. Если
|
||||||
|
generated path действительно должен быть публичным исключением, сначала
|
||||||
|
зафиксировать его явно и всё равно проверять остальные type-only edges. Новая
|
||||||
|
dependency или второй architecture checker не нужны.
|
||||||
|
|
||||||
|
### F-05. Ubiquitous Money formatting принадлежит Portfolio
|
||||||
|
|
||||||
|
**Category:** Architecture / Ownership
|
||||||
|
**Severity:** medium
|
||||||
|
**Confidence:** high
|
||||||
|
**Action:** change
|
||||||
|
**Effort:** medium
|
||||||
|
|
||||||
|
**Observed:** Общий display formatter `formatMoney` типизирован через
|
||||||
|
`Portfolio["totalsByCurrency"][number]` и экспортируется из Portfolio entity,
|
||||||
|
хотя используется для Brokerage Account, Ledger Entry, Income Analytics и
|
||||||
|
Future Cash Event. `formatSnapshotTime` и instrument logo helper также частично
|
||||||
|
разделяются через тот же Portfolio public API.
|
||||||
|
|
||||||
|
**Evidence:** Canonical domain glossary определяет Money как самостоятельный
|
||||||
|
ubiquitous value `{ currency, value }`, который frontend форматирует для
|
||||||
|
отображения (`docs/architecture/domain-glossary.md:20-28`). Реализация в
|
||||||
|
`src/entities/portfolio/model/display.ts:1-18` выводит Money type из Portfolio.
|
||||||
|
Repository-wide usage показывает импорт Portfolio formatter из
|
||||||
|
`src/entities/brokerage-account/ui/BrokerageAccountList.tsx`,
|
||||||
|
`src/entities/broker-operation/ui/LedgerEntryRow.tsx`, account/events/income
|
||||||
|
pages и их section views. `npm run deps` проходит, потому что текущие rules
|
||||||
|
контролируют direction/public entrypoints, но не смыслового владельца peer
|
||||||
|
entities. Counter-evidence: formatter один, протестирован и не дублируется;
|
||||||
|
проблема именно в owner/type dependency, а не в реализации форматирования.
|
||||||
|
|
||||||
|
**Why it matters:** Изменение Portfolio transport shape или реорганизация
|
||||||
|
Portfolio заставляет менять несвязанные account, ledger и income consumers.
|
||||||
|
Граница утверждает, что общий Money concern принадлежит одной product entity,
|
||||||
|
и легализует entity-to-entity coupling, которое dependency tool не различает.
|
||||||
|
|
||||||
|
**Reference repository:** **reference approach is unnecessary.** Todo-only
|
||||||
|
reference не моделирует shared Money value и не предлагает применимого owner.
|
||||||
|
|
||||||
|
**Recommended direction:** Переместить только действительно ubiquitous
|
||||||
|
display primitives (в первую очередь Money formatting и, при подтверждении
|
||||||
|
общего смысла, date formatting) в нейтральный существующий lower-level owner.
|
||||||
|
Оставить `formatShare` и portfolio error behavior в Portfolio. Не вводить Money
|
||||||
|
service, interface hierarchy или новую domain layer ради одного formatter.
|
||||||
|
|
||||||
|
### F-06. Entry-point docs обещают отсутствующие проверки
|
||||||
|
|
||||||
|
**Category:** AI Readiness / Documentation
|
||||||
|
**Severity:** medium
|
||||||
|
**Confidence:** high
|
||||||
|
**Action:** change
|
||||||
|
**Effort:** small
|
||||||
|
|
||||||
|
**Observed:** Человеческий entrypoint сообщает, что `verify` отклоняет неверный
|
||||||
|
Node runtime и проверяет repository hygiene, а conventions утверждают, что
|
||||||
|
pre-commit запускает OpenAPI check. Ни одной из этих команд в текущих scripts/
|
||||||
|
hook нет.
|
||||||
|
|
||||||
|
**Evidence:** `frontend/README.md:25-27` перечисляет runtime и hygiene;
|
||||||
|
`frontend/docs/CONVENTIONS.md:14` перечисляет pre-commit OpenAPI check.
|
||||||
|
Фактически `frontend/package.json:9-22` не содержит `runtime:check`,
|
||||||
|
`hygiene:check` или `openapi:check`, а `.husky/pre-commit` выполняет только
|
||||||
|
`npx lint-staged` и `npm run deps`. Counter-evidence: `.nvmrc`,
|
||||||
|
`engines.node = 22.x` и repository-local `AGENTS.md` корректно требуют
|
||||||
|
`nvm use --silent`; canonical config остаётся discoverable. Расхождение
|
||||||
|
ограничено двумя stale prose claims, а не всей документацией.
|
||||||
|
|
||||||
|
**Why it matters:** Новый разработчик или coding agent получает ложное
|
||||||
|
completion evidence и может считать runtime, env hygiene или generated artifact
|
||||||
|
проверенными. Особенно опасно сочетание с F-01: docs называют guardrail, которого
|
||||||
|
больше нет.
|
||||||
|
|
||||||
|
**Reference repository:** **both are acceptable only where config and prose
|
||||||
|
agree.** Reference действительно включает свой `openapi:check`, но его намного
|
||||||
|
больший documentation surface не нужен target. Target уже лучше как concise,
|
||||||
|
canonical entrypoint; ему требуется синхронизация нескольких утверждений, а не
|
||||||
|
перенос reference docs.
|
||||||
|
|
||||||
|
**Recommended direction:** Привести README и CONVENTIONS к фактическим scripts:
|
||||||
|
описать manual Node activation и реальные hook/gate stages. Если F-01 добавлен,
|
||||||
|
упомянуть только реально включённый OpenAPI check. Не возвращать отдельные
|
||||||
|
runtime/hygiene scripts без нового конкретного failure mode.
|
||||||
|
|
||||||
|
### F-07. File-splitting rule создаёт ложный hard invariant
|
||||||
|
|
||||||
|
**Category:** Complexity / AI Readiness
|
||||||
|
**Severity:** low
|
||||||
|
**Confidence:** high
|
||||||
|
**Action:** simplify
|
||||||
|
**Effort:** small
|
||||||
|
|
||||||
|
**Observed:** UI docs объявляют обязательным отдельный file для каждого
|
||||||
|
top-level state и каждого именуемого page section, но несколько текущих modules
|
||||||
|
сознательно хранят cohesive presentation рядом. Самый заметный пример —
|
||||||
|
388-line `AccountIncomePage.view.tsx` с loading/error/loaded и шестью semantic
|
||||||
|
section components.
|
||||||
|
|
||||||
|
**Evidence:** `frontend/docs/CONVENTIONS.md:12-13` и
|
||||||
|
`frontend/docs/UI-PATTERN.md:30-35` формулируют правило как Must/«нельзя».
|
||||||
|
`src/pages/account-income/ui/AccountIncomePage.view.tsx:29-380` содержит
|
||||||
|
`AccountIncomePageLoading`, `AccountIncomePageError`, loaded view, summary,
|
||||||
|
comparisons, trend, breakdown и forecast. Подобная colocated state structure
|
||||||
|
есть в `AccountsPage.view.tsx` и небольшом T-Bank view. Counter-evidence:
|
||||||
|
важная граница container → VM → props-only view в проверенных finance pages
|
||||||
|
соблюдается; большой income view читается последовательно и покрыт одним
|
||||||
|
behavior-level page test. Поэтому само количество components/files не является
|
||||||
|
failure mode.
|
||||||
|
|
||||||
|
**Why it matters:** Непроверяемое и уже нарушенное Must заставляет agents либо
|
||||||
|
игнорировать authority, либо создавать file/ barrel sprawl без улучшения
|
||||||
|
ownership. Оно также маскирует действительно важный invariant — views не должны
|
||||||
|
владеть data fetching и collection derivation.
|
||||||
|
|
||||||
|
**Reference repository:** **reference approach is unnecessary.** Reference
|
||||||
|
часто создаёт отдельную директорию и file для каждого Todo state; это полезно
|
||||||
|
для component catalog demo, но увеличивает navigation cost для текущего product
|
||||||
|
client и не должно становиться механической нормой.
|
||||||
|
|
||||||
|
**Recommended direction:** Оставить VM/view ownership rule обязательным, а
|
||||||
|
file splitting сделать locality-based guidance: выделять section, когда у неё
|
||||||
|
есть самостоятельная responsibility, reuse, test/change cadence или она мешает
|
||||||
|
пониманию owner. Не добавлять line-count checker и не требовать one-component-
|
||||||
|
per-file.
|
||||||
|
|
||||||
|
### F-08. T-Bank status обходит общий authenticated-query seam
|
||||||
|
|
||||||
|
**Category:** Architecture / Maintainability
|
||||||
|
**Severity:** low
|
||||||
|
**Confidence:** high
|
||||||
|
**Action:** change
|
||||||
|
**Effort:** small
|
||||||
|
|
||||||
|
**Observed:** Все entity queries используют shared `useAuthenticatedQuery`,
|
||||||
|
который подписывается на session snapshot. T-Bank connection status вместо
|
||||||
|
этого импортирует `useAuth` из соседней feature и вручную задаёт
|
||||||
|
`enabled: Boolean(accessToken)` обычному `useQuery`.
|
||||||
|
|
||||||
|
**Evidence:** `src/shared/api/authenticated-query.ts:13-33` владеет session
|
||||||
|
gating; его используют brokerage account, operation, event, income и portfolio
|
||||||
|
hooks. `src/features/tbank-connection/ui/TbankConnectionSection.vm.ts:1-15`
|
||||||
|
создаёт второй механизм и feature → feature dependency на `features/auth`.
|
||||||
|
`dependency-cruiser.cjs` не запрещает peer-feature dependencies, поэтому
|
||||||
|
успешный `npm run deps` не является counterexample. Counter-evidence: текущее
|
||||||
|
поведение функционально совпадает, access token обновляется через AuthProvider,
|
||||||
|
а mutations всё равно идут через общий authenticated fetch.
|
||||||
|
|
||||||
|
**Why it matters:** Изменение определения authenticated readiness,
|
||||||
|
`configureAuthClient` или session clearing может примениться ко всем entity
|
||||||
|
queries, но не к T-Bank status. Соседняя feature становится зависимой от auth
|
||||||
|
implementation detail при уже существующей lower-level seam.
|
||||||
|
|
||||||
|
**Reference repository:** **not comparable.** Reference не имеет auth/session
|
||||||
|
boundary.
|
||||||
|
|
||||||
|
**Recommended direction:** Переиспользовать existing authenticated-query seam
|
||||||
|
для status query и убрать feature-to-feature dependency. Новая abstraction или
|
||||||
|
расширение dependency rules ради единственного случая не нужны.
|
||||||
|
|
||||||
|
## Missing Practices
|
||||||
|
|
||||||
|
- F-01 — отсутствует доказательство происхождения committed generated API
|
||||||
|
types из committed snapshot.
|
||||||
|
- F-02 — отсутствует risk-focused integration proof главного auth journey.
|
||||||
|
- F-03 — trust-boundary date validation не total и не имеет malformed-input
|
||||||
|
regression coverage.
|
||||||
|
- F-04 — type-only dependencies не входят в заявленный public-API/layer gate;
|
||||||
|
уже существуют семь direct generated-internal imports.
|
||||||
|
|
||||||
|
Отсутствие CI/CD и deployment automation не считается finding: canonical
|
||||||
|
`docs/architecture/system-overview.md` явно ограничивает текущую поставку
|
||||||
|
локальной системой, а `frontend/AGENTS.md` требует отдельного approved change
|
||||||
|
для CI, Playwright или Storybook.
|
||||||
|
|
||||||
|
## Over-engineering / Simplification Opportunities
|
||||||
|
|
||||||
|
- F-07 — сузить file-splitting Must до правила ownership/locality, сохранив
|
||||||
|
важную VM/view границу.
|
||||||
|
- Не переносить из reference Storybook, browser MSW mode, Playwright harness,
|
||||||
|
локальный `openspec/` и 17 repository-provided skills: для target нет
|
||||||
|
наблюдаемой проблемы, оправдывающей этот context и maintenance surface.
|
||||||
|
- F-05 следует исправлять переносом маленьких shared primitives, не созданием
|
||||||
|
Money service/interface/factory.
|
||||||
|
- Не добавлять второй FSD framework или checker: текущий dependency-cruiser уже
|
||||||
|
проверяет cycles, public entrypoints и layer direction лучше reference.
|
||||||
|
|
||||||
|
## AI Readiness
|
||||||
|
|
||||||
|
AI readiness выше средней: `AGENTS.md` короткий, различает hard guardrails и
|
||||||
|
review guidance, направляет к специализированным docs и canonical multi-repo
|
||||||
|
authority; scripts и public APIs предсказуемы; generated seam явно помечена;
|
||||||
|
dependency-cruiser и Knip снижают необходимость загружать длинные инструкции.
|
||||||
|
Главные ухудшения — F-04 (type-only blind spot), F-06 (несовпадение prose с
|
||||||
|
executable truth) и F-07 (непроверяемый structural Must). Reference agent
|
||||||
|
surface значительно больше и для target был бы регрессом context locality.
|
||||||
|
|
||||||
|
## What Is Already Good
|
||||||
|
|
||||||
|
- Строгая и воспроизводимая основа: Node 22 contract, npm lockfile, exact direct
|
||||||
|
dependency versions, strict TypeScript и Biome.
|
||||||
|
- `npm run deps` подтверждает отсутствие cycles/layer/public-API violations в
|
||||||
|
анализируемых runtime edges; target имеет отдельные cycle/public-API rules,
|
||||||
|
которых нет в reference, но F-04 ограничивает полноту этого доказательства.
|
||||||
|
- Один HTTP owner: repository-wide search не нашёл прямой network access вне
|
||||||
|
`src/shared/api/client.ts`; transport types выводятся из OpenAPI.
|
||||||
|
- Auth design соответствует system/domain authority: access token остаётся в
|
||||||
|
memory, refresh использует cookie и single-flight behavior; Bearer exclusion,
|
||||||
|
refresh retry и cache clearing имеют focused tests.
|
||||||
|
- 22 colocated test-файла покрывают loading/error/empty/cached snapshot,
|
||||||
|
finance display, filtering/pagination, route boundary и T-Bank mutations.
|
||||||
|
- Coverage floors, Knip, dependency checks и production build собраны в одном
|
||||||
|
canonical local gate; controlled Knip/dependency runs успешны.
|
||||||
|
- UI resilience и доступные loading/error regions не только описаны, но и
|
||||||
|
представлены в code/tests; общий route error boundary ограничивает failures.
|
||||||
|
- Текущий OpenAPI snapshot и generated types фактически синхронны; F-01 —
|
||||||
|
prevention gap, не текущая поломка.
|
||||||
|
|
||||||
|
## Recommended Order
|
||||||
|
|
||||||
|
### Do now
|
||||||
|
|
||||||
|
1. F-01 — добавить non-mutating OpenAPI generated-artifact equivalence в
|
||||||
|
canonical gate.
|
||||||
|
2. F-02 — закрыть успешный auth journey существующим Vitest/MSW stack.
|
||||||
|
3. F-03 — сделать date validation total и закрепить malformed-input case.
|
||||||
|
4. F-04 — включить type-only edges и провести generated types через public API.
|
||||||
|
5. F-06 — синхронизировать README/CONVENTIONS с фактическими scripts и hooks.
|
||||||
|
|
||||||
|
### Do next
|
||||||
|
|
||||||
|
1. F-05 — вернуть ubiquitous Money display primitive нейтральному owner без
|
||||||
|
нового service layer.
|
||||||
|
|
||||||
|
### Consider later
|
||||||
|
|
||||||
|
1. F-08 — устранить единичный bypass authenticated-query seam при ближайшем
|
||||||
|
изменении T-Bank connection.
|
||||||
|
2. F-07 — упростить file-splitting guidance до решения по locality/ownership.
|
||||||
|
|
||||||
|
### Do not add
|
||||||
|
|
||||||
|
- Storybook, browser MSW mode или Playwright только потому, что они есть в
|
||||||
|
reference; F-02 дешевле и надёжнее закрывается текущим integration stack.
|
||||||
|
- Локальный OpenSpec store или копии reference agent skills: canonical SDD
|
||||||
|
authority уже находится вне target и entrypoint достаточно короткий.
|
||||||
|
- CI/CD, deployment или release platform вне отдельного approved scope:
|
||||||
|
as-built system явно локальна.
|
||||||
|
- Runtime validation каждого OpenAPI response без конкретного наблюдаемого
|
||||||
|
trust-boundary failure; это существенно расширит runtime и schema ownership.
|
||||||
|
- Новый FSD framework, line-count gate, one-component-per-file rule, Money
|
||||||
|
service/interface или dependency ради findings, которые решаются текущими
|
||||||
|
tools и небольшими ownership corrections.
|
||||||
+782
@@ -0,0 +1,782 @@
|
|||||||
|
# Frontend Structural Maintainability Audit
|
||||||
|
|
||||||
|
**Дата:** 2026-08-31
|
||||||
|
**Session ID:** `01a055ce-cefc-75c3-860f-6ca0ec8c881c`
|
||||||
|
**Invoked skills:** `repository-audit`
|
||||||
|
**Supporting skills invoked:** `graphify` — выполнен только preflight; готового
|
||||||
|
`graphify-out/graph.json` не было, а обязательный build создаёт cache и
|
||||||
|
visualization artifacts, поэтому он был остановлен как несовместимый с
|
||||||
|
read-only scope.
|
||||||
|
**External research performed:** no
|
||||||
|
|
||||||
|
## Scope and Confidence
|
||||||
|
|
||||||
|
**Target:** `frontend/`, branch `main`, commit
|
||||||
|
`7ab043dce63a3fbacbfc9ac7d15b00dd714a541f` (`docs: remove useless`).
|
||||||
|
Исходный `git status --short --untracked-files=all` был пуст. В inventory вошли
|
||||||
|
все 145 tracked files, включая 118 файлов под `src`, 42 non-test TSX files,
|
||||||
|
22 test files, пять CSS files и 23 `index.ts` barrels.
|
||||||
|
|
||||||
|
**Reference:** `references/frontend-ai-template/`. Директория не является
|
||||||
|
Git-репозиторием, поэтому commit identity отсутствует. Для этого аудита её
|
||||||
|
identity зафиксирована SHA-256 checksum
|
||||||
|
`72834908a7effb3d57d98729a6def3996383199e3c63596707f805a6cdc64e4e`
|
||||||
|
по 220 отсортированным per-file SHA-256 records; исключены `node_modules`,
|
||||||
|
`.idea`, `.playwright-mcp`, cache, coverage, build и dist directories.
|
||||||
|
|
||||||
|
**Existing context:** `frontend-repository-audit.md` прочитан полностью только
|
||||||
|
для устранения повторов и противоречий. Его общие findings об OpenAPI drift
|
||||||
|
gate, auth journey, malformed income date, type-only dependency checks и docs
|
||||||
|
не повторяются. Этот отчёт уточняет general F-05 и F-08 и пересматривает F-07
|
||||||
|
с учётом явно заданной component-file preference.
|
||||||
|
|
||||||
|
Глубоко проверены все production TS/TSX/CSS paths, public barrels, React Query
|
||||||
|
hooks и cache mutations, form/schema setup, formatting/parsing/error helpers,
|
||||||
|
все tests и `src/test/**`, Vitest/Vite/package configuration, relevant installed
|
||||||
|
library behavior, а также эквивалентные responsibilities reference. Для
|
||||||
|
income identity и `connectedAt` contract прочитаны узкие backend owner paths,
|
||||||
|
поскольку frontend иначе не может установить semantic owner.
|
||||||
|
|
||||||
|
Проверки были статическими и read-only. `npm run verify`, tests, coverage,
|
||||||
|
build, lint, Knip, dependency-cruiser, code generation и browser checks не
|
||||||
|
запускались: они не меняли бы структурные выводы, а часть из них создаёт или
|
||||||
|
может создавать `coverage`, `dist`, caches или generated output. Install,
|
||||||
|
autofix, snapshot update, OpenSpec/SDD commands и external research не
|
||||||
|
выполнялись. Поэтому confidence high для ownership, duplication и static test
|
||||||
|
isolation evidence; runtime/visual consequences имеют medium confidence там,
|
||||||
|
где явно отмечены ограничения.
|
||||||
|
|
||||||
|
**Final preservation check:** target остался на том же commit с пустым
|
||||||
|
`git status --short --untracked-files=all`; final reference checksum совпал с
|
||||||
|
initial. Единственная запись сессии — этот Markdown report вне target и
|
||||||
|
reference.
|
||||||
|
|
||||||
|
## Executive Summary
|
||||||
|
|
||||||
|
Критических findings нет. Архитектурная основа frontend в целом здорова:
|
||||||
|
typed API boundary, FSD direction, page VM pattern, scoped CSS Modules и
|
||||||
|
risk-oriented MSW tests уже дают хорошие владельческие seams.
|
||||||
|
|
||||||
|
Главный новый риск — F-01: смена или удаление T-Bank connection не удаляет
|
||||||
|
кэшированные данные прежнего connection. Это data-ownership issue, а не
|
||||||
|
косметическая консолидация. Следом идут test isolation (F-02), дублирование
|
||||||
|
backend instrument identity внутри income view (F-03) и семь component files,
|
||||||
|
где самостоятельные semantic owners действительно скрывают основной component
|
||||||
|
(F-04). F-04 частично отменяет вывод general audit F-07, но не превращает
|
||||||
|
предпочтение в механическое one-component-per-file правило.
|
||||||
|
|
||||||
|
Остальные structural costs сосредоточены в server-state policy/key ownership
|
||||||
|
(F-05), test provider harnesses (F-06), cross-slice display/API primitives
|
||||||
|
(F-07), global styling (F-08) и одном скрытом OpenAPI type defect (F-09).
|
||||||
|
Новых библиотек не требуется: platform APIs и уже имеющиеся TanStack Query,
|
||||||
|
Mantine, Zod/RHF и CSS Modules покрывают подходящие remedies.
|
||||||
|
|
||||||
|
## Priority Findings
|
||||||
|
|
||||||
|
| Severity | Finding | Category | Action | Effort |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| high | F-01. T-Bank connection lifecycle не удаляет прежний financial cache | Data Ownership / Cache | change | medium |
|
||||||
|
| medium | F-02. Test teardown разделён между setup и отдельными tests | Test Isolation | change | small |
|
||||||
|
| medium | F-03. Income view реконструирует server identity и collection model | Architecture / Correctness | change | medium |
|
||||||
|
| medium | F-04. Семь TSX modules скрывают основной component | Component Boundaries | change | medium |
|
||||||
|
| medium | F-05. Server-state defaults и query identity имеют разных случайных владельцев | Query Policy / Cache Keys | change / simplify | small |
|
||||||
|
| medium | F-06. Test provider и QueryClient harnesses систематически повторяются | Test Infrastructure | simplify | medium |
|
||||||
|
| medium | F-07. Cross-slice display и API primitives имеют misleading owners | Semantic Ownership | change | medium |
|
||||||
|
| medium | F-08. Global CSS владеет component и Mantine-internal styling | Styling | change / simplify | small–medium |
|
||||||
|
| medium | F-09. `connectedAt` contract скрыт через `String(...)` coercion | API Ownership / Correctness | change | small |
|
||||||
|
| low | F-10. Два presentation files владеют validation/payment invariants | Helper Ownership | change | small |
|
||||||
|
| low | F-11. Redundant position modes и test-facing proxy exports не несут invariant | Complexity | simplify | small |
|
||||||
|
|
||||||
|
## Findings
|
||||||
|
|
||||||
|
### F-01. T-Bank connection lifecycle не удаляет прежний financial cache
|
||||||
|
|
||||||
|
**Category:** Data Ownership / Cache Invalidation
|
||||||
|
**Severity:** high
|
||||||
|
**Confidence:** high
|
||||||
|
**Action:** change
|
||||||
|
**Effort:** medium
|
||||||
|
|
||||||
|
**Observed:** Успешные `PUT` и `DELETE` T-Bank connection обновляют status cache
|
||||||
|
и инвалидируют только Brokerage Accounts list. Они не удаляют уже
|
||||||
|
закэшированные данные, полученные через прежний connection.
|
||||||
|
|
||||||
|
**Evidence:** Оба mutation success path находятся в
|
||||||
|
`frontend/src/features/tbank-connection/ui/TbankConnectionSection.tsx:60-82`;
|
||||||
|
единственные dependent operations — `setQueryData` status и два вызова
|
||||||
|
`invalidateQueries({ queryKey: brokerageAccountsQueryKey })` на строках 62–63
|
||||||
|
и 73–77. Repository-wide search всех production `setQueryData`,
|
||||||
|
`invalidateQueries`, `removeQueries` и `clear` не нашёл connection cleanup.
|
||||||
|
|
||||||
|
Под connection зависят шесть query families: accounts, portfolio, operations,
|
||||||
|
events, income summary и income analytics
|
||||||
|
(`useBrokerageAccounts.ts:4-17`, `usePortfolio.ts:12-21`,
|
||||||
|
`useBrokerageOperations.ts:35-49`, `useBrokerageEvents.ts:9-19`,
|
||||||
|
`useIncome.ts:9-41`). Из них инвалидируется одна, но invalidation сохраняет её
|
||||||
|
последние data. Остальные пять не инвалидируются и не удаляются.
|
||||||
|
|
||||||
|
Scope search включал все query definitions, mutations и relevant T-Bank,
|
||||||
|
accounts и portfolio tests. Counter-evidence: logout/session loss корректно
|
||||||
|
вызывает `queryClient.clear()` в `features/auth/model/auth.tsx:57-62`, но
|
||||||
|
connection replacement/deletion не завершает Session. Backend cleanup не
|
||||||
|
может удалить browser cache.
|
||||||
|
|
||||||
|
**Semantic owner:** T-Bank connection lifecycle. Именно он меняет trust/data
|
||||||
|
boundary; отдельные entities не должны знать, когда credential был заменён.
|
||||||
|
|
||||||
|
**Why it matters:** Cached Snapshot намеренно остаётся видимым после failed
|
||||||
|
background refresh (`AccountsPage.test.tsx:46-133`,
|
||||||
|
`AccountPage.test.tsx:519-551`). После удаления connection это позволяет
|
||||||
|
продолжать показывать прежние accounts/portfolio вместе с refresh error; после
|
||||||
|
замены token данные прежнего connection могут пережить смену источника.
|
||||||
|
Canonical glossary определяет Brokerage Accounts как доступные только через
|
||||||
|
текущий T-Bank connection (`docs/architecture/domain-glossary.md:37-42`).
|
||||||
|
|
||||||
|
**Reference repository:** **not comparable.** Reference не имеет credential-
|
||||||
|
bound server-state lifecycle.
|
||||||
|
|
||||||
|
**Recommended direction:** На успешной замене и удалении выполнить одну явную
|
||||||
|
connection-boundary eviction всех T-Bank-derived queries, затем установить
|
||||||
|
новый status. Использовать текущий QueryClient/query-key shape; не создавать
|
||||||
|
entity registry или cache service без необходимости. Сфокусированное proof
|
||||||
|
должно seed accounts и один account-scoped cache и наблюдать их удаление.
|
||||||
|
|
||||||
|
### F-02. Test teardown разделён между setup и отдельными tests
|
||||||
|
|
||||||
|
**Category:** Test Infrastructure / Isolation
|
||||||
|
**Severity:** medium
|
||||||
|
**Confidence:** high
|
||||||
|
**Action:** change
|
||||||
|
**Effort:** small
|
||||||
|
|
||||||
|
**Observed:** RTL auto-cleanup фактически не активен. Из 20 React Testing
|
||||||
|
Library test files десять вызывают `cleanup` явно, а десять оставляют mounted
|
||||||
|
roots до file teardown. `configureAuthClient(null)` отдельно повторяется в
|
||||||
|
11 files.
|
||||||
|
|
||||||
|
**Evidence:** `frontend/vitest.config.ts:8-11` не включает globals; installed
|
||||||
|
Vitest default — `globals: false`
|
||||||
|
(`node_modules/vitest/dist/chunks/defaults.9aQKnqFk.js:45-50`). Installed RTL
|
||||||
|
регистрирует automatic cleanup только когда global `afterEach` существует
|
||||||
|
(`node_modules/@testing-library/react/dist/index.js:18-29`). Общий
|
||||||
|
`src/test/setup.ts:6-8` сейчас владеет только MSW lifecycle.
|
||||||
|
|
||||||
|
Без explicit cleanup остаются десять файлов: четыре entity query-hook tests,
|
||||||
|
`BrokerageAccountList.test.tsx`, `auth.test.tsx`, T-Bank state-view test,
|
||||||
|
Account Events page test, Account Page VM test и
|
||||||
|
`authenticated-query.test.tsx`. `AuthProvider` снимает module-global auth
|
||||||
|
client только при React cleanup (`features/auth/model/auth.tsx:116-123`), а
|
||||||
|
authenticated queries удаляют listeners при unmount
|
||||||
|
(`shared/api/client.ts:14-35`, `authenticated-query.ts:21-32`).
|
||||||
|
|
||||||
|
Scope — все 22 tests и test setup. Counter-evidence: Vitest file isolation
|
||||||
|
остаётся включён, MSW handlers уже reset глобально, а десять files выполняют
|
||||||
|
cleanup сами; следовательно risk ограничен главным образом соседними cases
|
||||||
|
одного file, но owner всё равно непоследователен.
|
||||||
|
|
||||||
|
**Semantic owner:** `src/test/setup.ts`.
|
||||||
|
|
||||||
|
**Why it matters:** DOM roots, query observers и auth-session listeners могут
|
||||||
|
дожить до следующего case. Следующий `configureAuthClient(...)` уведомляет
|
||||||
|
старые subscribers и способен разбудить прежний QueryClient уже против новых
|
||||||
|
handlers, создавая order dependence и лишнее удержание ресурсов.
|
||||||
|
|
||||||
|
**Reference repository:** **adopt outcome, not mechanism.** Reference включает
|
||||||
|
Vitest globals и потому получает RTL auto-cleanup. Target может сохранить
|
||||||
|
явные Vitest imports.
|
||||||
|
|
||||||
|
**Recommended direction:** Добавить общий `cleanup()` и auth-client reset в
|
||||||
|
существующий setup-level `afterEach` рядом с `server.resetHandlers()`, затем
|
||||||
|
удалить per-file copies. Не включать globals только ради cleanup.
|
||||||
|
|
||||||
|
### F-03. Income view реконструирует server identity и collection model
|
||||||
|
|
||||||
|
**Category:** Architecture / Correctness
|
||||||
|
**Severity:** medium
|
||||||
|
**Confidence:** high
|
||||||
|
**Action:** change
|
||||||
|
**Effort:** medium
|
||||||
|
|
||||||
|
**Observed:** `AccountIncomePage.view.tsx` не только рендерит props: он строит
|
||||||
|
currency buckets и chart series, группирует entries по instrument и повторяет
|
||||||
|
backend algorithm формирования instrument identity.
|
||||||
|
|
||||||
|
**Evidence:** Trend derivation занимает
|
||||||
|
`frontend/src/pages/account-income/ui/AccountIncomePage.view.tsx:179-204`;
|
||||||
|
instrument panel фильтрует полный `entries` для каждого instrument на строках
|
||||||
|
268–290; `entryKey` на строках 354–363 повторяет priority
|
||||||
|
`instrumentUid → figi → ticker/type → operation id` и добавляет currency.
|
||||||
|
Backend применяет тот же algorithm в
|
||||||
|
`backend/src/tbank/income-analytics.service.ts:187-225`, тогда как public DTO
|
||||||
|
отдаёт `instruments` и `entries` раздельно без явной membership
|
||||||
|
(`income-analytics.dto.ts:86-135`).
|
||||||
|
|
||||||
|
Подтверждены два collection-transformation clusters и одна cross-layer identity
|
||||||
|
formula. Scope search охватил все target grouping/mapping/filtering paths и
|
||||||
|
income tests. Counter-evidence: формулы сейчас совпадают; tests покрывают один
|
||||||
|
FIGI match, empty bucket и signed chart segments. Selected currency и чистая
|
||||||
|
chart geometry являются UI state и могут оставаться в trend component.
|
||||||
|
|
||||||
|
**Semantic owner:** Точное instrument-entry membership принадлежит backend/API
|
||||||
|
contract; response-to-view grouping — Income page VM/model; view владеет только
|
||||||
|
выбором currency и rendering geometry.
|
||||||
|
|
||||||
|
**Why it matters:** Изменение backend identity priority может молча дать пустые
|
||||||
|
или неверные drill-down entries при правильных totals. Текущее rendering также
|
||||||
|
выполняет `instruments × entries` filtering. Это прямо расходится с
|
||||||
|
`frontend/docs/UI-PATTERN.md:11-28`, где domain grouping и derived collection
|
||||||
|
values принадлежат VM.
|
||||||
|
|
||||||
|
**Reference repository:** **not comparable.** В reference нет analytics или
|
||||||
|
cross-layer identity contract.
|
||||||
|
|
||||||
|
**Recommended direction:** Contract должен явно передавать membership либо
|
||||||
|
сгруппированные entries; frontend display model строится один раз в VM. Если
|
||||||
|
contract пока не меняется, formula должна иметь одного изолированного owner и
|
||||||
|
focused test, а не находиться внутри JSX module.
|
||||||
|
|
||||||
|
### F-04. Семь TSX modules скрывают основной component
|
||||||
|
|
||||||
|
**Category:** Component / File Boundaries
|
||||||
|
**Severity:** medium
|
||||||
|
**Confidence:** high
|
||||||
|
**Action:** change
|
||||||
|
**Effort:** medium
|
||||||
|
|
||||||
|
**Observed:** Из 42 non-test TSX files одиннадцать содержат несколько component
|
||||||
|
declarations; семь имеют самостоятельные states/sections, props или hooks,
|
||||||
|
из-за которых главный component перестаёт быть явным owner.
|
||||||
|
|
||||||
|
**Evidence:**
|
||||||
|
|
||||||
|
| File | Confirmed declarations | Independent responsibilities |
|
||||||
|
| --- | ---: | --- |
|
||||||
|
| `pages/account-income/ui/AccountIncomePage.view.tsx` | 10 | 3 page states, 5 sections, 2 private detail components; lines 29–352 |
|
||||||
|
| `pages/account-events/ui/AccountEventsPage.tsx` | 6 | container, 4 states, event row; lines 32–128 |
|
||||||
|
| `app/routes/AppRoutes.tsx` | 5 | 3-layout skeleton, suspense wrapper, 2 auth guards, route table; lines 55–230 |
|
||||||
|
| `pages/accounts/ui/AccountsPage.view.tsx` | 5 | 4 states plus reusable refresh state; lines 42–168 |
|
||||||
|
| `pages/account/ui/AccountPage.view.tsx` | 4 | 4 exported top-level states; lines 54–231 |
|
||||||
|
| `app/ui/AppBreadcrumbs.tsx` | 3 | trail renderer, account mapping, route orchestrator; lines 20–144 |
|
||||||
|
| `features/tbank-connection/ui/TbankConnectionSection.tsx` | 2 | 7-line state router plus 116-line form/mutation/modal owner; lines 38–161 |
|
||||||
|
|
||||||
|
Scope охватил все 42 implementation TSX, 20 TSX tests и public exports.
|
||||||
|
Counter-evidence: `RouteErrorBoundary.tsx`, `BrokerageAccountList.tsx` и
|
||||||
|
`RecentEvents.view.tsx` содержат tightly-coupled small private components;
|
||||||
|
`PositionCard`, `AuthForm` и `AccountOperationsPageLoaded` имеют одного
|
||||||
|
principal component. Их splitting не окупает navigation cost.
|
||||||
|
|
||||||
|
**Semantic owner:** Каждый named page state/section, route fallback/guard и
|
||||||
|
T-Bank loaded interaction принадлежит flat sibling module в текущем UI slice.
|
||||||
|
Маленький private renderer остаётся рядом с parent.
|
||||||
|
|
||||||
|
**Why it matters:** В перечисленных семи files independently changeable owners
|
||||||
|
делят props, imports и navigation. Самый сильный случай — 388-line Income view:
|
||||||
|
chart, comparisons, breakdown и forecast меняются независимо, но любой change
|
||||||
|
требует работать в одном module.
|
||||||
|
|
||||||
|
**Reference repository:** **adopt intent, not structure.** Reference выделяет
|
||||||
|
states, но directory-per-component со stories/tests избыточен. Target-native
|
||||||
|
precedent лучше: `account-operations` использует короткий container и flat
|
||||||
|
`components/*Loading|Error|Loaded.view.tsx`; account page уже имеет восемь flat
|
||||||
|
section files.
|
||||||
|
|
||||||
|
**Recommended direction:** Выносить только independently named states/sections
|
||||||
|
в flat sibling files, сохраняя props с owner. `Comparison` можно оставить с
|
||||||
|
`IncomeComparisons`, `ForecastEvent` — с `IncomeForecast`. Не добавлять
|
||||||
|
line-count checker, directory/barrel на каждый component или обязательный
|
||||||
|
one-test-per-file.
|
||||||
|
|
||||||
|
**Reconciliation with general audit:** General F-07 был прав, что universal
|
||||||
|
splitting Must и механическое «один component — один file» создают sprawl, но
|
||||||
|
ошибочно признал конкретный Income module достаточно cohesive. С заданной
|
||||||
|
project preference семь случаев выше являются evidence-backed finding, а
|
||||||
|
маленькие исключения остаются colocated.
|
||||||
|
|
||||||
|
### F-05. Server-state defaults и query identity имеют разных случайных владельцев
|
||||||
|
|
||||||
|
**Category:** Query Policy / Cache Keys
|
||||||
|
**Severity:** medium
|
||||||
|
**Confidence:** high
|
||||||
|
**Action:** change / simplify
|
||||||
|
**Effort:** small
|
||||||
|
|
||||||
|
**Observed:** Шесть из семи protected query configurations повторяют
|
||||||
|
`retry: false`; T-Bank status использует raw `useQuery` без этого значения и
|
||||||
|
ручной `useAuth` gate. Одновременно один production consumer и два test sites
|
||||||
|
реконструируют endpoint query identity вне entity owner.
|
||||||
|
|
||||||
|
**Evidence:** `retry: false` повторён в accounts, events, operations, portfolio
|
||||||
|
и двух income hooks (`useBrokerageAccounts.ts:12-17`,
|
||||||
|
`useBrokerageEvents.ts:9-19`, `useBrokerageOperations.ts:35-49`,
|
||||||
|
`usePortfolio.ts:4-21`, `useIncome.ts:9-41`). T-Bank status outlier —
|
||||||
|
`TbankConnectionSection.vm.ts:1-15`. Global `refetchOnWindowFocus: false`
|
||||||
|
правильно задан в `app/ui/App.tsx:10-12`, но повторён в Income hook на строке
|
||||||
|
40. Installed TanStack fallback для browser queries — три retries, когда option
|
||||||
|
не задан (`node_modules/@tanstack/query-core/src/retryer.ts:170`).
|
||||||
|
|
||||||
|
`AccountOperationsPage.vm.ts:54-58` повторно строит operations `queryOptions`
|
||||||
|
только ради reset invalidation вместо получения key от entity API owner.
|
||||||
|
`AccountPage.test.tsx:535-536,570-571` дважды hard-codes generated Portfolio key.
|
||||||
|
Counter-evidence: Brokerage Accounts и T-Bank status уже экспортируют keys из
|
||||||
|
своих endpoint options; arbitrary string production keys не используются.
|
||||||
|
|
||||||
|
**Semantic owner:**
|
||||||
|
|
||||||
|
- `retry: false` — global QueryClient default с endpoint override;
|
||||||
|
- focus policy — уже global QueryClient;
|
||||||
|
- authenticated readiness — `useAuthenticatedQuery`;
|
||||||
|
- account-ID `enabled` и `staleTime` — конкретный endpoint/entity;
|
||||||
|
- query options/key factory — endpoint entity; invalidating feature/page лишь
|
||||||
|
использует его.
|
||||||
|
|
||||||
|
**Why it matters:** Status query сейчас имеет иной retry/error timing и peer-
|
||||||
|
feature dependency на auth. Новая protected query может забыть replicated
|
||||||
|
default. Operations reset и Portfolio background-refresh tests зависят от
|
||||||
|
внутреннего key shape, который может разойтись с hook normalization.
|
||||||
|
|
||||||
|
**Reference repository:** **adopt ownership, not values.** Reference задаёт
|
||||||
|
global QueryClient defaults и entity-owned Todo keys. Target уже лучше тем, что
|
||||||
|
получает keys из generated `apiQueryClient.queryOptions`; manual reference key
|
||||||
|
object копировать не нужно.
|
||||||
|
|
||||||
|
**Recommended direction:** Поднять доказанный no-automatic-retry default в
|
||||||
|
QueryClient, удалить шесть copies и redundant focus option, провести status
|
||||||
|
через `useAuthenticatedQuery`. Расширить operations/portfolio endpoint options
|
||||||
|
до единственного key owner. Сохранить caller override, разные `staleTime` и
|
||||||
|
resource-ID gates.
|
||||||
|
|
||||||
|
### F-06. Test provider и QueryClient harnesses систематически повторяются
|
||||||
|
|
||||||
|
**Category:** Test Infrastructure / Maintainability
|
||||||
|
**Severity:** medium
|
||||||
|
**Confidence:** high
|
||||||
|
**Action:** simplify
|
||||||
|
**Effort:** medium
|
||||||
|
|
||||||
|
**Observed:** 20 RTL test files содержат 36 `MantineProvider` trees в 19 files,
|
||||||
|
19 `QueryClientProvider` trees в 13 files, 25 `MemoryRouter` trees в 11 files и
|
||||||
|
22 `new QueryClient` constructions в 13 files. Только один client получает
|
||||||
|
deterministic test retry defaults.
|
||||||
|
|
||||||
|
**Evidence:** Пять pure data-hook tests повторяют QueryClient + Mantine wrapper,
|
||||||
|
хотя hooks не зависят от Mantine:
|
||||||
|
`useBrokerageAccounts.test.tsx:11-24`,
|
||||||
|
`useBrokerageEvents.test.tsx:11-24`,
|
||||||
|
`useBrokerageOperations.test.tsx:27-40`,
|
||||||
|
`usePortfolio.test.tsx:19-32` и
|
||||||
|
`authenticated-query.test.tsx:11-24`.
|
||||||
|
`AccountOperationsPage.test.tsx:195-338` четыре раза повторяет один integration
|
||||||
|
stack. Два wrappers создают QueryClient внутри render function
|
||||||
|
(`AccountIncomePage.test.tsx:94-100`, `AccountPage.vm.test.tsx:66-79`).
|
||||||
|
|
||||||
|
Scope — все tests, provider imports, render/renderHook calls и setup. Counter-
|
||||||
|
evidence: route initial entries, real AuthProvider и seeded QueryClient реально
|
||||||
|
варьируются; cache assertions правильно сохраняют прямой доступ к client.
|
||||||
|
Entity queries уже задают retry false, поэтому отсутствие test default пока не
|
||||||
|
создаёт массовых delays.
|
||||||
|
|
||||||
|
**Semantic owner:** Небольшие composable test primitives под `src/test/`;
|
||||||
|
route/auth-specific harness остаётся рядом с соответствующим test.
|
||||||
|
|
||||||
|
**Why it matters:** Изменение deterministic client policy или provider setup
|
||||||
|
требует правки множества copies. Unused Mantine/router providers скрывают
|
||||||
|
реальные dependencies, а client, созданный внутри wrapper render, делает cache
|
||||||
|
lifetime зависимым от rerender implementation detail.
|
||||||
|
|
||||||
|
**Reference repository:** **adopt factory shape only.** Reference wrapper
|
||||||
|
создаёт client снаружи, отключает retries и возвращает `{ queryClient, wrapper }`
|
||||||
|
(`test/configs/wrapper.tsx:7-27`), но всегда добавляет Mantine, Notifications и
|
||||||
|
Router. Для target это `renderWithEverything` и лишняя coupling.
|
||||||
|
|
||||||
|
**Recommended direction:** Добавить deterministic `createTestQueryClient` и
|
||||||
|
query-only wrapper/factory, возвращающий client. Mantine, Router, Auth и initial
|
||||||
|
entries подключать явными composable options только когда они нужны. Сохранить
|
||||||
|
feature-local seeded/route harnesses.
|
||||||
|
|
||||||
|
### F-07. Cross-slice display и API primitives имеют misleading owners
|
||||||
|
|
||||||
|
**Category:** Semantic Ownership / Reuse
|
||||||
|
**Severity:** medium
|
||||||
|
**Confidence:** high
|
||||||
|
**Action:** change
|
||||||
|
**Effort:** medium
|
||||||
|
|
||||||
|
**Observed:** Portfolio public API владеет display primitives, используемыми
|
||||||
|
несвязанными entities/pages, а один transport primitive продублирован в трёх
|
||||||
|
owners.
|
||||||
|
|
||||||
|
**Evidence:** `entities/portfolio/model/display.ts:1-63` типизирует
|
||||||
|
`formatMoney` через Portfolio transport. У него 10 production consumer files
|
||||||
|
в brokerage-account, broker-operation, account, income и events;
|
||||||
|
`formatSnapshotTime` и `instrumentLogoUrl` имеют по два consumers. Два peer
|
||||||
|
entities импортируют Portfolio helpers напрямую:
|
||||||
|
`BrokerageAccountList.tsx:5` и `LedgerEntryRow.tsx:5-9`. Canonical Money —
|
||||||
|
самостоятельный ubiquitous value (`docs/architecture/domain-glossary.md:20-28`).
|
||||||
|
|
||||||
|
Одинаковый unknown-object `code` extractor существует три раза:
|
||||||
|
`brokerage-account/model/display.ts:19-23`,
|
||||||
|
`portfolio/model/display.ts:43-47` и
|
||||||
|
`TbankConnectionSection.tsx:32-36`. Generated Problem Details имеет общий
|
||||||
|
`code` (`shared/api/generated/schema.d.ts:203-207`). Scope search охватил все
|
||||||
|
formatter/error definitions и consumers. Counter-evidence: monetary formatting
|
||||||
|
algorithm уже один, сохраняет decimal string и протестирован; page-specific
|
||||||
|
code-to-copy/action mappings различаются и должны остаться local.
|
||||||
|
|
||||||
|
**Semantic owner:** `formatMoney` и proven common display primitives — узко
|
||||||
|
названный neutral lower-level display owner; API error extraction —
|
||||||
|
`shared/api`; `formatShare` остаётся Portfolio; UI error mapping остаётся page/
|
||||||
|
feature. Instrument-logo URL — отдельный named presentation integration, а не
|
||||||
|
Portfolio transport helper.
|
||||||
|
|
||||||
|
**Why it matters:** Изменение Portfolio response shape затрагивает account,
|
||||||
|
ledger, events и income. Generic transport inspection загрязняет entity display
|
||||||
|
modules и требует synchronized edit в трёх местах.
|
||||||
|
|
||||||
|
**Reference repository:** **not comparable.** Reference не имеет Money,
|
||||||
|
Instrument Logo или structured Problem Details analogue.
|
||||||
|
|
||||||
|
**Recommended direction:** Переместить только подтверждённые cross-slice
|
||||||
|
primitives в narrowly named modules. Не создавать generic `utils`, Money
|
||||||
|
service/interface, новую domain layer или configurable formatter framework;
|
||||||
|
сохранить exact-string implementation.
|
||||||
|
|
||||||
|
### F-08. Global CSS владеет component и Mantine-internal styling
|
||||||
|
|
||||||
|
**Category:** Styling Boundaries / Complexity
|
||||||
|
**Severity:** medium
|
||||||
|
**Confidence:** high
|
||||||
|
**Action:** change / simplify
|
||||||
|
**Effort:** small–medium
|
||||||
|
|
||||||
|
**Observed:** `app/styles.css` смешивает reset/theme с styles четырёх component
|
||||||
|
owners и прямым selector внутренней Mantine class.
|
||||||
|
|
||||||
|
**Evidence:** AppShell rules `.app-header`, `.bottom-nav` и `.active` находятся
|
||||||
|
на строках 27–34 и 56–72, но consumers — `shared/ui/AppShell.tsx:25,43-47`.
|
||||||
|
Breadcrumb rules `[data-depth]` и mobile child hiding — строки 35–40,73–75,
|
||||||
|
consumer — `app/ui/AppBreadcrumbs.tsx:25-30`. Auth page/card/fieldset — строки
|
||||||
|
41–55, consumers — Login/Register pages. `.mantine-AppShell-main` на строках
|
||||||
|
76–78 зависит от library implementation name.
|
||||||
|
|
||||||
|
Из 11 объявленных `--ft-*` tokens используются пять; шесть dead: `bg-app`,
|
||||||
|
`bg-muted`, `text-secondary`, `danger`, `radius-card`, `shadow-card`.
|
||||||
|
`--ft-color-accent: #2563eb` повторяет Mantine primary blue из
|
||||||
|
`app/theme.ts:8-20`. Scope — все пять CSS files и все class consumers. Counter-
|
||||||
|
evidence: остальные четыре CSS files являются scoped Modules и преимущественно
|
||||||
|
используют Mantine variables; `*`, `body`, base anchor и page background имеют
|
||||||
|
реальный global owner.
|
||||||
|
|
||||||
|
**Semantic owner:** Shell/navigation — AppShell module или Mantine `classNames`;
|
||||||
|
breadcrumbs — AppBreadcrumbs module; auth layout — shared auth presentation
|
||||||
|
только если reuse остаётся реальным; reset/page theme — global app style.
|
||||||
|
|
||||||
|
**Why it matters:** Component changes требуют cross-layer lookup; generic data/
|
||||||
|
class selectors могут задеть будущую markup; library selector upgrade-sensitive;
|
||||||
|
dead/duplicated tokens создают ложную design-system surface.
|
||||||
|
|
||||||
|
**Reference repository:** **adopt localization only.** Reference широко
|
||||||
|
использует component CSS Modules, но hard-codes больше цветов и создаёт
|
||||||
|
directory-per-component styles. Target Mantine variables лучше и должны быть
|
||||||
|
сохранены.
|
||||||
|
|
||||||
|
**Recommended direction:** Оставить глобально только reset и genuine app theme.
|
||||||
|
Локализовать shell/breadcrumb/auth rules, style AppShell main через owned seam,
|
||||||
|
удалить dead tokens и использовать Mantine theme variable вместо duplicate
|
||||||
|
accent. Не добавлять CSS-in-JS или design-token dependency.
|
||||||
|
|
||||||
|
### F-09. `connectedAt` contract скрыт через `String(...)` coercion
|
||||||
|
|
||||||
|
**Category:** API Ownership / Correctness
|
||||||
|
**Severity:** medium
|
||||||
|
**Confidence:** high
|
||||||
|
**Action:** change
|
||||||
|
**Effort:** small
|
||||||
|
|
||||||
|
**Observed:** Committed OpenAPI snapshot описывает nullable date-time как
|
||||||
|
`type: object`; generated frontend type становится
|
||||||
|
`Record<string, never> | null`. Component скрывает mismatch через
|
||||||
|
`String(status.connectedAt)` перед date parsing.
|
||||||
|
|
||||||
|
**Evidence:** Snapshot —
|
||||||
|
`frontend/src/shared/api/spec/openapi.json:1271-1286`; generated type —
|
||||||
|
`shared/api/generated/schema.d.ts:232-236`; coercion —
|
||||||
|
`features/tbank-connection/ui/TbankConnectionSection.tsx:90-95`. Backend owner
|
||||||
|
объявляет `connectedAt: string | null` и возвращает ISO string
|
||||||
|
(`backend/src/tbank/tbank-connection.dto.ts:12-17`,
|
||||||
|
`tbank-connection.service.ts:18-25,47-50`). Все другие audited generated date/
|
||||||
|
date-time fields являются strings.
|
||||||
|
|
||||||
|
Scope search охватил snapshot/generated date fields и production date parsing.
|
||||||
|
Counter-evidence: runtime normal path сейчас действительно возвращает string,
|
||||||
|
поэтому visible behavior обычно корректен.
|
||||||
|
|
||||||
|
**Semantic owner:** Backend/OpenAPI contract; frontend formatter не должен
|
||||||
|
нормализовать invalid transport type.
|
||||||
|
|
||||||
|
**Why it matters:** Type boundary не доказывает parseable timestamp, а
|
||||||
|
`String(object)` превращает drift в `"[object Object]"`, скрывая contract
|
||||||
|
problem до invalid UI date. Это отличается от general audit F-01: generated
|
||||||
|
types здесь синхронны со snapshot, но сам snapshot неверен.
|
||||||
|
|
||||||
|
**Reference repository:** **not comparable.**
|
||||||
|
|
||||||
|
**Recommended direction:** Исправить source contract на nullable string с
|
||||||
|
`date-time`, синхронизировать snapshot/types и убрать coercion. Новая date
|
||||||
|
library не нужна.
|
||||||
|
|
||||||
|
### F-10. Два presentation files владеют validation/payment invariants
|
||||||
|
|
||||||
|
**Category:** Helper / Semantic Ownership
|
||||||
|
**Severity:** low
|
||||||
|
**Confidence:** high
|
||||||
|
**Action:** change
|
||||||
|
**Effort:** small
|
||||||
|
|
||||||
|
**Observed:** Два single-consumer helper clusters не являются внутренней JSX
|
||||||
|
механикой. `AuthForm.tsx:12-53` содержит email normalization/rules, UTF-8
|
||||||
|
password byte bounds и login/register schemas. `LedgerEntryRow.tsx:23-29,75-108`
|
||||||
|
содержит operation label mapping, signed payment presentation и
|
||||||
|
`isZeroDecimal`.
|
||||||
|
|
||||||
|
Scope search охватил schemas, form defaults, decimal/Money operations, mapping
|
||||||
|
и grouping functions. Counter-evidence: behavior сейчас согласован и частично
|
||||||
|
покрыт tests; количество consumers само по себе extraction не требует.
|
||||||
|
|
||||||
|
**Semantic owner:** Auth validation — соседний auth model module; cohesive
|
||||||
|
payment mapper вместе с `isZeroDecimal` — broker-operation model/display module.
|
||||||
|
`isZeroDecimal` не оправдывает generic decimal library.
|
||||||
|
|
||||||
|
**Why it matters:** Эти правила меняются вместе с auth/ledger semantics, а не с
|
||||||
|
Mantine markup, и труднее тестируются отдельно внутри component files.
|
||||||
|
|
||||||
|
**Reference repository:** Reference хранит reusable Todo schema у entity, что
|
||||||
|
подтверждает owner principle, но его folder hierarchy копировать не нужно.
|
||||||
|
|
||||||
|
**Recommended direction:** Вынести два cohesive semantic clusters к соседним
|
||||||
|
feature/entity owners. Оставить `toDateValue`, `fromDateValue` и
|
||||||
|
`fromMultiSelectValue` рядом с Mantine control adapter; `dateStart`, `dateEnd`
|
||||||
|
и `sameCategories` — рядом с operations VM; form defaults — у конкретных forms.
|
||||||
|
|
||||||
|
### F-11. Redundant position modes и test-facing proxy exports не несут invariant
|
||||||
|
|
||||||
|
**Category:** Complexity / Simplification
|
||||||
|
**Severity:** low
|
||||||
|
**Confidence:** high
|
||||||
|
**Action:** simplify
|
||||||
|
**Effort:** small
|
||||||
|
|
||||||
|
**Observed:** Две небольшие surfaces создают варианты/exports без отдельной
|
||||||
|
семантики.
|
||||||
|
|
||||||
|
Position `kind` и `wide` кодируют одну fixed mapping: stocks/bonds всегда wide,
|
||||||
|
other всегда compact (`AccountPage.vm.ts:33-36,179-214`).
|
||||||
|
`PositionSection.view.tsx:14-26` дополнительно выводит `variant`, а
|
||||||
|
`PositionCard.view.tsx:18-36` выбирает stock/bond class map. Шесть пар CSS roles
|
||||||
|
для stock и bond фактически идентичны
|
||||||
|
(`AccountPage.module.css:35-88`); реальное различие одно — identifier choice на
|
||||||
|
`PositionCard.view.tsx:48-49`.
|
||||||
|
|
||||||
|
Четыре test-oriented proxy export patterns: page alias `OperationRow` для
|
||||||
|
`LedgerEntryRow` (`AccountOperationsPage.tsx:2,22`), proxy Mantine `Button`
|
||||||
|
через `StateView.tsx:1,26`, четыре Account state re-exports
|
||||||
|
(`AccountPage.tsx:41-47`) и Accounts state public exports, используемые только
|
||||||
|
`AppRoutes.test.tsx` (`AccountsPage.tsx:32-37`, `pages/accounts/index.ts:1-6`).
|
||||||
|
|
||||||
|
Scope — all component props/classes, all barrels и consumers. Counter-evidence:
|
||||||
|
FSD slice barrels имеют реальную public-boundary роль и удаляться не должны;
|
||||||
|
stock/bond identifier semantics остаются различными.
|
||||||
|
|
||||||
|
**Semantic owner:** Один position presentation mode, выводимый из section kind;
|
||||||
|
tests импортируют actual owner либо наблюдают page behavior; public barrels
|
||||||
|
экспортируют только product surface.
|
||||||
|
|
||||||
|
**Why it matters:** Invalid prop combinations остаются representable, CSS edits
|
||||||
|
дублируются, а public APIs обещают reuse, которого нет.
|
||||||
|
|
||||||
|
**Reference repository:** **target should simplify.** Reference не proxy-export
|
||||||
|
Mantine/entity components под page-only именами.
|
||||||
|
|
||||||
|
**Recommended direction:** Удалить redundant `wide`/duplicate class mapping,
|
||||||
|
сохранив identifier distinction. Убрать proxy exports и вернуть tests к
|
||||||
|
реальным owners. Не удалять meaningful FSD barrels и не добавлять `clsx`:
|
||||||
|
единственная простая two-class interpolation этого не оправдывает.
|
||||||
|
|
||||||
|
### Evidence Ledger Reconciliation
|
||||||
|
|
||||||
|
Все обнаруженные high/medium candidates получили явный disposition:
|
||||||
|
|
||||||
|
- retained: F-01 — F-09;
|
||||||
|
- general audit F-03 (invalid income calendar date) подтверждён, но не
|
||||||
|
продублирован; он остаётся единственным полным owner этого correctness finding;
|
||||||
|
- general F-05 sharpened в F-07, general F-08 включён в F-05;
|
||||||
|
- general F-07 частично superseded в F-04 с сохранением colocated exceptions;
|
||||||
|
- объединены: oversized files/state counts/private-component size → F-04;
|
||||||
|
global selectors/dead tokens/theme duplication → F-08; Money/date/logo/error
|
||||||
|
primitives → F-07;
|
||||||
|
- discarded: объединить пять `staleTime: 30_000` — одинаковое число не
|
||||||
|
доказывает общий invariant; accounts имеет 60 seconds, а freshness ownership
|
||||||
|
endpoint-specific;
|
||||||
|
- discarded: перенести пять `enabled: Boolean(accountId)` в auth seam — это
|
||||||
|
endpoint parameter validation, не authentication;
|
||||||
|
- discarded: общий form factory/FormProvider для трёх forms — state и
|
||||||
|
validation semantics различаются;
|
||||||
|
- discarded: Day.js strict parsing, Decimal.js, Lodash/groupBy, error library,
|
||||||
|
`clsx`/`classnames`, design-token library — platform/current dependencies и
|
||||||
|
small local code достаточны;
|
||||||
|
- discarded: generic cache-key/entity registry для F-01 — один connection
|
||||||
|
boundary не оправдывает registry;
|
||||||
|
- discarded: universal one-component-per-file enforcement и reference
|
||||||
|
directory-per-component layout — navigation cost превышает пользу.
|
||||||
|
|
||||||
|
## Policy Consolidation
|
||||||
|
|
||||||
|
| Policy | Verdict | Owner / Finding |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| T-Bank-derived cache eviction | consolidate as one hard boundary | connection lifecycle / F-01 |
|
||||||
|
| `retry: false` | consolidate; explicit override remains possible | global QueryClient / F-05 |
|
||||||
|
| `refetchOnWindowFocus: false` | keep global; remove Income duplicate | global QueryClient / F-05 |
|
||||||
|
| authenticated readiness | keep consolidated | `useAuthenticatedQuery` / F-05 |
|
||||||
|
| `enabled: Boolean(accountId)` | keep local | endpoint hook / F-05 |
|
||||||
|
| `staleTime` 30s/60s | keep local and documented by endpoint | entity query / F-05 |
|
||||||
|
| query options and keys | one endpoint owner; consumers reuse | entity API / F-05 |
|
||||||
|
| mutation-specific status seeding/modal reset | keep colocated | T-Bank feature / F-01 |
|
||||||
|
| form defaults/resolvers | keep form/page-specific | Auth, T-Bank, Income / F-10 |
|
||||||
|
|
||||||
|
## Helper and Semantic Ownership
|
||||||
|
|
||||||
|
| Candidate | Verdict | Finding |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `formatMoney` | move to narrowly named neutral money-display owner | F-07 |
|
||||||
|
| exact decimal-string formatting | keep implementation; do not replace with `Number`/library | F-07 |
|
||||||
|
| `formatShare` | keep Portfolio-owned | F-07 |
|
||||||
|
| `formatSnapshotTime` / truly identical calendar display | named neutral date-display owner only when semantics match | F-07 |
|
||||||
|
| `instrumentLogoUrl` | move from Portfolio to named presentation integration owner | F-07 |
|
||||||
|
| generic API `code` extraction | merge into one `shared/api` guard | F-07 |
|
||||||
|
| code-to-message/action mapping | keep page/feature-local | F-07 |
|
||||||
|
| `paymentPresentation` + `isZeroDecimal` | extract to neighboring broker-operation model | F-10 |
|
||||||
|
| auth schemas/rules | extract to auth model, not shared validation | F-10 |
|
||||||
|
| income `entryKey` | API owns membership; VM owns display model | F-03 |
|
||||||
|
| `toAccountPagePositionSections` | keep in Account page VM | F-03 counter-pattern |
|
||||||
|
| `toForecast` | keep in Income VM | F-03 counter-pattern |
|
||||||
|
| Mantine date/select adapters | keep colocated with loaded operations view | F-10 |
|
||||||
|
|
||||||
|
## Component and File Boundaries
|
||||||
|
|
||||||
|
F-04 identifies the seven evidence-backed splits and explicit exceptions. The
|
||||||
|
recommended priority is Income states/sections first, then Account/Accounts/
|
||||||
|
Events states, route fallback/guards, breadcrumbs and T-Bank loaded
|
||||||
|
interaction. Use the target's flat `components/*.view.tsx` precedent; do not
|
||||||
|
copy the reference's directory-per-component hierarchy.
|
||||||
|
|
||||||
|
## Test Infrastructure
|
||||||
|
|
||||||
|
F-02 owns global teardown and session reset. F-06 owns deterministic QueryClient
|
||||||
|
and composable provider setup. MSW lifecycle should remain where it is:
|
||||||
|
`src/test/setup.ts` already uses strict `onUnhandledRequest: "error"`, resets
|
||||||
|
handlers per test and closes the shared server once. Route/auth-specific
|
||||||
|
harnesses and direct access to seeded QueryClient remain local when the test
|
||||||
|
observes those semantics.
|
||||||
|
|
||||||
|
## Styling and Library Leverage
|
||||||
|
|
||||||
|
F-08 covers localization of component styles and removal of false token surface;
|
||||||
|
F-11 covers duplicate position selectors and rejects `clsx` for one simple
|
||||||
|
interpolation. CSS Modules, Mantine props/theme variables, platform `Intl`,
|
||||||
|
`TextEncoder`, `Map`, arrays, Zod/RHF and TanStack Query already solve the
|
||||||
|
audited non-domain concerns. F-09 is a contract correction, not a reason to add
|
||||||
|
a date library.
|
||||||
|
|
||||||
|
## Simplification Opportunities
|
||||||
|
|
||||||
|
- F-11 — collapse redundant position mode/CSS and remove test-only proxy
|
||||||
|
exports while preserving meaningful slice barrels.
|
||||||
|
- F-08 — delete six unused `--ft-*` tokens instead of introducing a design-
|
||||||
|
token layer.
|
||||||
|
- F-06 — remove unused providers from pure hook tests rather than standardizing
|
||||||
|
on a maximal wrapper.
|
||||||
|
- F-04 — split semantic owners without generating one directory, barrel, test
|
||||||
|
and stylesheet per component.
|
||||||
|
- General audit F-07's warning against mechanical splitting remains applicable
|
||||||
|
to files outside the seven evidence-backed cases.
|
||||||
|
|
||||||
|
## What Should Stay Colocated
|
||||||
|
|
||||||
|
- `RouteErrorBoundary` class, fallback and hook-aware wrapper: один React error-
|
||||||
|
boundary invariant.
|
||||||
|
- `BrokerageAccountSummary` with its list and `RecentEvent` with its section:
|
||||||
|
по одному небольшому tightly-coupled private renderer.
|
||||||
|
- Tiny T-Bank loading/error sibling views: splitting is lower value than
|
||||||
|
extracting the large loaded interaction.
|
||||||
|
- `PositionCard`, `AuthForm` и `AccountOperationsPageLoaded`: по одному principal
|
||||||
|
component; move only semantic helpers named in F-10.
|
||||||
|
- `staleTime`, resource-ID gates и query parameter normalization: endpoint
|
||||||
|
semantics, not global convenience defaults.
|
||||||
|
- Page-specific error copy/actions and T-Bank mutation-specific modal/form
|
||||||
|
behavior.
|
||||||
|
- Income trend selected-currency state and pure `positive`/`negative`/
|
||||||
|
`chartHeight` rendering geometry after the collection model moves out.
|
||||||
|
- Meaningful one-line FSD public barrels and `ProtectedLayout` route-composition
|
||||||
|
seam.
|
||||||
|
- Separate `LoginForm`/`RegisterForm` use-case adapters; merging them into a
|
||||||
|
boolean-heavy generic wrapper would reduce semantic clarity.
|
||||||
|
|
||||||
|
## What Is Already Good
|
||||||
|
|
||||||
|
- `useAuthenticatedQuery` правильно объединяет session readiness с caller's
|
||||||
|
boolean/function `enabled`; F-05 требует reuse, а не новой auth abstraction.
|
||||||
|
- API Query options создают реальные TanStack keys; arbitrary production string
|
||||||
|
keys отсутствуют, а Operations normalization сортирует categories.
|
||||||
|
- Auth transport централизует Bearer, single-flight refresh, one unauthorized
|
||||||
|
retry и session cache clearing.
|
||||||
|
- Money formatting уже централизован, exact-string based и протестирован; нужно
|
||||||
|
исправить owner, а не algorithm.
|
||||||
|
- Page container → VM → props-only view pattern хорошо реализован в Account и
|
||||||
|
Account Operations; последний даёт пропорциональный file-splitting precedent.
|
||||||
|
- Account page уже выделяет восемь semantic sections в flat components без
|
||||||
|
excessive hierarchy.
|
||||||
|
- Четыре component/page style areas используют CSS Modules и Mantine variables;
|
||||||
|
глобальная leakage ограничена одним file.
|
||||||
|
- MSW server/setup, browser shims, i18n и jest-dom имеют ясных test owners;
|
||||||
|
target использует более строгий unhandled-request policy, чем reference.
|
||||||
|
- Tests, которым нужна проверка invalidation или cached snapshot, сохраняют
|
||||||
|
явный QueryClient и наблюдают behavior, а не implementation-only mocks.
|
||||||
|
- Current platform/library usage уже library-first; ни одна новая dependency не
|
||||||
|
прошла net-complexity gate.
|
||||||
|
|
||||||
|
## Recommended Order
|
||||||
|
|
||||||
|
### Do now
|
||||||
|
|
||||||
|
1. F-01 — восстановить connection-boundary cache eviction.
|
||||||
|
2. F-02 — централизовать RTL/auth teardown и убрать order-dependent listeners.
|
||||||
|
3. F-09 — исправить `connectedAt` source contract и удалить coercion.
|
||||||
|
4. F-03 — вернуть income membership/grouping правильным API/VM owners.
|
||||||
|
5. F-05 — централизовать proven query defaults и endpoint key ownership.
|
||||||
|
|
||||||
|
### Do next
|
||||||
|
|
||||||
|
1. F-04 — разделить семь крупных semantic owner modules по target-native flat
|
||||||
|
pattern.
|
||||||
|
2. F-06 — ввести narrow composable test primitives.
|
||||||
|
3. F-07 — переместить proven cross-slice primitives к neutral/shared owners.
|
||||||
|
4. F-08 — локализовать component styling и сократить token surface.
|
||||||
|
|
||||||
|
### Consider later
|
||||||
|
|
||||||
|
1. F-10 — вынести auth/payment semantic clusters при ближайшем изменении
|
||||||
|
соответствующей feature/entity.
|
||||||
|
2. F-11 — схлопнуть position modes/CSS и test-facing proxy exports.
|
||||||
|
|
||||||
|
### Do not add
|
||||||
|
|
||||||
|
- `renderWithEverything`, global Notifications/Dates/Auth providers или
|
||||||
|
reference wrapper без явной потребности test.
|
||||||
|
- Universal one-component-per-file rule, line-count gate, directory/barrel per
|
||||||
|
component или copy reference's Storybook-oriented hierarchy.
|
||||||
|
- `clsx`/`classnames`, date/decimal/grouping/error library только ради найденных
|
||||||
|
случаев.
|
||||||
|
- Generic `utils`, Money service/interface, cache registry, second FSD checker,
|
||||||
|
CSS-in-JS или design-token framework.
|
||||||
|
- Storybook, browser MSW, Playwright, CI/CD или локальный OpenSpec store: этот
|
||||||
|
focused audit не обнаружил structural problem, которая окупила бы их
|
||||||
|
lifecycle cost.
|
||||||
@@ -10,3 +10,4 @@
|
|||||||
- [Multi-source assessment WealthLedger SDD 26.08.2026](2026-08-26-wealth-ledger-sdd/README.md)
|
- [Multi-source assessment WealthLedger SDD 26.08.2026](2026-08-26-wealth-ledger-sdd/README.md)
|
||||||
- [Повторный multi-source review WealthLedger SDD v2 26.08.2026](2026-08-26-wealth-ledger-sdd-v2-review/README.md)
|
- [Повторный multi-source review WealthLedger SDD v2 26.08.2026](2026-08-26-wealth-ledger-sdd-v2-review/README.md)
|
||||||
- [Аудит расхода токенов Codex-сессий 26.08.2026](codex-sessions-token-audit-2026-08-26.md)
|
- [Аудит расхода токенов Codex-сессий 26.08.2026](codex-sessions-token-audit-2026-08-26.md)
|
||||||
|
- [Frontend repository и structural audits августа 2026](2026-08-frontend-repository-audits/README.md)
|
||||||
|
|||||||
@@ -22,11 +22,13 @@
|
|||||||
|
|
||||||
- Bootstrap change: [`2026-08-23-establish-audit-remediation-workflow`](../../specs/openspec/changes/archive/2026-08-23-establish-audit-remediation-workflow/) — `archived`; это process foundation и не закрывает ни одного Audit ID.
|
- Bootstrap change: [`2026-08-23-establish-audit-remediation-workflow`](../../specs/openspec/changes/archive/2026-08-23-establish-audit-remediation-workflow/) — `archived`; это process foundation и не закрывает ни одного Audit ID.
|
||||||
- Последний завершённый audit remediation change: [`2026-08-26-harden-openspec-sdd-agent-resilience`](../../specs/openspec/changes/archive/2026-08-26-harden-openspec-sdd-agent-resilience/) — `archived`; SDD26R2-01–SDD26R2-06 закрыты.
|
- Последний завершённый audit remediation change: [`2026-08-26-harden-openspec-sdd-agent-resilience`](../../specs/openspec/changes/archive/2026-08-26-harden-openspec-sdd-agent-resilience/) — `archived`; SDD26R2-01–SDD26R2-06 закрыты.
|
||||||
- Единственный active change: отсутствует.
|
- Единственный active remediation: `complete-frontend-audit-remediation`; [canonical change archived](../../specs/openspec/changes/archive/2026-09-06-complete-frontend-audit-remediation/), terminal evidence ещё оформляется.
|
||||||
|
|
||||||
## Текущая задача
|
## Текущая задача
|
||||||
|
|
||||||
Создать следующий remediation change, когда владелец выберет продолжение.
|
Завершить delivery commits и Completion Evidence для
|
||||||
|
[`complete-frontend-audit-remediation`](../../specs/openspec/changes/archive/2026-09-06-complete-frontend-audit-remediation/tasks.md).
|
||||||
|
Все tasks и независимая Validation завершены; до terminal evidence записи остаются `active`.
|
||||||
|
|
||||||
## Следующая рекомендуемая задача
|
## Следующая рекомендуемая задача
|
||||||
|
|
||||||
@@ -36,6 +38,45 @@
|
|||||||
|
|
||||||
## Реестр findings
|
## Реестр findings
|
||||||
|
|
||||||
|
### Frontend audits: август 2026
|
||||||
|
|
||||||
|
Источник: [три неизменяемых snapshots](../audits/2026-08-frontend-repository-audits/README.md).
|
||||||
|
`G` — общий audit от 31 августа, `A28` — audit от 28 августа,
|
||||||
|
`S` — structural audit от 31 августа. Все 22 source findings сопоставлены
|
||||||
|
18 underlying claims ниже; одинаковые F IDs без source prefix неоднозначны.
|
||||||
|
|
||||||
|
Дата сопоставления: 06.09.2026. Текущий change всех `active` записей —
|
||||||
|
[`complete-frontend-audit-remediation`](../../specs/openspec/changes/archive/2026-09-06-complete-frontend-audit-remediation/).
|
||||||
|
`already-covered` означает наличие проверяемой реализации, а `active` для неё —
|
||||||
|
незавершённое подтверждение текущей поставки, не повторную implementation task.
|
||||||
|
Реализация прежних исправлений находится во frontend `c97f62b` и backend
|
||||||
|
`55b100d`; эти идентификаторы не доказывают исторические approvals или gates.
|
||||||
|
|
||||||
|
| Audit ID | Нормализованный claim | Sources | Disposition | Remediation status | Evidence / текущее состояние |
|
||||||
|
| --- | --- | --- | --- | --- | --- |
|
||||||
|
| FE26-01 | Общая граница финансового кэша при смене T-Bank connection. | [FE-A28] F1; [FE-S] F-01 | already-covered | done | 01.09.2026; [archive](../../specs/openspec/changes/archive/2026-09-01-harden-tbank-connection-cache-boundary/), delivery commits, gates и independent review — [существующее Completion Evidence](completion-evidence.md#2026-09-01-harden-tbank-connection-cache-boundary). |
|
||||||
|
| FE26-02 | Проверять соответствие generated types OpenAPI snapshot. | [FE-A28] F2; [FE-G] F-01 | already-covered | active | [check-openapi](../../frontend/scripts/check-openapi.mjs) включён в [verify](../../frontend/package.json); snapshot/types проверяются offline. Pre-commit состав честно описан в conventions. |
|
||||||
|
| FE26-03 | Events должен соблюдать принятую VM/view границу. | [FE-A28] F3 | confirmed | active | [Events VM](../../frontend/src/pages/account-events/ui/AccountEventsPage.vm.ts) владеет route/query/state/retry; [container](../../frontend/src/pages/account-events/ui/AccountEventsPage.tsx) использует прежние views. Остальные named pages уже разделены; см. FE26-13. |
|
||||||
|
| FE26-04 | Покрыть успешный public login journey. | [FE-G] F-02 | already-covered | active | [AppRoutes tests](../../frontend/src/app/routes/AppRoutes.test.tsx): форма, нормализованные credentials, реальный AuthProvider/MSW и protected landing. |
|
||||||
|
| FE26-05 | Безопасно отклонять невозможные календарные даты. | [FE-G] F-03 | already-covered | active | [Income VM](../../frontend/src/pages/account-income/ui/AccountIncomePage.vm.ts) проверяет finite date до ISO conversion; [regression tests](../../frontend/src/pages/account-income/ui/AccountIncomePage.test.tsx) покрывают invalid date и leap year. |
|
||||||
|
| FE26-06 | Включить type-only dependencies в public API guardrail. | [FE-G] F-04 | already-covered | active | [dependency-cruiser](../../frontend/dependency-cruiser.cjs) включает tsPreCompilationDeps; [shared API](../../frontend/src/shared/api/index.ts) экспортирует paths. |
|
||||||
|
| FE26-07 | Дать общим display/API primitives нейтральных владельцев. | [FE-G] F-05; [FE-S] F-07 | already-covered | active | [shared display](../../frontend/src/shared/lib/display.ts) владеет Money/date/logo; [apiErrorCode](../../frontend/src/shared/api/problem-details.ts) объединяет transport extraction. S F-07 расширяет тот же ownership claim API helper; повторной реализации нет. |
|
||||||
|
| FE26-08 | Синхронизировать entrypoint docs с фактическими gates. | [FE-G] F-06 | already-covered | active | [Frontend README](../../frontend/README.md), [conventions](../../frontend/docs/CONVENTIONS.md) и [workflow](frontend-workflow.md) описывают существующие команды, не обещая удалённые runtime/hygiene checks. |
|
||||||
|
| FE26-09 | Убрать механическое обязательное дробление каждого component. | [FE-G] F-07 | already-covered | active | [UI pattern](../../frontend/docs/UI-PATTERN.md) сохраняет semantic ownership и допускает tightly-coupled renderers; это guidance, а не line-count gate. Конкретное разделение крупных modules учтено отдельно FE26-13. |
|
||||||
|
| FE26-10 | Объединить protected query policy и endpoint query identity ownership. | [FE-G] F-08; [FE-S] F-05 | already-covered | active | [App defaults](../../frontend/src/app/ui/App.tsx), [T-Bank VM](../../frontend/src/features/tbank-connection/ui/TbankConnectionSection.vm.ts) и entity-owned options operations/portfolio; status использует общий authenticated query seam. |
|
||||||
|
| FE26-11 | Централизовать test cleanup и auth reset. | [FE-S] F-02 | already-covered | active | [Test setup](../../frontend/src/test/setup.ts): RTL cleanup, auth reset, MSW handler reset в общем afterEach. |
|
||||||
|
| FE26-12 | Убрать повтор server identity из Income view/model. | [FE-S] F-03 | confirmed | active | [Backend income service](../../backend/src/tbank/income-analytics.service.ts) передаёт instrumentKey; [Income VM](../../frontend/src/pages/account-income/ui/AccountIncomePage.vm.ts) проверяет и группирует готовую связь, не вычисляя identity. |
|
||||||
|
| FE26-13 | Выделить самостоятельные ответственности семи крупных TSX modules. | [FE-S] F-04 | already-covered | active | Income/Events/Accounts/Account state views, [route fallback](../../frontend/src/app/routes/RouteFallback.tsx), [guards](../../frontend/src/app/routes/RouteGuards.tsx), breadcrumbs и T-Bank loaded section выделены во frontend `c97f62b`. |
|
||||||
|
| FE26-14 | Устранить повтор test client policy и нестабильный cache lifetime. | [FE-S] F-06 | confirmed | active | [Test query factory](../../frontend/src/test/query.tsx) используется оставшимися auth/T-Bank harnesses; [auth test](../../frontend/src/features/auth/model/auth.test.tsx) проверяет сохранение client и seeded cache при rerender. |
|
||||||
|
| FE26-15 | Перенести компонентные стили из global CSS к владельцам. | [FE-S] F-08 | already-covered | active | [Global styles](../../frontend/src/app/styles.css) содержат базовые правила; AppShell, breadcrumbs и auth используют CSS Modules, StateView — Mantine props. |
|
||||||
|
| FE26-16 | Исправить source connectedAt contract вместо String coercion. | [FE-S] F-09 | already-covered | active | [Backend DTO](../../backend/src/tbank/tbank-connection.dto.ts) задаёт nullable date-time string; [frontend consumer](../../frontend/src/features/tbank-connection/ui/TbankConnectionSectionLoaded.tsx) не скрывает неверный transport type через String. |
|
||||||
|
| FE26-17 | Перенести auth validation и payment invariants из JSX к model owners. | [FE-S] F-10 | already-covered | active | [Auth validation](../../frontend/src/features/auth/model/validation.ts), [ledger display](../../frontend/src/entities/broker-operation/model/display.ts) владеют соответствующими правилами. |
|
||||||
|
| FE26-18 | Удалить redundant position modes и test-only proxy exports. | [FE-S] F-11 | already-covered | active | [PositionCard](../../frontend/src/pages/account/ui/components/PositionCard.view.tsx) выводит mode из kind; [StateView](../../frontend/src/shared/ui/StateView.tsx) не экспортирует Mantine Button; page barrels сохраняют product surface. |
|
||||||
|
|
||||||
|
[FE-G]: ../audits/2026-08-frontend-repository-audits/frontend-repository-audit.md
|
||||||
|
[FE-A28]: ../audits/2026-08-frontend-repository-audits/frontend-repository-audit-2026-08-28.md
|
||||||
|
[FE-S]: ../audits/2026-08-frontend-repository-audits/frontend-structural-maintainability-audit.md
|
||||||
|
|
||||||
### Повторный multi-source review WealthLedger SDD v2 26.08.2026
|
### Повторный multi-source review WealthLedger SDD v2 26.08.2026
|
||||||
|
|
||||||
Шесть исходных reports находятся в [immutable snapshot set](../audits/2026-08-26-wealth-ledger-sdd-v2-review/README.md).
|
Шесть исходных reports находятся в [immutable snapshot set](../audits/2026-08-26-wealth-ledger-sdd-v2-review/README.md).
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ npm ci
|
|||||||
npm run verify
|
npm run verify
|
||||||
```
|
```
|
||||||
|
|
||||||
`verify` проверяет TypeScript, Biome, один coverage-backed запуск Vitest, Knip,
|
`verify` проверяет OpenAPI drift, TypeScript, Biome, один coverage-backed запуск Vitest, Knip,
|
||||||
FSD dependency rules и production build. Он работает без backend. Coverage
|
FSD dependency rules и production build. Он работает без backend. Coverage
|
||||||
остаётся anti-regression constraint; правила выбора тестов зафиксированы в
|
остаётся anti-regression constraint; правила выбора тестов зафиксированы в
|
||||||
[engineering governance](engineering-governance.md).
|
[engineering governance](engineering-governance.md).
|
||||||
@@ -25,3 +25,14 @@ npm run openapi:generate
|
|||||||
`src/shared/api/generated/schema.d.ts` offline. Проверку соответствия backend
|
`src/shared/api/generated/schema.d.ts` offline. Проверку соответствия backend
|
||||||
runtime contract и frontend snapshot выполняют отдельной backend-командой
|
runtime contract и frontend snapshot выполняют отдельной backend-командой
|
||||||
`npm run test:openapi-contract`.
|
`npm run test:openapi-contract`.
|
||||||
|
|
||||||
|
## Состояние тестового клиента
|
||||||
|
|
||||||
|
`frontend/src/test/query.tsx` предоставляет `createTestQueryClient` с отключёнными
|
||||||
|
test retries и query-only `createQueryWrapper`. Независимый case получает свой
|
||||||
|
client до React wrapper render; rerender сохраняет тот же экземпляр и seeded
|
||||||
|
cache. Auth и T-Bank tests используют эту же фабрику. Mantine, Router и
|
||||||
|
AuthProvider остаются явно выбранными локальными providers.
|
||||||
|
|
||||||
|
Общий teardown последовательно выполняет RTL cleanup, сброс auth client и
|
||||||
|
сброс MSW handlers. Production query defaults этой инфраструктурой не меняются.
|
||||||
|
|||||||
@@ -18,6 +18,37 @@
|
|||||||
- Trend buckets приходят с backend: дни для 1–45 дней, недели для 46–180 и месяцы для более длинного периода. Пустые buckets остаются в response и текстовом представлении.
|
- Trend buckets приходят с backend: дни для 1–45 дней, недели для 46–180 и месяцы для более длинного периода. Пустые buckets остаются в response и текстовом представлении.
|
||||||
- Breakdown раскрывает включённые Ledger Entries из того же ответа, поэтому отображённые строки не требуют отдельного запроса и воспроизводят сумму Instrument и валюты.
|
- Breakdown раскрывает включённые Ledger Entries из того же ответа, поэтому отображённые строки не требуют отдельного запроса и воспроизводят сумму Instrument и валюты.
|
||||||
|
|
||||||
|
## Связь breakdown с выплатами
|
||||||
|
|
||||||
|
Каждая запись `income-analytics.entries` содержит обязательный `instrumentKey`,
|
||||||
|
равный `key` соответствующей строки `instruments`. Backend вычисляет этот ключ
|
||||||
|
один раз при подготовке income entries и использует его же для группировки.
|
||||||
|
Приоритет UID, FIGI, ticker/type и operation ID с разделением по валюте остаётся
|
||||||
|
серверным правилом; клиент воспринимает ключ как непрозрачную строку.
|
||||||
|
Общий Ledger endpoint не приобретает income-specific поле.
|
||||||
|
|
||||||
|
Income VM группирует entries по полученному ключу. Отсутствующий, пустой или
|
||||||
|
нестроковый ключ, неоднозначные instrument keys, неизвестная группа, другая
|
||||||
|
валюта или несовпадение receipt count приводят к существующему локальному
|
||||||
|
error/retry состоянию аналитики. Частичный drill-down не показывается как
|
||||||
|
полный; формула идентификации в клиенте не восстанавливается. Полностью пустой
|
||||||
|
ответ допустим. Состав и порядок выплат, signed Money и сортировка breakdown
|
||||||
|
сохраняются, frontend не пересчитывает денежные итоги.
|
||||||
|
|
||||||
|
## Совместимость и локальное обновление
|
||||||
|
|
||||||
|
Сначала обновляется backend: дополнительное поле совместимо с прежним
|
||||||
|
frontend. Затем синхронизируются OpenAPI snapshot/generated types и обновляется
|
||||||
|
frontend consumer. При переключении клиент перезагружается, чтобы не переносить
|
||||||
|
старые in-memory results и незавершённые запросы. Новый frontend требует новый
|
||||||
|
backend; отсутствие membership не скрывается fallback на прежнюю формулу.
|
||||||
|
|
||||||
|
При регрессии сначала возвращается прежний frontend вместе с соответствующим
|
||||||
|
snapshot/types и перезагружается клиент; затем при необходимости возвращается
|
||||||
|
backend. Старый frontend может временно работать с новым additive response.
|
||||||
|
SQLite schema, credentials и broker-reported операции не меняются: rollback
|
||||||
|
не удаляет данные, производные результаты запрашиваются заново.
|
||||||
|
|
||||||
## Прогноз и ограничения
|
## Прогноз и ограничения
|
||||||
|
|
||||||
Блок прогноза переиспользует `/events`: его `incomeTotalsByCurrency` содержит только coupon/dividend с известной projected amount, а на UI отображаются не более трёх ближайших таких событий. Maturity, offer и неизвестные суммы не искажают totals. Прогноз не гарантирован, не входит в фактические итоги и не зависит от выбранного исторического периода; его loading/error/empty outcome локален для блока.
|
Блок прогноза переиспользует `/events`: его `incomeTotalsByCurrency` содержит только coupon/dividend с известной projected amount, а на UI отображаются не более трёх ближайших таких событий. Maturity, offer и неизвестные суммы не искажают totals. Прогноз не гарантирован, не входит в фактические итоги и не зависит от выбранного исторического периода; его loading/error/empty outcome локален для блока.
|
||||||
|
|||||||
@@ -31,6 +31,11 @@ sequenceDiagram
|
|||||||
данные и не является интерактивным; error и empty outcomes остаются отдельными
|
данные и не является интерактивным; error и empty outcomes остаются отдельными
|
||||||
состояниями после завершения запроса.
|
состояниями после завершения запроса.
|
||||||
|
|
||||||
|
Page VM владеет account route input, authenticated entity query, выбором
|
||||||
|
loading/error/empty/loaded и retry. Container вызывает один VM и передаёт
|
||||||
|
данные существующим state views. Query без account ID остаётся отключённым;
|
||||||
|
выделение VM не меняет порядок выбора состояний или поведение фонового refetch.
|
||||||
|
|
||||||
- Период включает текущую календарную дату и следующие 30 дней.
|
- Период включает текущую календарную дату и следующие 30 дней.
|
||||||
- Суммы до налогов рассчитываются по текущему количеству бумаг на `calculatedAt`; они не учитывают будущие сделки или владение на дату реестра.
|
- Суммы до налогов рассчитываются по текущему количеству бумаг на `calculatedAt`; они не учитывают будущие сделки или владение на дату реестра.
|
||||||
- Список не имеет фильтров, пагинации, конвертации валют и общего итога.
|
- Список не имеет фильтров, пагинации, конвертации валют и общего итога.
|
||||||
|
|||||||
+1
-1
@@ -6,7 +6,7 @@
|
|||||||
"node": "22.x"
|
"node": "22.x"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lint": "markdownlint-cli2 \"**/*.md\" \"#node_modules\" \"#audits/2026-08-26-wealth-ledger-sdd/*-result.md\" \"#audits/2026-08-26-wealth-ledger-sdd-v2-review/*-audit.md\" \"#audits/codex-sessions-token-audit-2026-08-26.md\"",
|
"lint": "markdownlint-cli2 \"**/*.md\" \"#node_modules\" \"#audits/2026-08-26-wealth-ledger-sdd/*-result.md\" \"#audits/2026-08-26-wealth-ledger-sdd-v2-review/*-audit.md\" \"#audits/codex-sessions-token-audit-2026-08-26.md\" \"#audits/2026-08-frontend-repository-audits/frontend-repository-audit-2026-08-28.md\"",
|
||||||
"verify": "npm run lint && git diff --check"
|
"verify": "npm run lint && git diff --check"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
Reference in New Issue
Block a user