# 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 | 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.