# 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