Files
main/audits/2026-10-11/reports/05-frontend-architecture-report.md
T
codex-bot d07fbfabb1 docs: archive technical audit 2026-10-11 in develop (#36)
Переносит архив `audits/2026-10-11/` (19 файлов) из codex/audit-remediation-2026-10-11 в develop и добавляет точечный allowlist в .gitignore.
2026-10-17 19:38:03 +03:00

100 KiB
Raw Blame History

Аудит 05 — Frontend Architecture

1. Audit metadata

Поле Значение
Audit ID / name 05 / Frontend Architecture
Дата 2026-10-11
Исполнитель Один независимый агент; дополнительные сабагенты не запускались
Workspace /Users/ksv741/Project/vibe/WealthLedger
Режим Исходники/config четырёх repo и root read-only; единственная запись — этот новый отчёт
Scope React/TypeScript UI architecture, ownership, VM/view, Query/state, формы, presentation/recovery, routing/accessibility
Источник задания audit-prompts/05-frontend-architecture.md, прочитан целиком
Предыдущий аудит audit-results/technical-audit-2026-10-11.md прочитан как реестр гипотез; каждый использованный механизм проверен заново

Репозитории и ревизии

В каждом checkout самостоятельно выполнены git rev-parse HEAD и git status --short --untracked-files=all до исследования.

Repo Абсолютный путь Точный HEAD Начальный status
root /Users/ksv741/Project/vibe/WealthLedger dc030f84c979cedb2628ef4bd97c62d6c63a9256 Пустой
frontend /Users/ksv741/Project/vibe/WealthLedger/frontend c8821200ce699dab1ce65360d1968d5877a2e78b Пустой
backend /Users/ksv741/Project/vibe/WealthLedger/backend 0ad84ff4f0b4712abc7e69dc299856f14af6eadd Пустой
specs /Users/ksv741/Project/vibe/WealthLedger/specs fcdc805f6c1b3be6a20a7e858e3e80cbd83a9121 Пустой
docs /Users/ksv741/Project/vibe/WealthLedger/docs e6fd4f64b81dd73b33959b845a1babcfac1891c8 Пустой

Перед чтением каждого nested repo прочитан его AGENTS.md. Для frontend прочитаны engineering, architecture, typescript-react, ui, data, testing; для backend — engineering и HTTP API, поскольку использовался только явно предоставленный публичный artifact. Specs выбраны через AI_INDEX.md и точечный поиск основных behavioral specs; docs — через README и тематические страницы. Backend application internals здесь не исследовались.

Skills: первым загружен и применён установленный ponytail-audit. Его complexity-pass отделён от проверки correctness: абстракция сама по себе не является дефектом. Также применены feature-sliced-design с reference layer-structure, vercel-react-best-practices, web-design-guidelines, graphify и verification-before-completion. В root и frontend не найден существующий graphify-out/graph.json. Graphify pipeline, генерация графа и вспомогательные artifacts не выполнялись; владельцы и связи прослежены по исходникам.

Фактически выполненные проверки

Команда / действие Результат и граница доказательства
git rev-parse HEAD && git status --short --untracked-files=all, cwd каждого из пяти repo SHA выше; все начальные деревья чистые
git diff --no-index --stat -- "/Users/ksv741/Project/vibe/WealthLedger/backend/openapi.json" "/Users/ksv741/Project/vibe/WealthLedger/frontend/src/shared/api/spec/openapi.json" Exit 0, вывода нет: два имеющихся artifact совпадают; свежесть относительно runtime не проверена
nvm use --silent && ./node_modules/.bin/depcruise src packages --config dependency-cruiser.cjs --output-type err-long, cwd frontend Первая попытка остановилась до Node: nvm: command not found
source "/Users/ksv741/.nvm/nvm.sh" && nvm use --silent && ./node_modules/.bin/depcruise src packages --config dependency-cruiser.cjs --output-type err-long, cwd frontend Exit 0: no dependency violations found (420 modules, 1773 dependencies cruised); прямой статический analyzer, без npm wrapper, запускающего build
Read / Glob / Grep Source, callers, выбранные tests, schemas/config и behavioral requirements; абсолютные repo paths
Context7 /tanstack/query Проверены manual refetch при enabled: false, stale/focus semantics и встроенное polling
Context7 /websites/mantine_dev Проверена граница Fieldset disabled и Button, отображаемого ссылкой
WebFetch официальных Web Interface Guidelines Использованы как checklist, без автоматического превращения каждого guideline в finding

Официальные источники библиотечных фактов: TanStack disabled queries, TanStack window focus, Mantine Fieldset, Mantine Button: disabled links. Context7 вернул документацию текущей ветки, а не гарантированный snapshot именно установленных minor versions; runtime-проверка этих versions не выполнялась. Guideline checklist получен из официального источника.

Application tests не запускались: для выводов достаточно детерминированных source branches и прочитанных existing scenarios. Ссылки на тесты ниже означают просмотр сценария, не свежий зелёный test run. Install, build, codegen, migrations, full-suite/verify, запуск приложения, финансовые API, benchmark и profiler не выполнялись. Отчёты аудитов 06/07 не читались. .gitignore, index, commits и PR не изменялись.

Финальный контроль: отчёт перечитан; git diff --no-index --check /dev/null "audit-results/05-frontend-architecture-report.md" и git diff --check выполнены; HEAD/status всех пяти repo повторно сверены. Отчёт находится в ignored audit-results, поэтому чистый root status сам по себе не доказывает содержимое отчёта: новый файл проверен отдельно.

Ссылки на code evidence далее имеют формат repo/relative-path:lines и относятся строго к указанным SHA. High — высокая уверенность в механизме по коду, не заявление о runtime-воспроизведении. Runtime impact и частота сценариев не измерены.

2. Executive verdict

  1. Факт: FSD-граф работоспособен, масштабный перенос слоёв не нужен. Проверенный analyzer не выявил циклов, запрещённых направлений и обходов public API. Реальные неудобства сосредоточены в потерях данных на границе VM/view, а не в количестве slices. Полезные owners защищены FE-008–FE-011.
  2. Факт: основной портфель теряет source metadata, analytics не включает её в loaded API. Время и статус результата оказываются недоступны пользователю при сохранённых денежных значениях — FE-001.
  3. Факт: сохранение snapshot реализовано лучше, чем сообщение о его неуспешном перечитывании. Investment VMs игнорируют feedback общего screen hook; section view скрывает ошибку при наличии data. Пользователь не получает предупреждение/retry — FE-002. Банковский список при cold error может вообще вернуть пустой вывод — FE-004.
  4. Подтверждённый риск исполнения: ручной timer обходит query readiness. После его срока возможен поиск короче минимума либо запрос дочернего ресурса с пустым UID. Focus policy также противоречит сохранённому portfolio requirement. Это FE-003; частота и стоимость запросов не измерены.
  5. Факт: Fieldset не блокирует auth-ссылку. Во время submit остаётся переход на другую auth-форму, что нарушает local pending contract и позволяет потерять контекст отправки — FE-005. Утечки identity этим наблюдением не доказаны: session guards сохраняются.
  6. Непроверенные решения отделены от дефектов. CURRENCY намеренно скрывается в UI, хотя основной spec требует доступности иных позиций: FE-006 — INVESTIGATE, пока не разрешён конфликт продуктового решения. Длинные банковские списки/графики — potential bottleneck, FE-012, UNVERIFIED, а не установленная performance regression.

Counts: actionable FIX/SIMPLIFY — 6: P0 0, P1 0, P2 5, P3 1. Отдельно KEEP 4, INVESTIGATE 2. FE-006 и FE-012 не включены в подтверждённые дефекты. FE-007 — необязательное упрощение P3, не пользовательский сбой. P1 не присвоен: серьёзный эксплуатационный или дорогой системный сценарий в этом audit scope не подтверждён.

3. Inventory & coverage

3.1. Фактическое устройство и метод подсчёта

  • SPA: React 19.3.0, TypeScript 5.9.3, Vite 8.3.0, React Router 8.3.1. TanStack Query 5.102.8, Mantine 9.6.1, RHF 7.88.0, Zod 4.6.4, openapi-fetch 0.17.0, openapi-react-query 0.5.4 — direct versions из frontend/package.json:39–62, не результат проверки runtime deployment.
  • React Compiler включён в frontend/vite.config.ts:9–11; Vitest имеет отдельный React/JSDOM profile (frontend/vitest.config.ts:11–34). Это не доказательство оптимальности каждого render.
  • По непосредственным directory entries frontend/src: 15 page slices, 8 entity slices, 4 feature slices, 1 widget slice; по frontend/packages — 3 private packages (icons, tokens, ui). Files/LOC этим методом не считались. Assets/test не интерпретировались как FSD slices.
  • 420 modules / 1773 dependencies — точный вывод единственного успешного dependency-cruiser run по src packages и действующему config. Это размер его графа, не число production components и не семантическая оценка ownership.
  • 8 production callers useScreenSync установлены поиском символа по frontend/src и чтением VM: accounts, account, operations, events, income, bonds, bond, stock. Tests/index исключены из этого счёта.
  • Публичные query keys образуются generated queryOptions; banking types выводятся из paths (frontend/src/entities/bank-card/api/types.ts:1–34). API snapshot совпал с предоставленным backend artifact, но генерация schema.d.ts заново не выполнялась.

3.2. Шесть end-to-end use cases

Эти трассы включают UI route, фактических owners и shared transport. Видимые состояния установлены по source и выбранным RTL/MSW сценариям; browser render/keyboard interaction в этой сессии не воспроизводились.

Use case Route → page → widget/feature → entity → shared API Success / loading / error / stale / empty
U1. Accounts и account/portfolio, включая all AppRoutes.tsx:194–210 → AccountsPage / AccountPage VM → PortfolioStructure, useScreenSync → brokerage-account, portfolio, broker-operation, brokerage-event, income → useResourceQuery → useAuthenticatedQuery → generated client Structural skeleton; complete empty без вымышленных нулей; loaded группирует позиции и preview sections; access failure локален. Query snapshot сохраняется. Main source props теряются, transport feedback отсутствует: FE-001/002. Позиции CURRENCY скрываются: FE-006
U2. Instrument search AppRoutes.tsx:158–165 → BondsPage → instrument-search (q, debounce, results/card), useScreenSync → bond/useBondSearch → useResourceQuery → generated GET /bonds URL хранит q, trim + debounce 300 ms, автоматический query только с двух символов. Пустой успешный массив различим от отсутствующего result; labels и ссылки семантические. Cached error тихий; initial error ведёт даже при temporary failure в settings, без retry. Timer нарушает минимальную длину: FE-002/003
U3. Bonds/stocks: detail и скринер AppRoutes.tsx:167–192 → BondPage / StockPage VM → instrument-details sections/chart/history + useScreenSync → bond/stock, broker-operation history → resource query/client. /stocks → StocksPage VM → generated POST search / GET search page напрямую через shared apiClient UID/ticker canonical navigation сохраняет search; child queries ждут UID; sections независимы. Position без accounts скрыта; quote time и stale position видимы; complete empty не равен missing. Sections теряют cached-error warning/retry. Screener хранит единственный локальный snapshot результата и requestId, показывает asOf/error/empty и блокирует native controls; это не второй Query cache
U4. Banking: journal + create/edit/refund AppRoutes.tsx:140–156 → CardsPage / CardPage VM → page-owned OperationForm/DeleteOperation и entity BankCardForm/CategoryManager → bank-card queries/mutations/money/date → authenticated generated client Card/history/summary независимы; URL owns period/page/limit; placeholder предыдущей страницы только в той же card/range/limit, edit на ней запрещён. Form validation, pending, conflict draft и intent replay сохранены. Cold list error может дать blank area; retry отсутствует у части read states, cached failures тихие: FE-004
U5. Login/session/connection AppRoutes.tsx:109–138, RouteGuards → LoginPage/RegisterPage → auth form/provider VM; settings → tbank-connection → shared client/session-aware query Neutral bootstrap skeleton; один сериализованный refresh, один auth replay; logout очищает cache даже до ответа сети. Ошибки формы сохраняют ввод; email normalized, password untouched. Fieldset блокирует поля, но не Link: FE-005. Успешная connection mutation сбрасывает investment subtree; failed mutation сохраняет его
U6. Income analytics как сложный URL/form/data flow AppRoutes.tsx:221–228 → AccountIncomePage VM → page-local IncomePeriodForm/summary/comparisons/payments + useScreenSync → income query → resource/authenticated/generated API Applied period в URL, draft в RHF; validation по календарю/Москве; resetRevision отдельно сбрасывает calendar UI. Новый uncached period не переименовывает старые строки, показывает skeleton; partial hides totals; previous/year dependency notices независимы. Source metadata и transport warning/retry в loaded API отсутствуют: FE-001/002

В U3 намеренная dependency identifier → UID → child queries нужна для точного инструмента; её нельзя назвать лишней waterfall без изменения contract. Детальные request counts и server fan-out относятся к 07.

3.3. UI Ownership Matrix

UI / responsibility Фактический owner Проверенные consumers / public API Оценка
Providers, lazy routes, auth gates, route error reset app/ui/App.tsx, app/routes App → page slice index; ProtectedLayout → app/ui public API KEEP: глобальная навигация не спрятана в shared
Portfolio state selection и позиции/previews pages/account AccountPage → VM → entity APIs → loaded/empty views; AccountPage.vm.ts:66–136 KEEP owner; FIX потери source и recovery, не перенос fetching во view
Структура портфеля/валюта/unknown/negative widgets/portfolio-structure Loaded и Empty account views; PortfolioStructure.tsx:9–12 Понятная существующая граница; перенос из-за общего FSD discouragement widgets не оправдан
Query keys, DTO projection, access groups entities/*/api + shared/api portfolio key используется тестами/consumers; generated option identity KEEP; HTTP infrastructure не дублируется per page
Account breadcrumb scope app/ui/AppBreadcrumbs/AccountScopeSwitch useBrokerageAccounts, route section/search preservation; VM :8–36 KEEP; повторный observer использует тот же key, не собственный cache
Instrument search input/results features/instrument-search Текущий production consumer BondsPage; useInstrumentSearch.ts:8–22 Небольшой coherent owner; single consumer сам по себе не основание переносить slice
Общие chart/position/history sections features/instrument-details BondPage и StockPage; narrow structural VM types model/types.ts:7–39 KEEP: реальный reuse двух screens; section recovery расширяется здесь
Stocks criteria/search snapshot pages/stocks StocksPage VM → view; public shared apiClient KEEP local ownership; запрос не скрыт в presentation hook; URL persistence — открытый product question
Banking queries/types/feedback/money/card form entities/bank-card CardsPage, CardPage, OperationForm, CategoryManager, BankCardForm; index :1–30 KEEP для согласованных money, card и conflict responsibilities
Transfer/refund/edit orchestration pages/card/ui/OperationForm CardPage :21–27; RHF context fields и entity APIs KEEP page-local feature; не дробить по числу props
Analytics period, draft, grouping/comparisons pages/account-income VM → own form/views → income API KEEP: бизнес-проекция отличается от wire DTO
Periodic investment reads features/tbank-connection/model/screen-sync.ts 8 production page VMs; scope descriptors сейчас игнорируются FE-003: readiness/policy у query-owner, а interval обходной
Source text + busy/entry primitives shared/ui/SourceSyncStatus, packages/ui Accounts/Account/Operations/Events; bank/history, skeletons KEEP semantic wrappers; ответственность шире нового имени для Mantine
Pagination labels stocks StocksPagePresentation.vm.ts Единственный StocksPage.view FE-007: необязательная дополнительная navigation hop

FSD checks: frontend/dependency-cruiser.cjs:30–75 задаёт public API, same-layer и upward restrictions. Успешный graph gate подтверждает эти настроенные проверки; не доказывает правильность любого бизнес-owner. Не обнаружено actual propagation-of-change, требующего общего god slice, нового state manager, обязательного @x или удаления entities слоя.

3.4. State Ownership Matrix

State Source of truth Secondary copy Consequence
Investment data/meta/query error/fetch status TanStack Query envelope useResourceQuery возвращает projection, VM вычисляет presentation Второго data store нет; FE-001/002 возникают при выбрасывании полей projection
30-minute refresh schedule Сейчас собственный setInterval Query отдельно owns stale/focus/reconnect + HTTP retry Два policy owners; readiness не соблюдается ручным refetch — FE-003
Accounts для breadcrumb и page Один generated Query key Второй observer, не копия data KEEP; не превращать в custom global context
Instrument q, period, event filter Router search params Debounced trim q; computed categories Обоснованная временная debounce projection; parameters переживают canonical route
Ledger applied filters URL Draft range/categories в React state Обоснованное Apply поведение; scope change сбрасывает draft и cursors
Brokerage cursors useBrokerageOperationHistory scope-bound local pagination Нет копии rows Оpaque cursor остаётся API-owned; invalid cursor сбрасывается к первой странице
Income applied period URL normalized period RHF draft DD.MM.YYYY, resetRevision/calendar state Draft и committed state различаются по смыслу; form не должен refetch при каждой правке
Screener result/page/asOf Один page-local page snapshot Нет второго Query result Императивный search owner допустим; requestId препятствует stale completion; перенос в Query сам по себе не нужен
Screener criteria Local React draft Результат от предыдущего search сохраняется отдельно Не меняет старый список до Search; reload/back persistence не обещана отдельной spec
Bank card/history/summary TanStack Query History placeholder предыдущей страницы в том же scope Не новый cache; readonly editing guard защищает placeholder identity
Bank operation/card draft RHF SubmittedValues, intent, adopted version, latest record Это replay/conflict workflow, не ненужная копия server state; FE-010 KEEP
Modal open, purchase page, trigger focus Ближайший component/presentation hook Refs для trigger/scroll, не data cache Связный UI lifecycle; CardPagePresentation.vm.ts:18–53 выполняет layout read в effect, не в render
Auth identity/token AuthProvider VM; token только ref в памяти Shared transport session snapshot содержит readiness/handlers; userRef нужен current closure KEEP single-flight/generation/cache clearing; не переносить token в storage
Connection status Query + successful mutation response Нет локальной копии финансовых datasets Boundary commit/reset только при успехе; error сохраняет cache
PortfolioStructure currency Local выбранная валюта Selected derived из доступного structure Fallback к первой валюте вычисляемый; неполная/negative оценка остаётся явной

3.5. Query Policy Inventory

Общее наследование: frontend/src/app/ui/App.tsx:13–20 задаёт refetchOnWindowFocus: true, refetchOnReconnect: true, retry: false. Если строка ниже не переопределяет policy, действует это наследование. staleTime — время stale eligibility, не rate limiter. Не-auth GET дополнительно имеет transport recovery: frontend/src/shared/api/client.ts:127–151,172–194,205–249: deadline 15 000 ms, задержки 1000/2000 ms, permanent code exclusions и AbortSignal. Эти числа прочитаны из constants/branches; это не измеренный request budget. Auth endpoints и non-GET исключены из этого GET retry.

Use case / query-owner enabled / key inputs staleTime Interval / refresh Invalidation / recovery
Accounts / useBrokerageAccounts.ts:3–18 Has session; generated GET accounts key 1 800 000 ms Screen timer; ещё observers в breadcrumb/account Connection subtree reset, auth clear, accessGroup brokerage; cached transport feedback теряется
Portfolio / usePortfolio.ts:11–25 Session AND accountId; path accountId 30 000 ms Screen timer; ручной query.refetch доступен инфраструктуре, page result hook игнорируется Access-group cancellation/clear; сохраняется data при temporary failure
Account preview operations / useBrokerageOperations.ts:26–65 Session AND accountId; normalized categories, cursor, limit/from/to/instrument 30 000 ms Account/ledger/detail screen timers Connection/access reset; history scope/cursor recovery у entity
Events / useBrokerageEvents.ts:10–23 Session AND accountId, from/to 30 000 ms Account/events screen timers Access-group reset; partial/empty видимы, cached transport error не передан
Summary/analytics / useIncome.ts:14–45 Session AND accountId; analytics from/to 30 000 ms Account/income screen timers Same financial boundary; comparison dependencies из envelope, не отдельный client validation
Bond search / bond/api/queries.ts:97–104 Session AND query.length >= 2; trimmed debounced query 30 000 ms Bonds screen timer вызывает refetch даже disabled query Resource без brokerage accessGroup; общая connection mutation всё равно reset bonds subtree
Bond/stock detail and child resources / соответствующие api/queries.ts Session AND identifier; child session AND UID; candles also period 30 000 ms Detail screen timer over all resources; UID-empty child всё ещё включён в массив Personal position/coupons/dividends и operations помечены accessGroup; connection reset охватывает весь stocks/bonds GET subtree
Stocks screener / StocksPage.vm.ts:29–101 Handler Search/page; apiClient, не useQuery Не применяется Новый POST только по действию; GET page императивный, requestId fence Локальный snapshot/error/pending; GET transport и session generation guards остаются; local state исчезает при unmount
Bank card/cards/categories/expenses/operation / bank-card/api/queries.ts:28–73 Session; item/expenses дополнительно id; cards includeArchived в key Не задано: Query default 0 Focus/reconnect/mount; собственного screen timer нет Bank mutation invalidates банковское GET subtree; session clear отдельно
Bank journal / bank-card/api/queries.ts:76–145 Session AND id/from/to; key range/limit/page; all-mode отдельный suffix Не задано: 0 All-mode последовательное чтение страниц с одним signal; page-mode conditional placeholder Refetch/invalidation inherited; нет UI retry у read failure; FE-004
Bank summary / bank-card/api/queries.ts:147–156 Session; cardId/from/to Не задано: 0 Query select сортирует exact cents; busy передан в view Mutation invalidation и session clear; error только без data, без retry
Connection / TbankConnectionSection.vm.ts:5–22 Session, generated status key Не задано: 0 Cached progress/error/retry явно переданы Успех PUT/DELETE коммитит status и reset investment subtree; неуспех не reset
Auth / auth.vm.ts:81–151 Cold-start refresh; serial auth requests; memory session Не Query cache Single-flight refresh + serialized cookie-writing operations clearSession → generation increment/queryClient.clear; смена identity reset active/remove inactive

Тестовый QueryClient выключает Query retries (frontend/src/test/query.tsx:15–20), но не отражает автоматически все production focus/timer/transport policies. observe не вызывается production UI по найденным callers; вопрос удаления совместимости передан 04, не раскрывался здесь как самостоятельный dead-code audit.

3.6. Render, forms, accessibility и contracts: результат покрытия

  • Rendering: позиции вычисляются в page model, chart data/table — в presentation VM. Child VM types принимают narrow structural contracts, даже если caller передаёт весь vm object. Оснований заменить их universal component или добавить memo каждому компоненту не установлено. Большие all-mode/candle arrays отмечены только FE-012.
  • Keys: проверены account id, instrument UID, operation id/account pair, candle time, category id, group key. Есть occurrence keys у income entries (model/display.ts:101–117), что защищает повторяющиеся identifiers; logoName входит в некоторые keys. Remount cost/регрессия не измерены, массовое изменение keys не рекомендовано.
  • Effects: screen timer имеет cleanup, resourcesRef обновляется effect; его _scope и resource queryKey не управляют timer. Initial availability имеет budget 10 000 ms и identity (shared/lib/initial-availability.ts:18–46), но isFetching input не используется. Это не основание удалять bounded initial-wait UX: hook не является polling source. URL/form reset effects и cursor recovery выполняют внешнюю синхронизацию, а не второй data cache.
  • Forms: RHF + resolver; factories учитывают актуальные cards/categories, refund limit и explicit adopted version. Нормализация amount/email/date имеет разные владельцы и разные инварианты; общий universal normalizer не нужен. Серверная validation/authorization остаётся обязательной.
  • Accessibility: семантические Link/Button, именованные action icons, labels/field errors, busy skeleton, pagination labels, modal focus return, keyboard-focusable price table, reduced motion theme проверены по source. Подтверждённый pending-link gap — FE-005. Наличие main landmark не доказывает route focus/skip-navigation experience: screen reader, contrast, responsive overflow и реальный keyboard path UNVERIFIED без browser session.
  • Contract: типы data/request выводятся из generated paths; unknown money отличается от zero, formatMoney сохраняет decimal строку (shared/lib/display.ts:21–61). Chart Number используется для визуальной шкалы, точная таблица сохраняет original close. ApiErrorCode — subset открытого wire code с fallback (shared/api/problem-details.ts:19–28), не runtime validator всей schema. Malformed runtime payload не обещает гарантированной локальной recovery: protected route ErrorBoundary — последний safe fallback.

Ограничения репрезентативности: изучены все route definitions и выбранные end-to-end owners, не каждый JSX/CSS/test во frontend. Невыполненные browser и measurement checks не скрыты за успешным dependency gate. Backend execution, DRY cross-repo, systemic dead code, качество всех assertions, CI/tooling и load efficiency находятся вне scope; их сигналы перечислены в разделе 8.

4. Findings

FE-001 — Source metadata теряется в API финансового view

  • Decision: FIX.

  • Severity: P2.

  • Confidence: High.

  • Evidence: frontend/src/pages/account/ui/AccountPage/AccountPage.vm.ts:46–50,125–135; frontend/src/pages/account/ui/AccountPage/AccountPage.tsx:13–34; frontend/src/pages/account/ui/AccountPage/components/AccountPageLoaded.view.tsx:28–38,55–59; frontend/src/pages/account-income/ui/AccountIncomePage/AccountIncomePage.vm.ts:24–39,110–119; frontend/src/pages/account-income/ui/AccountIncomePage/components/AccountIncomePageLoaded.view.tsx:36–70.

  • Claim: у account container уже вычисленный source не передаётся view; у analytics loaded model source отсутствует целиком. Source warning/time нельзя восстановить из сохранённых денежных rows без envelope.

  • Current mechanism: route вызывает page VM → resource query отдаёт data/meta → VM account возвращает source → container явно перечисляет props без source. Empty account branch также не несёт metadata. Income VM использует meta для partial/comparison notices, но выбрасывает freshness/time/attempt/availability. Portfolio.isStale на основном экране не читается отдельным consumer.

  • Impact / actual cost: old complete snapshot после failed hourly attempt выглядит как обычный результат; отсутствует контекст происхождения сумм. Impact unmeasured: частота старых snapshots и пользовательские ошибки интерпретации не измерены. Прямой propagation defect, а не новый API requirement.

  • Why now: информация уже опубликована и частично отображается на соседних screens. Specs frontend-data-resilience/spec.md:11–28 и portfolio-view/spec.md:121–158 сохраняют явный source context; полезность правила здесь конкретна — не выдавать старые суммы за новое наблюдение.

  • Minimum change: передать account source; включить источник в loaded/empty финансовое presentation API; income использует тот же существующий status renderer с page-owned completeness/availability wording. Не вводить client timestamps вместо server lastSuccessfulAt.

  • Before / after API:

    // До: source вычислен, но потерян.
    <AccountPageLoaded portfolio={vm.portfolio} positionSections={vm.positionSections} />
    // После: остальные props сохраняются, source проходит boundary.
    <AccountPageLoaded portfolio={vm.portfolio} positionSections={vm.positionSections} source={vm.source} />
    // Income loaded VM: добавить source: analytics.meta;
    // view получает source, а не useIncomeAnalytics внутри view.
    
  • Guarantees at risk: нельзя путать server stale, failed hourly attempt и client transport failure; data/null/complete empty, деньги/валюты и исходное время сохраняются. Для существующих responses meta гарантирована contract, optional compatibility props можно оставить там, где нужны fixtures.

  • Counterarguments / trade-offs: тихий экран может быть намеренной UX политикой: AccountPage.test.tsx:803–855 явно ожидает отсутствие source time. Это доказательство действующего поведения, но не его полезности. Соседний Accounts view уже показывает SourceSyncStatus; время не требует нового слоя.

  • Verification or rollback: RTL/MSW current account route с full data и failed/partial attempt; source timestamp неизменен, sums остаются; empty data не становится missing. Изменить silent expectations в существующем test, добавить income source presentation case. Проверять view props и route, не только helper. Rollback только presentation diff, Query policies не трогать.

  • Effort: M (account wiring S; полный scope включает income/empty states).

  • Related finding IDs: FE-002, FE-008, FE-009, FE-011.

FE-002 — Investment view contract теряет transport recovery

  • Decision: FIX.

  • Severity: P2.

  • Confidence: High.

  • Evidence: frontend/src/features/tbank-connection/model/screen-sync.ts:37–61; frontend/src/pages/account/ui/AccountPage/AccountPage.vm.ts:78–84; frontend/src/pages/accounts/ui/AccountsPage/AccountsPage.vm.ts:21–58; frontend/src/pages/account-income/ui/AccountIncomePage/AccountIncomePage.vm.ts:66–69,110–119; frontend/src/features/instrument-details/model/section.ts:14–27; frontend/src/features/instrument-details/ui/InstrumentSection.view.tsx:22–28,76–84; frontend/src/pages/bonds/ui/BondsPage/BondsPage.vm.ts:17–20,27–39.

  • Claim: data сохранена, но cached transport error/progress/retry не доходит до пользовательской boundary. InstrumentSection даже полученный error скрывает при hasData; нет локального retry API.

  • Current mechanism: page VMs вызывают useScreenSync, не сохраняя его результат. Loaded unions не содержат client feedback. section сводит HTTP failure и source failure в boolean; view Alert имеет guard !hasData. Search boolean error тоже требует отсутствия data. Operations/events передают source meta, но не client failed-read state. Initial generic Account/Accounts error (AccountPageError.view.tsx:46–50, AccountsPageError.view.tsx:41–45) не имеет retry; income error view также только StateView.

  • Impact / actual cost: неуспешное перечитывание неразличимо от штатного snapshot; восстановление требует внешней navigation/focus/timer вместо действия затронутой section. Impact unmeasured. Наличие data предотвращает разрушение экрана, но не выполняет recovery contract.

  • Why now: существующий status UI и Query.refetch уже достаточны. frontend-data-resilience/spec.md:18–24, UI convention :133–146 и docs architecture/cross-cutting/ui-resilience.md:19–35 требуют двух различимых состояний именно для сохранённых финансовых результатов.

  • Minimum change: page VM отдаёт client feedback (refreshing, refreshError, retry), view показывает неблокирующее предупреждение и read-only retry. SectionState раздельно несёт source и transport status; retry относится к failed query/section. Access failures сохраняют отдельный navigation-only путь. Использовать Mantine Alert/Button и существующую presentation семантику, не universal global error store.

  • Before / after API:

    // До:
    type SectionState = { error: boolean; hasData: boolean; loading: boolean; partial?: boolean };
    // После: оставшиеся поля сохраняются.
    type SectionFeedback = {
      refreshError: boolean; refreshing: boolean;
      sourceFailed: boolean; retry?: () => void;
    };
    // Page/section VM собирает feedback из query; view получает named props.
    // section error с hasData => warning + data, без data => local error + retry.
    
  • Guarantees at risk: не скрывать data при transient error; не показывать чужой session/connection snapshot; retry — только HTTP read, не manual import. Не перемещать query hook во view. Invalid cursor сначала восстанавливает scope через existing history owner, а не повторяет бесконечно тот же cursor.

  • Counterarguments / trade-offs: отсутствие кнопки manual source sync оправданно; это не запрет повторить HTTP. Quiet behavior действительно закреплён AccountPage.test.tsx:751–855, AccountsPage quiet tests и StockPage.test.tsx:392–407,449–471; tests не заменяют behavioral решение. Source warning из FE-001 не объясняет transport error, поскольку HTTP мог не доставить новое meta вовсе. Не следует показывать дублирующиеся alerts.

  • Verification or rollback: existing route RTL/MSW: initial temporary fail, cached fail, slow refetch, successful retry, independent failed company section, invalid cursor и confirmed access loss. Проверять завершение failed response, сохранённые суммы/draft и доступную кнопку, а не private callback count. Изменяются ожидаемые visible outcomes, не весь test stack.

  • Effort: M.

  • Related finding IDs: FE-001, FE-003, FE-004, FE-008, FE-011.

FE-003 — Screen timer обходит enabled, policy разбита между owners

  • Decision: FIX (с native simplification после behavior proof).

  • Severity: P2.

  • Confidence: High.

  • Evidence: frontend/src/features/tbank-connection/model/screen-sync.ts:7–35,39–50; frontend/src/pages/bonds/ui/BondsPage/BondsPage.vm.ts:9–13; frontend/src/entities/bond/api/queries.ts:97–104; frontend/src/pages/stock/ui/StockPage/StockPage.vm.ts:23–55; frontend/src/pages/bond/ui/BondPage/BondPage.vm.ts:23–55; frontend/src/shared/api/authenticated-query.ts:42–50; frontend/src/app/ui/App.tsx:13–20.

  • Claim: timer вызывает manual refetch каждого resource, не проверяя isEnabled/isFetching. Disabled Query защищает автоматическое fetching, но не ручной refetch. Это подтверждено текущей TanStack documentation.

  • Current mechanism: 30-minute setInterval, scope descriptor игнорируется, latest resources лежат в ref. ScreenResource не содержит isEnabled, хотя useResourceQuery возвращает его (resource-query.ts:132–146). При пустом bond query endpoint всё равно получает request; при неразрешённом UID все dependent observers существуют с uid: "", timer их принудительно читает. Отдельно stale portfolio после 30 секунд допускает focus refetch из App, хотя portfolio-view/spec.md:121–124 запрещает focus-triggered refetch.

  • Impact / actual cost: invalid-input GET, ложные local errors при ожидании prerequisite, чтение в другой частоте, чем ожидает policy owner. Impact unmeasured: traffic/latency/API load не считались. StaleTime не превращает ручной timer в readiness-aware schedule.

  • Why now: root-cause fix в одном общем владельце защищает все восемь callers. Потерянные scope inputs создают необходимость поддерживать descriptors, не влияющие на execution.

  • Minimum change: сначала фильтровать enabled и уже fetching resources во всех manual refresh/retry/timer ветках. Focus policy выбрать явно у investment query owner, не менять banking только ради одного правила. Следующий компактный вариант — встроенный refetchInterval у query owner, с polling только у нужного screen observer; убрать собственный timer и неиспользуемый scope. Проверить observers Accounts/breadcrumb, прежде чем включать interval во всех экземплярах одного hook.

  • Before / after API:

    // Минимальная коррекция existing API:
    type ScreenResource = { isEnabled: boolean; isFetching: boolean; refetch: () => Promise<unknown> /* existing feedback fields */ };
    // resources.filter(r => r.isEnabled && !r.isFetching) перед manual refetch.
    // Native destination, policy находится в entity query options:
    useResourceQuery({ ...options, enabled: ready, refetchInterval: READ_INTERVAL_MS,
      refetchIntervalInBackground: false, refetchOnWindowFocus: false });
    // Вместо useScreenSync({ unused scope fields }, [resources]).
    
  • Guarantees at risk: session-aware enabled, AbortSignal, canonical keys, cached snapshot, interval stop on unmount и read-only backend semantics. Native background/focus defaults меняют нынешнюю работу hidden tab; нельзя выдать такое изменение за чисто механический refactor.

  • Counterarguments / trade-offs: текущий timer объединяет ресурсы одного экрана и может быть осознанной политикой hidden tab. Query polling без observer ownership способен размножить timers для одного key. Поэтому immediate readiness guard подтверждён, а полное native replacement требует узкого proof; новый scheduler framework не нужен. Reconnect policy не объявляется дефектом автоматически: product meaning minimum interval надо согласовать.

  • Verification or rollback: fake timers + existing screen-sync test boundary: пустой/однобуквенный поиск, unresolved ticker, позднее получение UID, unmount, cached refetch failure, два observers одного key, focus/reconnect. MSW должен подтвердить отсутствие invalid request, а не только call helper. Existing screen-sync.test.tsx:21–80 проверяет ручные reads, но не disabled interval. Rollback native phase к исправленному enabled-aware timer.

  • Effort: M (immediate shared guard S).

  • Related finding IDs: FE-002, FE-008, FE-011, FE-012.

FE-004 — Banking read-state API не обеспечивает локальный recovery

  • Decision: FIX.

  • Severity: P2.

  • Confidence: High.

  • Evidence: frontend/src/pages/cards/ui/CardsPage/CardsPage.vm.ts:15–45; frontend/src/pages/cards/ui/CardsPage/CardsPage.view.tsx:31–77; frontend/src/pages/card/ui/CardPage/CardPage.vm.ts:95–118,140–145; frontend/src/pages/card/ui/CardPage/CardPage.view.tsx:52–83,133–142,177–186; frontend/src/pages/card/ui/CardPage/CardHistory.view.tsx:38–123; frontend/src/entities/bank-card/ui/BankSummary/BankSummary.view.tsx:17–23,84–125.

  • Claim: initial cards failure без data/loading возвращает null, не error state. Card/history/summary не дают retry для соответствующего read; cached card failure fields computed в VM не используются view, history и summary warnings подавлены при наличии data.

  • Current mechanism: CardsPageViewCards обрабатывает только items и isLoading, затем null. CardPage initial error предлагает только /cards. History возвращает rows раньше анализа failure; outer message имеет history.error && !rows. Summary error также требует !data и имеет только текст. VM summary type не передаёт error object/retry. Existing BankFeedback уже принимает cached, loadingError, retry.

  • Impact / actual cost: blank cards area после ошибки похожа на отсутствие результата UI; пользователь не может адресно восстановить journal/summary. Сохранённый баланс не становится нулём — это полезная гарантия, но ошибка чтения остаётся скрытой. Impact unmeasured.

  • Why now: specs/openspec/specs/manual-bank-cards/spec.md:631–665 прямо различает empty/error и требует repeat. Это необходимо для ручного учёта, не generic preference к количеству Alert components.

  • Minimum change: query owner остаётся bank-card; page VM отдаёт section feedback и named retries. CardsPage добавляет error branch и cached warning; CardPage/BankSummaryView используют BankFeedback либо простой локальный Alert/Button. Ошибка одной section не убирает успешные card/history/summary.

  • Before / after API:

    // До: summary: { data, error: boolean, fetching, loading }.
    // После, без смены query key/transport:
    summary: { data: summary.data, error: summary.error,
      fetching: summary.isFetching, loading: summary.isLoading,
      retry: () => void summary.refetch() }
    // Cards branch: !data && isError => BankFeedback(loadingError, retry).
    // Cached branch: data + error => rows и BankFeedback(cached, retry).
    
  • Guarantees at risk: точный balance из backend, запрет optimistic success, independence от T-Bank, pagination scope, disabled edit на placeholder rows, drafts/version/intent сохраняются. NotFound не должен предлагать повтор записи/создание дубля; recovery повторяет только GET.

  • Counterarguments / trade-offs: summary failure уже отображает текст без data, и prerequisites cardsFeedback существует. Finding не утверждает, что все banking errors потеряны. Отсутствие alert при cached failure закреплено CardPage.test.tsx:315–341; после исправления нужны новые ожидаемые outcomes. Invalidation всего банковского subtree пока сохраняется.

  • Verification or rollback: RTL/MSW по существующим CardsPage/CardPage и BankSummary tests: cold cards failure, failure only summary/history, cached failures, GET retry success, сохранение balance, draft и current URL. Для placeholder rows оставить текущий edit guard. Rollback presentation wiring, не write protections. Не создавать общий banking error manager.

  • Effort: M.

  • Related finding IDs: FE-002, FE-009, FE-010, FE-011.

  • Decision: FIX.

  • Severity: P2.

  • Confidence: High.

  • Evidence: frontend/src/features/auth/ui/AuthForm/AuthForm.tsx:26–45,51–84; frontend/src/features/auth/ui/LoginForm/LoginForm.tsx:6–9; frontend/src/features/auth/ui/RegisterForm/RegisterForm.tsx:6–9; frontend/src/features/auth/model/auth.vm.ts:121–133; specs/openspec/specs/user-authentication/spec.md:109–122.

  • Claim: <Fieldset disabled={isSubmitting}> не делает дочерний anchor disabled. Mantine Button с component={Link} здесь не получает отдельного interaction guard, поэтому переключение login/register остаётся доступно.

  • Current mechanism: native fieldset блокирует form controls; React Router Link отображается ссылкой. На Button component={Link} нет disabled data, aria-disabled, tab exclusion или guarded click. Mantine docs явно требуют для link separate data-disabled и предотвращения navigation.

  • Impact / actual cost: пользователь меняет auth route во время request, теряет старую форму/draft/error context; authentication всё ещё завершается в общем provider. Нарушение pending UX подтверждено механизмом; account compromise/утечка не доказаны. Impact unmeasured.

  • Why now: малая локальная правка удовлетворяет уже принятому правилу блокировки всех auth actions и не требует новой auth orchestration.

  • Minimum change: guard navigation while pending, обозначить link недоступным и исключить его из tab order; сохранить настоящую ссылку в обычном состоянии. Handler находится у AuthForm owner.

  • Before / after API:

    // До:
    <Button component={Link} to={to} variant="subtle" />
    // После: onSwitchAuthPage предотвращает переход только при isSubmitting.
    <Button component={Link} to={to} variant="subtle"
      aria-disabled={isSubmitting || undefined}
      data-disabled={isSubmitting || undefined}
      tabIndex={isSubmitting ? -1 : undefined} onClick={onSwitchAuthPage} />
    
  • Guarantees at risk: keyboard и pointer behavior должны совпасть; не заменять navigation div/click; submission error сохраняет ввод; auth queue, generation guard и raw password остаются. Одного CSS pointer-events guard недостаточно для keyboard navigation.

  • Counterarguments / trade-offs: маршрут после успешного auth всё равно redirected, поэтому это локальный UX дефект, а не P1 auth vulnerability. Browser back не обязан блокироваться этим локальным исправлением. Existing AuthForm test (:8–32) проверяет mismatch, не pending navigation.

  • Verification or rollback: deferred onSubmit в existing RTL/MemoryRouter: заполнить valid inputs, submit, попытаться переключить route click/Enter, дождаться failure и проверить сохранение ввода + восстановление ссылки. Browser keyboard check нужен для реальной focusability. Rollback только interaction props/handler.

  • Effort: S.

  • Related finding IDs: FE-010, FE-011.

FE-006 — Видимость CURRENCY: подтверждённое скрытие, спорное намерение

  • Decision: INVESTIGATE.
  • Severity: P2 (потенциальная потеря видимости позиции, не подтверждённый дефект продукта).
  • Confidence: Medium; механизм исключения High, нужность изменения UNVERIFIED.
  • Evidence: frontend/src/pages/account/model/positions.ts:70–94,136–160; frontend/src/pages/account/ui/AccountPage/AccountPage.vm.ts:109–135; frontend/src/pages/account/ui/AccountPage/components/PositionSection.view.tsx:14–39; frontend/src/pages/account/model/positions.test.ts:15–69; frontend/src/pages/account/ui/AccountPage/AccountPage.test.tsx:287–325,349–394; specs/openspec/specs/portfolio-view/spec.md:22–33,164–169.
  • Claim: CURRENCY не проходит ни stocks, ни bonds, ни other filter. Currency-only portfolio остаётся loaded, но не показывает карточку позиции. Это намеренно закреплено текущими tests, тогда как saved spec различает торгуемый валютный инструмент и Cash Balance и требует доступности иных типов.
  • Current mechanism: единственный production caller модели — account VM, также используемый route /accounts/all. Denominator включает CURRENCY, но sections исключают её; cash view показывает только backend cashBalances.
  • Impact / actual cost: возможна невидимая quantity/valuation позиции, участвующей в sums/shares; не доказано, что реальный upstream в рабочем наборе возвращает самостоятельные currency contracts, которые требуется показывать. Impact unmeasured; product defect UNVERIFIED.
  • Why now: конфликт делает изменение этого model опасным для следующего разработчика: tests и spec дают противоположные answers. Это не повод автоматически принять spec или удалить tests.
  • Minimum change: один product/domain ответ: является ли CURRENCY в публичном Portfolio самостоятельной строкой, которую UI должен показывать? Сверить безопасный synthetic payload с contract, не ходить в real API. При положительном ответе удалить только exclusion из other predicate; при отрицательном — явно согласовать behavioral spec с UI policy.
  • Before / after API: API формы sections не меняется. Возможное изменение после решения: other = !STOCK && !BOND && !CURRENCY → other = !STOCK && !BOND. cashBalances и totalsByCurrency не пересчитывать.
  • Guarantees at risk: currency instrument ≠ cash balance, никакой FX конвертации и двойного суммирования; server totals authoritative; order, unknown values и all-account source identity сохраняются.
  • Counterarguments / trade-offs: tests прямо защищают отдельный cash block и скрытие валютных instruments; это сильный признак принятого product choice. Нельзя назвать это случайной опечаткой. Но cash block не показывает quantity торгуемой позиции и сам по себе не объясняет скрытие contract row.
  • Verification or rollback: после решения изменить существующий positions test и route test: CURRENCY-only, CURRENCY + cash same currency, all-account rows, отсутствие double-added totals. До решения production predicate и tests сохранять. Rollback локального filter, без API migration.
  • Effort: S после ответа; до ответа правка не рекомендована.
  • Related finding IDs: FE-008, FE-010.

FE-007 — Малый presentation VM добавляет hop без самостоятельной orchestration

  • Decision: SIMPLIFY.
  • Severity: P3.
  • Confidence: High для scope/caller, не измеренная экономия.
  • Evidence: frontend/src/pages/stocks/ui/StocksPage/StocksPagePresentation.vm.ts:1–20; frontend/src/pages/stocks/ui/StocksPage/StocksPage.view.tsx:205–208,259–268.
  • Claim: hook только получает t и возвращает два Pagination label callback; sole consumer находится рядом и уже получает t. Это optional yagni: кандидат, не основание отменять MVVM или presentation hooks вообще.
  • Current mechanism: view → extra hook → useTranslation → callbacks; нет query, form context, local state/effect или независимого lifecycle.
  • Impact / actual cost: дополнительный файл/переход при изменении двух доступных labels. Impact unmeasured: agent tokens/maintenance time и render overhead не измерены; runtime benefit не заявлен.
  • Why now: только при ближайшем изменении Pagination. Самостоятельная cleanup задача имеет низкую ценность относительно FE-001–FE-005.
  • Minimum change: callbacks рядом с Pagination/view либо небольшой page-local presentation component, если этого требует текущий lint profile. Не создавать shared pagination label abstraction ради одного caller.
  • Before / after API: const { getControlProps, getItemProps } = useStocksPagePresentationVm() → local named callbacks с existing t; public <StocksPageView vm={vm}> и Pagination props не меняются.
  • Guarantees at risk: сохранить translated control/page names, typing ComponentProps<typeof Pagination>, keyboard semantics и отсутствие data hook во view. Локальный lint может потребовать узкого согласованного exception.
  • Counterarguments / trade-offs: hook даёт единый файл-layout по convention, и это небольшая цена. CardPagePresentation.vm.ts:18–73 имеет реальный scroll/focus/layout lifecycle и не является аналогом для удаления.
  • Verification or rollback: existing StocksPage page-control RTL scenario StocksPage.test.tsx:127–172; имена/переходы одинаковы. Отдельный новый passthrough test не нужен. Rollback локального placement, без Query changes.
  • Effort: S.
  • Related finding IDs: FE-008, FE-009.

FE-008 — KEEP полезные page VMs и coherent presentation owners

  • Decision: KEEP.
  • Severity: P3 (защищаемый механизм; не дефект).
  • Confidence: High.
  • Evidence: frontend/src/pages/account/ui/AccountPage/AccountPage.vm.ts:66–136; frontend/src/pages/account-income/ui/AccountIncomePage/AccountIncomePage.vm.ts:41–119; frontend/src/pages/card/ui/CardPage/CardPage.vm.ts:21–158; frontend/src/pages/stock/ui/StockPage/StockPage.vm.ts:23–98; frontend/src/pages/card/ui/CardPage/CardPagePresentation.vm.ts:18–73.
  • Claim: эти VMs реально owns route/data/form/draft/grouping/recovery orchestration. Удаление файла перенесёт связную сложность в view, а не уберёт её.
  • Current mechanism: page container выбирает state; VM объединяет resources и derived sections; views получают props. Income separates URL commit и RHF draft. Card presentation VM сохраняет scroll position при paging.
  • Impact / actual cost: исправлять недостающие projection fields локально проще, чем менять все view consumers. Экономия от удаления not measured, оправданная ответственность подтверждена конкретными callers.
  • Why now: complexity audit легко ошибочно отождествляет VM с boilerplate; FE-007 не должен превратиться в массовое объединение файлов.
  • Minimum change: сохранить boundaries; расширить их только source/client feedback из FE-001/002/004. Не вводить дополнительный generic VM framework.
  • Before / after API: AccountPage → useAccountPageViewModel → state view остаётся тем же; после исправлений добавляются explicit source/feedback props, query hooks остаются внутри owner VM.
  • Guarantees at risk: hook ordering, current closures, grouping by source account, independent sections, URL/draft separation, scroll preservation.
  • Counterarguments / trade-offs: props/файлов больше, чем в монолитном компоненте. Это приемлемо для перечисленных обязанностей; single caller и размер view не доказывают необходимость ни split, ни merge.
  • Verification or rollback: существующие route/form/pagination RTL scenarios остаются boundary проверки; browser scroll proof требуется для изменения Card presentation lifecycle. Никакой новой проверки «существует VM».
  • Effort: S (сохранить owner при локальных правках).
  • Related finding IDs: FE-001, FE-002, FE-003, FE-006, FE-007.

FE-009 — KEEP semantic Mantine wrappers и package foundation

  • Decision: KEEP.
  • Severity: P3 (не дефект).
  • Confidence: High.
  • Evidence: frontend/packages/ui/src/EntryCard/EntryCard.tsx:7–80; frontend/packages/ui/src/LoadingSkeleton/LoadingSkeleton.tsx:5–16; frontend/packages/ui/src/StateView/StateView.tsx:5–25; frontend/src/shared/ui/SourceSyncStatus/SourceSyncStatus.tsx:13–47; frontend/packages/ui/src/theme.ts:27–75; frontend/src/pages/card/ui/CardPage/CardHistory.view.tsx:58–102.
  • Claim: wrappers имеют project presentation responsibility: entry semantics/actions/tone, accessible busy region, safe state shell и исходное время/attempt text. Это не повторная реализация Mantine Button/Modal.
  • Current mechanism: Mantine controls и theme используются напрямую; shared package предоставляет low-level presentation, переводы и retry принадлежат callers. Form bodies используют Fieldset/Modal, не custom widgets.
  • Impact / actual cost: удаление wrappers размножит проверенные status/entry структуры и пропустит busy/unknown/tone semantics. Benefit not measured.
  • Why now: minimal code не должен сокращаться за счёт accessibility и единого смысла financial display. FE-001/004 можно решить существующими parts.
  • Minimum change: сохранить wrappers, расширять только недостающий feedback в существующем owner. Не строить новую design system поверх Mantine.
  • Before / after API: <LoadingSkeleton label={t(key)}>{shapes}</LoadingSkeleton> и <EntryCard title metadata value valueLabel tone actions /> остаются без изменений; page source/retry props добавляются у callers по необходимости.
  • Guarantees at risk: semantic busy state, decorative media, explicit money currency/sign/unknown, reduced motion и owned modal exit/focus lifecycle.
  • Counterarguments / trade-offs: SourceSyncStatus знает generated resource metadata в shared; это reuse инфраструктурного envelope, а не financial calculation. Перенос только ради буквального запрета domain names не имеет подтверждённой пользы. Малый wrapper не автоматически лишний.
  • Verification or rollback: existing wrapper stories/RTL и page states; contrast/focus/reduced motion — browser proof при visual изменении. Не добавлять tests, которые лишь повторяют Mantine props.
  • Effort: S.
  • Related finding IDs: FE-001, FE-004, FE-007, FE-010.

FE-010 — KEEP form boundaries, exact money, replay и version adoption

  • Decision: KEEP.
  • Severity: P3 (не дефект; снятие защит создаст другой риск).
  • Confidence: High.
  • Evidence: frontend/src/pages/card/ui/OperationForm/OperationForm.vm.ts:29–81,90–151,192–225; frontend/src/pages/card/ui/OperationForm/OperationForm.schema.ts:46–104,107–131; frontend/src/entities/bank-card/model/money.ts:4–23; frontend/src/entities/bank-card/model/form.schema.ts:8–42; frontend/src/entities/bank-card/ui/BankCardForm/BankCardForm.vm.ts:18–45,50–115; frontend/src/features/auth/model/auth.schema.ts:23–57.
  • Claim: local version/intent/submittedValues/latest/refundLimit не являются лишним Query cache. Это отдельный подтверждённый workflow, который нельзя заменить «всегда бери свежую версию из query» или Number money input.
  • Current mechanism: RHF draft сохраняется при error; stable UUID intention повторяется после потерянного create response; adopted version меняется только явным действием; same submitted values могут дойти до server replay даже после изменения source. Amount/date и refund validation имеют boundary schemas, а query invalidates related bank subtree.
  • Impact / actual cost: защищает от дублирования намерения и молчаливого принятия чужой версии. Pure money parsing выполняется bigint, transport amount остаётся decimal string. Benefit not measured, инварианты не заменяют доказательство backend transaction/replay — это scope 06/13.
  • Why now: именно эта сложность легко выглядит как state duplication при mechanical simplification. Existing tests OperationForm.test.tsx:154–227, 228–324,404–479 описывают lost response/refund/version behavior.
  • Minimum change: KEEP; исправления read feedback не меняют form intent, explicit adoption, positive/range checks, raw password или server validation.
  • Before / after API: useOperationFormVm({ card, cards, operation, onClose }) → тот же API; POST { id: intent, amount: { currency: "RUB", value } } и PATCH { ...body, version } неизменны. Currency/date normalization не объединяется с email/password policy.
  • Guarantees at risk: exact cents/ranges, no rounding, changed-intent conflict, refund bounds, server-side ownership/atomicity, password untouched, draft retention, disabled controls и focus return.
  • Counterarguments / trade-offs: frontend checks могут устареть между validation и mutation. Это причина сохранить серверную проверку, не убрать frontend UX validation. Explicit version adoption намеренно не подменяет draft свежим record, хотя требует state и дополнительных действий.
  • Verification or rollback: current financial form scenarios — нужная regression boundary; extra numeric edge cases у existing pure tests, configured backend tests относятся владельцу 06/13. Не оценивать atomicity mock form тестом. Rollback simplification при потере любого перечисленного invariant; сейчас simplification не предлагается.
  • Effort: S.
  • Related finding IDs: FE-004, FE-005, FE-006, FE-009, FE-011.

FE-011 — KEEP generated API и session/connection reuse boundaries

  • Decision: KEEP.
  • Severity: P3 (не дефект).
  • Confidence: High для source mechanisms; fresh runtime proof не выполнялся.
  • Evidence: frontend/src/shared/api/authenticated-query.ts:34–58; frontend/src/shared/api/client.ts:53–119,284–290; frontend/src/features/auth/model/auth.vm.ts:41–115,121–151; frontend/src/features/tbank-connection/model/connection-boundary.ts:11–39; frontend/src/features/tbank-connection/ui/TbankConnectionSection/TbankConnectionSectionLoaded.vm.ts:32–58; frontend/src/shared/api/resource-query.ts:37–67,99–102,129–149.
  • Claim: authenticated/resource wrappers не являются бессмысленными passthrough: session enabling, access loss cleanup, metadata projection и confirmed connection reset защищают reuse финансовых данных.
  • Current mechanism: generated queryFn/options/key; protected enabled содержит session snapshot; auth requests serialized/single-flight, client проверяет generation после HTTP/refresh. Logout clears cache; connection mutation reset GET brokerage/stocks/bonds only on success. Access-group failure cancel + clear data, а ordinary temporary failure сохраняет snapshot.
  • Impact / actual cost: снижает риск late-response/identity reuse и конкурирующих auth loops. Benefit not measured; crypto/server auth safety отдельно не проверялась. Один shared transport вместо отдельных clients подтверждён прослеженными consumers.
  • Why now: native simplification FE-003 должна использовать эти boundaries, не обходить их прямым fetch. Совпадение artifacts не оправдывает ручные DTO.
  • Minimum change: KEEP; никаких localStorage token stores, URL-wide request registries, hash validators или дополнительных epochs. observe compatibility оценивать отдельно по callers, не удаляя access projection вместе с ним.
  • Before / after API: useResourceQuery(generatedOptions) → тот же API; commitTbankConnectionBoundary(client, confirmedStatus) → тот же success-only commit. GET/refetch returns envelope, .data projection сохраняет его отличие от payload.
  • Guarantees at risk: canonical keys, AbortSignal, no previous session data, cold-start neutral UI, one auth retry, failed connection mutation preserves cache, confirmed access loss clears private resource, unknown code fallback.
  • Counterarguments / trade-offs: access loss uses lower-level query.setState; это чувствительная библиотечная точка. Упрощать только после равносильного proof. Existing tests resource-query.test.tsx:174–209, auth.test.tsx:146–187,279–354 и connection boundary scenarios дают полезную boundary, но не объявлены свежим runtime подтверждением в этом аудите.
  • Verification or rollback: existing auth/client/resource/connection MSW suites при любом touched boundary; late response + identity/connection switch, failure without reset, logout network failure. Не требовать нового global state manager или cross-tab locking: spec допускает re-login другой вкладки.
  • Effort: S.
  • Related finding IDs: FE-001, FE-002, FE-003, FE-005, FE-010.

FE-012 — Длинные all-mode lists и candle tables: potential bottleneck

  • Decision: INVESTIGATE.
  • Severity: P3.
  • Confidence: Low для практической значимости; структура проходов подтверждена.
  • Evidence: frontend/src/entities/bank-card/api/queries.ts:120–143; frontend/src/pages/card/ui/CardPage/CardPage.vm.ts:56–93; frontend/src/pages/card/ui/CardPage/CardHistory.view.tsx:38–105; frontend/src/features/instrument-details/ui/InstrumentChart/InstrumentChart.vm.ts:13–37; frontend/src/features/instrument-details/ui/InstrumentChart/InstrumentChart.view.tsx:94–153; frontend/vite.config.ts:9–11.
  • Claim: all journal mode materializes all returned rows, then maps them в presentation, chart/table model проходит все candles. Реального slow render, memory pressure или INP regression UNVERIFIED.
  • Current mechanism: sequential pages + accumulated items in one Query; view строит каждый EntryCard. Chart creates numeric series and exact reversed priceRows; ScrollArea ограничивает viewport, но не количество rows.
  • Impact / actual cost: potential CPU/DOM/memory cost на длинной истории; Impact unmeasured. Ни число обычных операций, ни length candles, ни commit time не измерены. Перенос на virtualization пока не оправдан.
  • Why now: только если пользователь/безопасный profiler подтвердит лаг на выбранном all-mode или all-history. Не считать guideline threshold универсальным финансовым требованием.
  • Minimum change: сначала один synthetic representative profile. При установленной render стоимости ограничить presentation/использовать native content visibility там, где подходит; более сложную virtualization добавлять только при недостаточности native решения и сохранённой accessibility.
  • Before / after API: сейчас и до измерения useBankOperations(...limit: "all") / <InstrumentChart vm={vm}> неизменны. Возможный after меняет internal row renderer/window, не DTO, query identity и точную таблицу.
  • Guarantees at risk: полнота выбранного списка, stable IDs, keyboard search/ navigation/scroll, доступность точных money values и целостность source scope.
  • Counterarguments / trade-offs: наборы данных могут быть небольшими; browser/React Compiler могут сделать текущую реализацию достаточной. Paging/default page limit уже ограничивает обычный journal. Chart/table служит доступной альтернативой изображению: удалять таблицу ради ускорения нельзя.
  • Verification or rollback: safe synthetic browser profile, зафиксировать fixture size, viewport и commit/interaction evidence; после изменения сравнить тот же scenario и keyboard/table availability. Request timing/counts — 07. При отсутствии измеримого выигрыша оставить нынешний API/renderer.
  • Effort: M для measurement и возможного narrow fix.
  • Related finding IDs: FE-003, FE-008, FE-009.

5. DELETE / SIMPLIFY / KEEP / INVESTIGATE

Decision IDs / область Решение и сохраняемый смысл
DELETE Нет подтверждённых standalone кандидатов в scope 05 Не удалять entity/feature/widget из-за одного caller или имени. observe — сигнал 04
SIMPLIFY FE-007 yagni: убрать отдельный label-only presentation hook при ближайшем touched Pagination; labels сохранить
FIX + native destination FE-003 native: сначала enabled-aware refetch, затем рассмотреть Query interval у owner; сохранить screen observer policy
FIX FE-001, FE-002, FE-004, FE-005 Source/transport/read recovery и pending link; исправления в existing boundaries
KEEP FE-008 Полезные page VMs, page-local composition, portfolio widget, Card scroll presentation hook
KEEP FE-009 Semantic Mantine wrappers, theme, skeleton/entry/state parts
KEEP FE-010 RHF/Zod, decimal/bigint, stable intent, submitted replay, explicit version adoption, серверная validation
KEEP FE-011 Generated DTO/key identity, auth single-flight/queue/generation, connection/access cache fences
INVESTIGATE FE-006 Явное product решение видимости валютной позиции; source behavior установлен, defect status UNVERIFIED
INVESTIGATE FE-012 Render hot spots только после measurement

Complexity-pass ponytail-audit: net: not measured; dependency removals: none established. Оценка строк/процентов не выдумывалась; prepared application diff отсутствует. Correctness fixes могут добавить несколько presentation props, и это необходимая информация, а не нарушение принципа minimal complexity.

6. Cost & benefit

Область Ожидаемый класс benefit Фактическое измерение Цена / обязательные regression boundaries
FE-001 source wiring Reliability и понятность financial result; меньше diagnostic ambiguity not measured Account/analytics/empty source presentation; не менять timestamp/money/availability semantics
FE-002/004 recovery Maintenance: один явный section feedback contract; UX восстановления not measured Temporary vs access/source errors, cached/empty/missing, draft preservation, GET-only retry
FE-003 readiness/native polling Runtime: исключить неподготовленные GET; cognitive load: один policy owner not measured; сокращение запросов не оценивалось Disabled/UID/focus/reconnect/background/unmount и несколько observers; тестовая сложность M
FE-005 pending link Reliability/keyboard UX, малая стоимость local handler not measured Pointer + keyboard navigation, failure retains draft, restored link
FE-007 local labels Agent navigation/cognitive load not measured Сохранённые a11y names; возможное lint convention решение; выполнять opportunistically
FE-008–011 KEEP Сохранение доказуемых responsibilities и reuse guards not measured Не обещать экономию CI minutes или security без исполнения соответствующих checks
FE-006 visibility Potential financial explainability not measured / intended behavior UNVERIFIED Сначала contract/product answer; валютный инструмент не считать cash, totals не пересчитывать
FE-012 rendering Potential runtime CPU/DOM/memory not measured / bottleneck UNVERIFIED Same fixture profiling; exact values, stable keys и keyboard table availability

Нет основания добавлять новую dependency, store, transport client, global VM framework или набор генераторов. Текущий installed stack покрывает предлагаемые изменения. Экономия runtime/CI/agent tokens численно не оценивалась.

7. Top actions

Только подтверждённые изменения; 6 действий, не обязательство реализовать всё за один PR. INVESTIGATE вынесены в verification plan.

Quick wins

  1. FE-001: передать уже вычисленный source в AccountPageLoaded и сохранить metadata в empty branch. Самый маленький production wiring fix; existing route assertions должны начать проверять source time/attempt вместе с sums.
  2. FE-003: добавить enabled/fetching guard в общий manual refetch owner; проверить disabled search и unresolved UID через fake timers/MSW.
  3. FE-005: блокировать auth Link отдельно от native Fieldset; deferred submit + keyboard recovery proof.

Несколько owners / тестовое подтверждение

  1. FE-002 + остальная часть FE-001: explicit source/client feedback в investment loaded models и section recovery. Первым representative account, затем analytics и detail sections; не переносить fetching во view.
  2. FE-004: локальные cold/cached error/retry branches для cards/history/ summary с existing BankFeedback; сохранить независимость успешных sections.
  3. FE-003: согласовать investment focus/reconnect/background policy и перейти к native polling, если narrow proof нескольких observers подтвердит равносильность. Краткое изменение существующего решения/ADR оправдано только при изменении semantics hidden tab/minimum interval, не для readiness guard.

FE-007 не входит в обязательные top actions: малый cleanup только при следующем изменении Pagination. KEEP не превращаются в новые work items.

8. Cross-audit signals

Направление Короткий сигнал, files и рекомендуемый audit ID
06 Backend Architecture Frontend принимает password minimum в UTF-8 bytes (frontend/src/features/auth/model/auth.schema.ts:36–42), artifact содержит CredentialsDto.minLength/maxLength (backend/openapi.json:5239–5250, совпадает с frontend snapshot). Сверить runtime validators с specs/.../user-authentication/spec.md:10; runtime mismatch здесь UNVERIFIED
06 Backend Architecture UI recovery требует HTTP-only read/retry guarantee; сверить источники /brokerage-accounts/.../income-* и historical operations/candles с принятой границей docs/adr/019-hourly-reads-and-online-instrument-reference.md:9–29. Backend execution в этом аудите не проверялось
07 API/Data Flow Efficiency screen-sync.ts:28–32, App.tsx:13–20, bond/stock query policies: измерить реальный request budget focus/reconnect/timer; FE-003 доказывает readiness bypass, а не нагрузочную метрику
07 API/Data Flow Efficiency bank-card/api/queries.ts:120–139 all-mode последовательно собирает страницы; InstrumentChart.vm.ts:13–37 работает с полным candle result. Оценить payload/GET budget отдельно от render hypothesis FE-012
07 API/Data Flow Efficiency BondsPage.view.tsx:27–109 показывает nominal/couponFrequency/flags/quote/classCode/ISIN; использовать эти actual UI consumers при оценке стоимости online search enrichment, не объявлять detail поля ненужными по внешнему виду карточки
04 Dead Code / Legacy resource-query.ts:69–127 observe/observationKey найден только в resource-query tests, production consumers не найдены; InvestmentRefreshState найден в stories/test catalog, не page composition. Проверить compatibility/export scope прежде удаления
08 Test Quality Quiet/missing-warning expectations: AccountPage.test.tsx:751–855, CardPage.test.tsx:315–341, AccountIncomePage.test.tsx:618–649; поправки visible contract требуют пересмотра assertions. StocksPage.test.tsx:175–237 late response completion barrier — вопрос 08, без самостоятельного test audit
15 Specs / Documentation FE-006: explicit tests скрывают CURRENCY, portfolio-view/spec.md:22–33,164–169 говорит другое. Stocks/bonds docs содержат старые source/manual-refresh описания вместе с добавленным Online reference (stocks.md:149–169,194–199; bonds.md:105–134,147–151)
13 Reliability / Security Form intent/version и session/connection fences FE-010/011 должны входить в security/atomicity regression boundaries; подтверждение backend CAS/replay/transactions здесь не выполнялось

Эти сигналы не являются findings аудитов 06/07 и не входят в severity counts.

9. Open questions / verification plan

Вопросы, действительно меняющие решение

  1. CURRENCY visibility (FE-006): скрывается обычный баланс брокера или самостоятельная contract position? Разрешить один конфликт tests/spec на synthetic Portfolio с currency-only и currency+cash. До ответа KEEP filter.
  2. Refresh semantics (FE-003): «30 минут» — только периодический read либо minimum для focus/reconnect/mount? Для portfolio focus уже запрещён spec; остаётся выбор policy других resources и hidden tab. Не ставить 30-minute staleTime всем queries автоматически.
  3. Multiple observers: Accounts hook используется page и breadcrumb. Размещая native interval, какой observer owns periodic screen read? Самый дешёвый proof — isolated Query wrapper с двумя observers и fake timers.
  4. Screener deep links: нужен ли возврат к searchId/criteria/page после browser back/reload? Сейчас local snapshot связен и соответствует button Search UX. Без этого требования миграция в URL/Query — лишняя работа.
  5. Real render scale (FE-012): достаточно ли обычной pagination и текущих chart/table passes? Только synthetic profile с фиксированным размером fixture/viewport меняет решение о memo/virtualization.

Самый дешёвый последовательный verification plan

Шаг Boundary и ожидаемое доказательство Команда / ограничение
V1 Source metadata проходит реальную VM/container/view цепочку; complete empty, failed attempt и original time различимы После будущей реализации существующие AccountPage/AccountIncomePage RTL/MSW cases; не только helper unit
V2 Failed background GET оставляет суммы/draft, показывает warning, retry доставляет новый success; source stale остаётся отдельно Existing page tests с deferred response и ожиданием actual settled failure; permanent access failure очищает data
V3 Disabled/min-length/UID-empty observers не выполняют manual periodic GET; interval stops after unmount Existing screen-sync boundary расширить fake timers + MSW; focus/reconnect отдельно; production app не нужен
V4 Cold banking error не blank/empty; cards/history/summary independently retry; balance exact Existing CardsPage/CardPage/BankSummary RTL/MSW tests; same card/range/limit placeholder edit prohibition сохраняется
V5 Auth route не меняется click/Enter во время submit, после failure сохраняется ввод и ссылка активна Existing AuthForm MemoryRouter scenario с deferred promise; narrow browser keyboard proof для focusability
V6 Form/identity protections остаются при touched changes Targeted existing OperationForm, client, auth, resource-query, TbankConnectionSection tests; mutation/access/source boundaries не подменять mocks реального backend
V7 Accessibility/visual states Synthetic narrow/wide browser check без real API: focus return, labels/live status, contrast, table scrolling, reduced motion. Сейчас UNVERIFIED
V8 Performance hypothesis меняет решение только после evidence Один synthetic profile FE-012, не production benchmark и не full network audit; при отсутствии лагов не добавлять memo/virtualization

Перед будущими Node-командами — local nvm use --silent. Стандартный npm run test:run -- <path> в текущем manifest запускает package build; его нельзя считать разрешённой read-only командой этой сессии. Поэтому test commands выше — план для implementation phase, не якобы выполненная verification. Для application change владелец обязан выбрать local gates; этот отчёт не заменяет их.

Итог read-only и semantic review

Все actionable conclusions привязаны к source branch и actual caller. Защиты money, form replay/version, generated API и session/access reuse сохранены в KEEP. Проверено, что рекомендации не требуют нового shared framework или обязательной cross-repo build dependency. Новые artifacts, кроме этого отчёта, агентом не создавались. Итоговые HEAD совпали с таблицей раздела 1; tracked/staged/unstaged/untracked status всех пяти repo пустой; git diff --check без замечаний. Root ignored report проверен отдельно через no-index whitespace check и полный Read.

Машинно-считываемая таблица для консолидации

ID Decision Severity Confidence Repo Files One-line impact Effort Related IDs
FE-001 FIX P2 High frontend src/pages/account/ui/AccountPage/AccountPage.tsx; src/pages/account/ui/AccountPage/AccountPage.vm.ts; src/pages/account-income/ui/AccountIncomePage/AccountIncomePage.vm.ts Source metadata теряется перед financial view M FE-002, FE-008, FE-009, FE-011
FE-002 FIX P2 High frontend src/features/instrument-details/model/section.ts; src/features/instrument-details/ui/InstrumentSection.view.tsx; src/pages/account/ui/AccountPage/AccountPage.vm.ts; src/pages/accounts/ui/AccountsPage/AccountsPage.vm.ts Cached investment read failure не имеет warning/retry M FE-001, FE-003, FE-004, FE-008, FE-011
FE-003 FIX P2 High frontend src/features/tbank-connection/model/screen-sync.ts; src/pages/bonds/ui/BondsPage/BondsPage.vm.ts; src/entities/bond/api/queries.ts; src/app/ui/App.tsx Timer обходит enabled, focus расходится с portfolio policy M FE-002, FE-008, FE-011, FE-012
FE-004 FIX P2 High frontend src/pages/cards/ui/CardsPage/CardsPage.view.tsx; src/pages/card/ui/CardPage/CardPage.vm.ts; src/pages/card/ui/CardPage/CardPage.view.tsx; src/entities/bank-card/ui/BankSummary/BankSummary.view.tsx Banking cold list error может быть blank, read retry отсутствует M FE-002, FE-009, FE-010, FE-011
FE-005 FIX P2 High frontend src/features/auth/ui/AuthForm/AuthForm.tsx Auth Link остаётся активным во время submit S FE-010, FE-011
FE-006 INVESTIGATE P2 Medium frontend src/pages/account/model/positions.ts; src/pages/account/model/positions.test.ts; src/pages/account/ui/AccountPage/AccountPage.test.tsx CURRENCY скрывается; intended visibility UNVERIFIED S FE-008, FE-010
FE-007 SIMPLIFY P3 High frontend src/pages/stocks/ui/StocksPage/StocksPagePresentation.vm.ts; src/pages/stocks/ui/StocksPage/StocksPage.view.tsx Label-only VM добавляет необязательный navigation hop S FE-008, FE-009
FE-008 KEEP P3 High frontend src/pages/account/ui/AccountPage/AccountPage.vm.ts; src/pages/account-income/ui/AccountIncomePage/AccountIncomePage.vm.ts; src/pages/card/ui/CardPage/CardPagePresentation.vm.ts Page VMs и UI lifecycle имеют реальную orchestration S FE-001, FE-002, FE-003, FE-006, FE-007
FE-009 KEEP P3 High frontend packages/ui/src/EntryCard/EntryCard.tsx; packages/ui/src/LoadingSkeleton/LoadingSkeleton.tsx; packages/ui/src/StateView/StateView.tsx; src/shared/ui/SourceSyncStatus/SourceSyncStatus.tsx Mantine wrappers сохраняют semantic display и busy states S FE-001, FE-004, FE-007, FE-010
FE-010 KEEP P3 High frontend src/pages/card/ui/OperationForm/OperationForm.vm.ts; src/pages/card/ui/OperationForm/OperationForm.schema.ts; src/entities/bank-card/model/money.ts; src/entities/bank-card/ui/BankCardForm/BankCardForm.vm.ts Form state защищает exact money, replay и version adoption S FE-004, FE-005, FE-006, FE-009, FE-011
FE-011 KEEP P3 High frontend src/shared/api/client.ts; src/shared/api/authenticated-query.ts; src/shared/api/resource-query.ts; src/features/auth/model/auth.vm.ts; src/features/tbank-connection/model/connection-boundary.ts Generated/session/connection boundaries защищают reuse данных S FE-001, FE-002, FE-003, FE-005, FE-010
FE-012 INVESTIGATE P3 Low frontend src/entities/bank-card/api/queries.ts; src/pages/card/ui/CardPage/CardHistory.view.tsx; src/features/instrument-details/ui/InstrumentChart/InstrumentChart.vm.ts; src/features/instrument-details/ui/InstrumentChart/InstrumentChart.view.tsx Long-list/candle render cost UNVERIFIED без profile M FE-003, FE-008, FE-009