11 KiB
codify-frontend-component-style — Design
Context
Frontend уже использует React/Vite, TypeScript strict, Biome, FSD checks,
Knip, coverage ratchet, build и bundle budget внутри canonical gate
npm run verify. Code style сейчас документирован в frontend/docs, но React
component declaration style не нормализован: production *.tsx files содержат
export function, function declarations для локальных components,
destructuring props прямо в parameters, inline props object types и несколько
export statements в одном module.
Желаемый project style уже проявлен на AccountHeading: React component
становится const, получает named props type, принимает props, destructuring
делает внутри body, а module export стоит внизу. Этот shape нужно распространить
на authored frontend source. Generated OpenAPI source исключён из style
ownership, потому что им владеет generator.
Ограничение Change Owner: не писать custom style checker. Поэтому hard guardrail покрывает только то, что поддерживает existing deterministic local tooling. Остальные conventions фиксируются как mandatory review guidance, но не выдаются за gate-blocking invariant.
Goals / Non-Goals
Goals:
- Нормализовать authored frontend React components к const-valued component
declarations с
FCtyping. - Зафиксировать named props types и запрет inline props typing как mandatory frontend convention.
- Перенести props destructuring из function parameters внутрь component body как mandatory frontend convention.
- Настроить existing frontend tooling так, чтобы проверяемая часть component
style блокировала
npm run verify. - Мигрировать current authored frontend source, включая
index.tsbarrels, к единому export style без изменения runtime behavior. - Документировать границу между enforceable guardrails и review-only conventions.
Non-Goals:
- Custom AST checker, repository hygiene style checker или новый lint framework.
- Автоматический запрет inline props typing, props parameter destructuring или более чем одного export statement, если existing tooling не поддерживает это правило.
- Изменение route structure, FSD boundaries, API hooks, generated OpenAPI types, localization keys, visual design, tests semantics или product behavior.
- Изменение backend, persisted data, runtime OpenAPI contract или отдельного as-built docs repository.
Decisions
D1. Components объявляются как const с FC. Named React components в
authored frontend source объявляются как const Component: FC<Props> = (props) => { ... } или const Component: FC = () => { ... }, когда props нет. Это
сохраняет визуально явную React-component семантику и позволяет не повторять
props type в parameter annotation.
Alternative: const Component = (props: Props) => { ... }. Отклонено:
Change Owner предпочёл FC, потому что это яснее маркирует React component и
уменьшает annotation noise в arguments.
D2. Props contract named и не inline. Components с props получают named
type ComponentProps = ... рядом с component либо в соседнем
Component.types.ts, если тип переиспользуется или module становится крупным.
Components только с children используют FC<PropsWithChildren> без лишнего
props alias. Components, у которых кроме children есть другие props,
используют named type на основе PropsWithChildren<{ ... }>. Empty props type
не создаётся для components без props.
Alternative: разрешить inline { foo: Foo } для маленьких components.
Отклонено: именно inline props typing создаёт разнобой и ухудшает scanning.
D3. Props destructuring выполняется внутри body. Component parameter
остаётся props, а const { ... } = props; располагается в начале component
body после local hooks/перед использованием согласно читаемости конкретного
component. Это делает parameter shape одинаковым и упрощает добавление новых
props без изменения signature style.
D4. Export surface выбирает форму по роли файла. Authored source использует
named exports, default exports запрещены. Для обычных modules preferred shape —
один bottom export statement. Для чистых index.ts barrels preferred shape —
прямые re-exports export { X } from "./X"; без промежуточных imports. Где
TypeScript/Biome не позволяет выразить всё одним statement без ухудшения
читаемости, это остаётся review convention, но useExportsLast всё равно
запрещает exports перед non-export declarations.
D5. Existing Biome rules покрывают enforceable subset. Biome configuration
включает rules для named React components as arrow/const-valued components,
запрета default exports и размещения exports после body declarations. Эти rules
становятся частью existing npm run lint, а значит и npm run verify.
Alternative: написать project-specific checker для exact style. Отклонено по явному решению Change Owner и из-за maintenance cost для правил, которые должны оставаться review conventions.
D6. Проверка нового hard guardrail доказывает pass и fail без custom checker. Implementation должен показать, что compliant authored source проходит configured Biome gate, а controlled representative violations падают. Это проверяет именно Biome config, не вводя отдельный style checker для project source.
D7. Migration behavior-preserving и mechanical. Existing frontend source мигрируется синтаксически: declarations, props typing/destructuring и export placement. JSX, hooks order, route definitions, query behavior, state selection, localization keys и public imports не меняются намеренно. Если при миграции обнаруживается behavior change, work возвращается к upstream artifact.
Delivery Map
| Area | Status | Architectural outcome or N/A reason |
|---|---|---|
| frontend | affected |
Authored frontend source migrated to unified React component/export style; frontend Biome config enforces the checkable subset; frontend conventions document enforceable vs review-only parts; frontend canonical gate covers the configured rules. |
| backend | N/A |
Backend behavior, routes, DTOs, persistence, T-Bank integration and runtime OpenAPI are not touched. |
| specs | affected |
This SDD change carries approved Proposal, Delta Spec, Design and later Tasks for the frontend component style guardrail. |
| docs | N/A |
Separate as-built docs repository does not change: product behavior and system architecture are unchanged; frontend-local conventions live in the frontend repository. |
Contract Gate Decision
| Gate | Status | Reason |
|---|---|---|
| runtime-openapi | N/A |
Change does not touch backend API behavior, generated frontend API client or committed OpenAPI snapshot. |
Solution Assessment
This change adjusts frontend tooling policy but does not introduce a new tooling platform. The relevant options are:
- Existing Biome rules. Best fit for enforceable subset: already installed,
already part of
npm run lintandnpm run verify, deterministic and low-maintenance. Limitation: it cannot enforce every preferred convention, including exact single-export count and inline props typing. - Custom repository checker. Could enforce exact local style, but adds parser/AST maintenance, exception policy and separate failure UX. Rejected by Change Owner.
- ESLint or another lint framework. Could offer broader ecosystem rules but would duplicate Biome, add dependencies and increase maintenance for a small frontend. Rejected because existing Biome support covers the hard guardrail subset required by Delta Spec.
Decision: use existing Biome rules for gate-blocking checks and document the remaining style as review conventions.
Risks / Trade-offs
- Partial enforcement. Inline props typing, destructuring-in-parameters and exact single-export count can still regress. Mitigation: conventions clearly mark them mandatory review rules, while docs avoid calling them hard guardrails.
- Nursery rule stability. Biome's React component definition rule is a nursery rule. Mitigation: pin exact dependency versions already used by the project and validate config through the canonical gate.
- Large mechanical diff. Migrating all frontend source can obscure accidental behavior changes. Mitigation: keep edits syntax-focused, run existing tests/build/typecheck and perform semantic verification against the non-goals.
FCtyping trade-off.FCmakes component intent explicit but imports a React type into many files and may imply React component-specific typing where a plain function would suffice. This is accepted as the project convention.- Barrel readability. Direct re-exports in
index.tscan create several export statements. Mitigation: use this shape only for pure barrels and keep ordinary modules on bottom named export blocks.
Migration Plan
- Configure Biome for the checkable style subset: const-valued named React components, no default exports and exports after body declarations.
- Add verification evidence for the configured hard guardrail with one compliant case and representative failing cases, without adding a custom project style checker.
- Update frontend conventions to document the full preferred style and clearly separate hard gate rules from review-only conventions.
- Migrate authored frontend source mechanically: component declarations,
FCimports, named props types where they carry real props shape, directFC<PropsWithChildren>for children-only components, internal props destructuring, bottom grouped exports for ordinary modules and direct re-exports for pure barrels. - Run focused style/lint checks, then frontend canonical
npm run verify. - Validate the OpenSpec change and run the workspace dispatcher after Tasks are complete.
Rollback is a normal revert of frontend/specs commits. No data migration, feature flag, API migration or backend deployment sequence is required.