Files

11 KiB
Raw Permalink Blame History

codify-frontend-view-model-boundary — Design

Context

Frontend уже использует FSD-направление импортов, typed API hooks и page pattern container + ViewModel + view на части finance pages. Однако текущий AccountPage показывает противоречивый пример: container вызывает useAccountPageViewModel, но AccountPage.view.tsx дополнительно вызывает server-state hooks для recent Account Ledger и Future Cash Events, группирует Portfolio Positions по типам, считает display-only доли и содержит много самостоятельных presentation sections в одном файле.

Reference template фиксирует другую семантику: *.tsx без суффикса — container или public component, *.vm.ts — hook, который инкапсулирует данные, действия и state mapping, а *.view.tsx — чистое представление. В текущем frontend docs эта граница описана короче и не проговаривает отрицательные правила, поэтому AI-agent может принять локальный god-view за допустимый pattern.

Ограничения frontend: React/Vite, TanStack Query, Mantine, React Router, i18n через useTranslation, FSD public API checks, canonical gate npm ci && npm run verify под Node 22. Change не вводит новый automatic guardrail: enforcement остаётся docs/review guidance плюс исправленный локальный пример и tests.

Goals / Non-Goals

Goals:

  • Восстановить AccountPage как корректный пример page container, ViewModel и маленьких presentation views.
  • Перенести recent data loading, state selection, Portfolio Position grouping, derived display flags/numbers и callbacks из view layer в ViewModel/container layer.
  • Раздробить account page presentation на именованные соседние components, когда section/state имеет самостоятельный смысл.
  • Уточнить frontend-local docs и frontend/AGENTS.md, чтобы будущие AI agents видели явную границу container/ViewModel/view.
  • Покрыть VM-derived presentation data focused tests и сохранить existing render tests как regression net пользовательского поведения.

Non-Goals:

  • Automatic lint, dependency-cruiser или AST guardrail для VM/view boundary.
  • Нормализация account-operations, account-events, login, register или settings pages.
  • Изменение API hooks signatures, auth-gating pattern или централизованного access-token handling.
  • Изменение backend, HTTP routes, OpenAPI snapshot, generated types, domain docs или product behavior.
  • Визуальный redesign account page.

Decisions

D1. Сохранить template-семантику ролей файлов. AccountPage.tsx остаётся container: он вызывает useAccountPageViewModel() и выбирает top-level state view. AccountPage.vm.ts становится owner данных и derived presentation model: portfolio state, recent ledger/events state, refresh/retry callbacks, position sections и display-only calculations. *.view.tsx и nested view components получают props и рендерят JSX.

Alternative: сделать AccountPage.view.tsx единственным component, который вызывает VM hook. Отклонено: это переворачивает роль *.view.tsx относительно reference template и текущих conventions.

D2. View modules остаются render-only, но не hook-free. Presentation views могут использовать useTranslation, form-context hooks для form views и локальный UI-only state, например image fallback state. Они не вызывают server-state/API hooks, auth hooks, route/navigation hooks или VM hooks, не делают domain grouping и не считают derived presentation values из collection data. Простое форматирование одного переданного значения через существующие formatters остаётся presentation concern.

Alternative: запретить все hooks во view. Отклонено: это вынудит переносить i18n labels и локальный UI fallback state в VM, делая VM слишком dependent от деталей rendering.

D3. VM отдаёт presentation model, а не только raw domain data. ViewModel возвращает данные, уже разделённые на account page sections, вместе с flags, share denominator/result values и recent sections states. View остаётся ответственной за layout, labels, Mantine components, links и final rendering. Это оставляет вычисления тестируемыми без DOM, но не превращает VM в generator готовой разметки или локализованных strings.

D4. Дробить view по смысловым границам. Account page presentation делится на nearby components для heading/refresh state, totals/cash, position sections, position card/logo и recent sections. Критерий split — самостоятельная ответственность и имя, а не только число строк. Если section может быть названа и протестирована/прочитана независимо, она не должна накапливаться внутри одного большого view file.

D5. Docs first, automated enforcement later. Этот change уточняет frontend/docs/UI-PATTERN.md, frontend/docs/CONVENTIONS.md и короткий routing pointer в frontend/AGENTS.md. Hard checker не добавляется, потому что сейчас нет утверждённого design для надёжного AST rule, исключений и maintenance cost. Rule остаётся review guidance; corrected AccountPage и tests уменьшают риск повтора.

D6. Tests фиксируют новую поверхность ответственности. Новые focused tests проверяют ViewModel-derived presentation data: секции позиций, display-only доли, recent ledger/events state и retry/loading/error propagation. Existing render tests сохраняют observable account page behavior, включая stale snapshot, nullable values, logo fallback, recent failure isolation и cached portfolio visibility.

Delivery Map

Area Status Architectural outcome or N/A reason
frontend affected AccountPage возвращается к container/ViewModel/view boundary; account page views дробятся по смысловым presentation components; frontend-local docs и AGENTS.md фиксируют guidance; focused VM tests добавляются к existing render regressions.
backend N/A Backend behavior, routes, DTOs, T-Bank integration и OpenAPI runtime не меняются.
specs affected Этот SDD change хранит approved Proposal, skipped Delta Specs, Design и Tasks для behavior-preserving frontend architecture hardening.
docs N/A Отдельный as-built docs repository не меняется: product capability и documented system behavior не меняются; frontend-local conventions живут в frontend repo.

Contract Gate Decision

Gate Status Reason
runtime-openapi N/A Change не затрагивает backend и не меняет frontend/backend API contract, generated client или OpenAPI snapshot.

Solution Assessment

Формальная build-versus-buy assessment не triggered. Change не вводит и не заменяет auth, security, storage, external integration, observability, caching, concurrency или infrastructure/tooling. Automatic checker явно оставлен non-goal; docs/review guidance не требует выбора стороннего решения.

Risks / Trade-offs

  • Regression при переносе вычислений. Перемещение grouping/share/recent state из view в VM может изменить детали отображения. Mitigation: focused VM tests фиксируют derived data, existing render tests фиксируют пользовательски видимые states.
  • VM становится слишком presentation-specific. VM получает display flags и grouped sections, но не локализует text и не собирает JSX. Это удерживает границу между testable preparation и rendering.
  • Docs-only guardrail может быть пропущен. Без automatic checker нарушение возможно. Mitigation: frontend/AGENTS.md ведёт AI agents к явной boundary guidance, а largest local account page перестаёт быть плохим примером.
  • Слишком мелкое дробление компонентов. Split по каждому JSX fragment ухудшит навигацию. Mitigation: дробление основано на semantic responsibility и самостоятельном имени section/state, а не на механическом лимите строк.

Migration Plan

  1. Добавить focused tests для ViewModel-derived account page presentation data, чтобы зафиксировать переносимую логику до refactor.
  2. Расширить AccountPage.vm.ts: перенести recent queries, position grouping, share calculations, refresh/retry data и recent section states в возвращаемую presentation model.
  3. Раздробить account page views на nearby presentation components и обновить AccountPage.view.tsx так, чтобы view modules получали props и не вызывали data/auth/router/navigation/VM hooks.
  4. Обновить frontend-local docs и frontend/AGENTS.md с explicit container/ViewModel/view boundary и split guidance.
  5. Запустить targeted account page tests, затем frontend canonical gate npm ci && npm run verify; после implementation выполнить semantic verification против Proposal и Design и workspace dispatcher для change.

Rollback — обычный revert frontend/specs commits; schema, data migration, runtime contract migration и feature flag не вводятся.

Open Questions