Переносит архив `audits/2026-10-11/` (19 файлов) из codex/audit-remediation-2026-10-11 в develop и добавляет точечный allowlist в .gitignore.
100 KiB
Аудит 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
- Факт: FSD-граф работоспособен, масштабный перенос слоёв не нужен. Проверенный analyzer не выявил циклов, запрещённых направлений и обходов public API. Реальные неудобства сосредоточены в потерях данных на границе VM/view, а не в количестве slices. Полезные owners защищены FE-008–FE-011.
- Факт: основной портфель теряет source metadata, analytics не включает её в loaded API. Время и статус результата оказываются недоступны пользователю при сохранённых денежных значениях — FE-001.
- Факт: сохранение snapshot реализовано лучше, чем сообщение о его неуспешном перечитывании. Investment VMs игнорируют feedback общего screen hook; section view скрывает ошибку при наличии data. Пользователь не получает предупреждение/retry — FE-002. Банковский список при cold error может вообще вернуть пустой вывод — FE-004.
- Подтверждённый риск исполнения: ручной timer обходит query readiness. После его срока возможен поиск короче минимума либо запрос дочернего ресурса с пустым UID. Focus policy также противоречит сохранённому portfolio requirement. Это FE-003; частота и стоимость запросов не измерены.
- Факт: Fieldset не блокирует auth-ссылку. Во время submit остаётся переход на другую auth-форму, что нарушает local pending contract и позволяет потерять контекст отправки — FE-005. Утечки identity этим наблюдением не доказаны: session guards сохраняются.
- Непроверенные решения отделены от дефектов.
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, TypeScript5.9.3, Vite8.3.0, React Router8.3.1. TanStack Query5.102.8, Mantine9.6.1, RHF7.88.0, Zod4.6.4, openapi-fetch0.17.0, openapi-react-query0.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), ноisFetchinginput не используется. Это не основание удалять 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). ChartNumberиспользуется для визуальной шкалы, точная таблица сохраняет 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и docsarchitecture/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,AccountsPagequiet 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.
FE-005 — Auth Link остаётся активным во время отправки
-
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 separatedata-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 с existingt; 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.
observecompatibility оценивать отдельно по callers, не удаляя access projection вместе с ним. - Before / after API:
useResourceQuery(generatedOptions)→ тот же API;commitTbankConnectionBoundary(client, confirmedStatus)→ тот же success-only commit. GET/refetch returns envelope,.dataprojection сохраняет его отличие от 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
- FE-001: передать уже вычисленный
sourceв AccountPageLoaded и сохранить metadata в empty branch. Самый маленький production wiring fix; existing route assertions должны начать проверять source time/attempt вместе с sums. - FE-003: добавить enabled/fetching guard в общий manual refetch owner; проверить disabled search и unresolved UID через fake timers/MSW.
- FE-005: блокировать auth Link отдельно от native Fieldset; deferred submit + keyboard recovery proof.
Несколько owners / тестовое подтверждение
- FE-002 + остальная часть FE-001: explicit source/client feedback в investment loaded models и section recovery. Первым representative account, затем analytics и detail sections; не переносить fetching во view.
- FE-004: локальные cold/cached error/retry branches для cards/history/ summary с existing BankFeedback; сохранить независимость успешных sections.
- 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
Вопросы, действительно меняющие решение
- CURRENCY visibility (FE-006): скрывается обычный баланс брокера или самостоятельная contract position? Разрешить один конфликт tests/spec на synthetic Portfolio с currency-only и currency+cash. До ответа KEEP filter.
- Refresh semantics (FE-003): «30 минут» — только периодический read либо minimum для focus/reconnect/mount? Для portfolio focus уже запрещён spec; остаётся выбор policy других resources и hidden tab. Не ставить 30-minute staleTime всем queries автоматически.
- Multiple observers: Accounts hook используется page и breadcrumb. Размещая native interval, какой observer owns periodic screen read? Самый дешёвый proof — isolated Query wrapper с двумя observers и fake timers.
- Screener deep links: нужен ли возврат к searchId/criteria/page после browser back/reload? Сейчас local snapshot связен и соответствует button Search UX. Без этого требования миграция в URL/Query — лишняя работа.
- 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 |