refactor: enforce frontend view model boundaries
This commit is contained in:
@@ -19,7 +19,9 @@ StateView/LoadingSkeleton и их stories принадлежат `packages/ui`.
|
||||
Используй CSF, `Meta`/`StoryObj`, `satisfies Meta`, локальные fixtures и actions.
|
||||
Общие Mantine/theme/i18n/Dates providers уже есть в
|
||||
[preview](../../.storybook/preview.tsx); не создавай второй theme source.
|
||||
Stories не достигают application API, auth, VM и query providers даже транзитивно;
|
||||
Stories не достигают application API, auth и query providers даже транзитивно.
|
||||
Presentation VM с form-context/UI-only state допустима; data VM остаётся за
|
||||
этой границей, которую проверяет полный reachable graph;
|
||||
границу проверяет `deps:storybook`. Не добавляй MSW worker ради каталога.
|
||||
|
||||
`play` через `storybook/test` нужен, когда interaction иллюстрирует собственный
|
||||
|
||||
@@ -17,6 +17,30 @@
|
||||
CSF сохраняет default meta и прямые story exports; configuration exceptions
|
||||
заданы в [ESLint](../../eslint.config.mjs). Не распространяй их на application code.
|
||||
|
||||
## Читаемость: ESLint и review
|
||||
|
||||
ESLint проверяет ограничение 80 символов (tab = 2), пустые строки между
|
||||
JSX siblings и вокруг многострочных declarations/control-flow/return, вынос
|
||||
функций и условных выражений из JSX props, отдельные Zod schema files,
|
||||
отсутствие SVG в приложении, optional trailing parameters и многострочные
|
||||
сложные union types. Generated artifacts не форматируй вручную.
|
||||
URL, строковые/template/regex literals и комментарии исключены из `max-len`;
|
||||
это исключение для содержимого, а не повод собирать длинные expressions.
|
||||
CSS и Markdown также держи в 80 колонках там, где это не ломает данные/ссылки.
|
||||
|
||||
Review проверяет то, чего синтаксис не доказывает:
|
||||
|
||||
- Пустая строка отделяет смысловые шаги: inputs, вычисления, действие, результат.
|
||||
- Имена derived values описывают значение, например `projectedAmount`.
|
||||
- Тернарник остаётся коротким выбором двух значений. Mapping, несколько веток
|
||||
и многострочное преобразование — отдельная функция с guard и `if/else`.
|
||||
- Union из object shapes оформляй несколькими строками; nullable scalar
|
||||
`null | string` остаётся компактным. Для повторяемого shape используй named type.
|
||||
- Последний необязательный аргумент — `accountId?: string`. Required аргумент
|
||||
перед следующим required параметром может сохранять `T | undefined`.
|
||||
- Helpers и действия размещай по [UI ownership](ui.md#container-vm-и-view);
|
||||
перенос не меняет null/zero, validation, recovery, closures и hook ordering.
|
||||
|
||||
## Hooks и derived state
|
||||
|
||||
Hooks вызываются без условий и ранних выходов перед ними; custom hook нужен
|
||||
|
||||
+29
-11
@@ -11,13 +11,11 @@ Mantine component и общую тему. Для выпадающего выбо
|
||||
ограничения JSDOM решай в test setup, сохраняя production UI.
|
||||
Семантический HTML для структуры страницы остаётся уместным.
|
||||
|
||||
Icons ищи у существующего owner (например,
|
||||
[BankOperationIcon](../../src/entities/bank-card/ui/BankOperationIcon/BankOperationIcon.tsx));
|
||||
статичные Add/Edit/Delete доступны в `@wealthledger/icons`. Их SVG декоративны
|
||||
по умолчанию; имя action принадлежит кнопке. Standalone meaningful icon получает
|
||||
`aria-hidden={false}`, `role="img"` и доступное имя. Category/kind mappings
|
||||
и анимации остаются в entity. Не добавляй пакет ради одного символа. Business/domain
|
||||
components живут у feature/entity/page, даже при повторном использовании.
|
||||
Icons импортируй из `@wealthledger/icons`; геометрия, SVG assets и animation
|
||||
lifecycle принадлежат этому пакету. Domain/category lookup возвращает параметры
|
||||
иконки из model сущности. SVG декоративны по умолчанию; доступное имя action
|
||||
принадлежит кнопке. Meaningful icon получает `aria-hidden={false}`, `role="img"`
|
||||
и доступное имя. Приложение и Storybook подключают CSS пакета.
|
||||
|
||||
Wrapper нужен для проектной ответственности, а не ради нового имени
|
||||
для Button. Локальный CSS module принадлежит component; общий token вводи для
|
||||
@@ -51,10 +49,26 @@ retry callbacks, domain grouping и derived presentation data. View получа
|
||||
| `Component.types.ts` | Переиспользуемые props/local types, когда отдельный owner полезен |
|
||||
| `Component.module.css` | Локальные стили |
|
||||
|
||||
Во view нет data/auth/router/navigation/VM hooks и вычислений по domain collection.
|
||||
Допустимы `useTranslation`, form-context hooks, UI-only state (например failed
|
||||
image fallback) и formatter одного переданного значения. Группировка/доли
|
||||
коллекции готовятся в VM. Перенос fetch во view ради меньшего container нарушает границу.
|
||||
View получает готовые данные и именованные действия. Container вызывает VM;
|
||||
VM владеет запросами, формой, состоянием, обработчиками и derived values.
|
||||
Во view нет data/auth/router/navigation hooks и domain grouping. Data VM
|
||||
вызывает container; presentation hook не должен скрывать fetching во view.
|
||||
Presentation VM для form-context/UI-only state может использоваться автономно
|
||||
в каталоге: её граф зависимостей не достигает application data/API.
|
||||
`useTranslation` и декларативный вывод коллекции допустимы во view.
|
||||
|
||||
JSX props получают ссылки на функции, включая `onClick`, render props,
|
||||
`dateParser` и callbacks внутри объектов props. Для React Hook Form предпочитай
|
||||
`useController` в VM вместо inline `Controller.render`. Значение prop с условием
|
||||
сначала получает предметное имя: `projectedAmount`, `archiveActionLabel`.
|
||||
Проверяй narrowing при переносе: выражение не должно читать отсутствующие данные
|
||||
до ветки, которая раньше защищала доступ; обработчик сохраняет актуальный snapshot.
|
||||
|
||||
Внутри компонента нет именованных helpers. Чистые функции форматирования и
|
||||
адаптации UI inputs выноси в `shared/lib`, передавая явные аргументы. Domain
|
||||
преобразование остаётся в model своего owner; shared не импортирует entities/pages.
|
||||
Closure над состоянием, mutations и refs является действием VM, а не shared helper.
|
||||
Render-helper замени самостоятельным presentation component у того же owner.
|
||||
|
||||
Отдельный state/section с самостоятельной responsibility выноси в
|
||||
`components/` своего component. Небольшой tightly-coupled renderer оставляй
|
||||
@@ -87,6 +101,10 @@ image fallback) и formatter одного переданного значени
|
||||
|
||||
## Формы
|
||||
|
||||
Схемы Zod объявляй в `*.schema.ts`, включая `.refine`/`.superRefine`.
|
||||
Зависимая от текущих данных схема — фабрика с явными inputs в том же файле;
|
||||
VM вызывает фабрику. Сохраняй актуальность данных и deferred validation messages.
|
||||
|
||||
Используй `react-hook-form`, `zodResolver`, `FormProvider`. Container/VM создаёт
|
||||
form state, view получает `onSubmit`/`disabled`, fields используют `useFormContext`.
|
||||
Во время submit блокируй все действия формы. Zod errors показывай у поля;
|
||||
|
||||
Reference in New Issue
Block a user