docs(specs): archive frontend view model boundary change
This commit is contained in:
@@ -0,0 +1,164 @@
|
||||
# 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
|
||||
|
||||
<!-- none -->
|
||||
Reference in New Issue
Block a user