186 lines
11 KiB
Markdown
186 lines
11 KiB
Markdown
# 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 с `FC` typing.
|
||
- Зафиксировать 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.ts` barrels, к
|
||
единому 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 lint` and `npm 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.
|
||
- **`FC` typing trade-off.** `FC` makes 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.ts` can create several
|
||
export statements. Mitigation: use this shape only for pure barrels and keep
|
||
ordinary modules on bottom named export blocks.
|
||
|
||
## Migration Plan
|
||
|
||
1. Configure Biome for the checkable style subset: const-valued named React
|
||
components, no default exports and exports after body declarations.
|
||
2. Add verification evidence for the configured hard guardrail with one
|
||
compliant case and representative failing cases, without adding a custom
|
||
project style checker.
|
||
3. Update frontend conventions to document the full preferred style and clearly
|
||
separate hard gate rules from review-only conventions.
|
||
4. Migrate authored frontend source mechanically: component declarations,
|
||
`FC` imports, named props types where they carry real props shape, direct
|
||
`FC<PropsWithChildren>` for children-only components, internal props
|
||
destructuring, bottom grouped exports for ordinary modules and direct
|
||
re-exports for pure barrels.
|
||
5. Run focused style/lint checks, then frontend canonical `npm run verify`.
|
||
6. 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.
|