Files
specs/openspec/changes/archive/2026-08-25-codify-frontend-component-style/design.md
T

186 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.