diff --git a/openspec/changes/codify-frontend-component-style/.openspec.yaml b/openspec/changes/codify-frontend-component-style/.openspec.yaml new file mode 100644 index 0000000..d18106d --- /dev/null +++ b/openspec/changes/codify-frontend-component-style/.openspec.yaml @@ -0,0 +1,6 @@ +schema: wealth-ledger-sdd +created: 2026-08-25 +goal: Codify one frontend React component style and make the enforceable subset part of the frontend verification gate. +affected_areas: + - frontend + - specs diff --git a/openspec/changes/codify-frontend-component-style/design.md b/openspec/changes/codify-frontend-component-style/design.md new file mode 100644 index 0000000..7c03f0e --- /dev/null +++ b/openspec/changes/codify-frontend-component-style/design.md @@ -0,0 +1,185 @@ +# 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) +=> { ... }` или `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` без лишнего +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` 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. diff --git a/openspec/changes/codify-frontend-component-style/implementation-summary.md b/openspec/changes/codify-frontend-component-style/implementation-summary.md new file mode 100644 index 0000000..3be90c2 --- /dev/null +++ b/openspec/changes/codify-frontend-component-style/implementation-summary.md @@ -0,0 +1,109 @@ +# Implementation Summary: codify-frontend-component-style + +## Scope + +Реализована behavior-preserving frontend style migration для утверждённого +change: + +- Biome настроен на enforceable subset React component style; +- frontend conventions описывают полный React component style и границу между + enforced guardrails и mandatory review conventions; +- authored frontend source мигрирован на const-valued `FC` React components, + named props types only where they carry real props shape, direct + `FC` for children-only components, internal props + destructuring, bottom named exports for ordinary modules and direct + re-exports for pure barrels; +- generated OpenAPI source оставлен под ownership генератора и не + редактировался руками ради style. + +Backend code, persisted data, runtime OpenAPI behavior, routes, localization +keys, API calls, state selection и visual design намеренно не менялись. + +## Guardrail Proof + +Локально использовался Biome `2.5.8`. + +- `npm exec biome -- check .tmp-biome-style/compliant.tsx` — pass, + `Checked 1 file ... No fixes applied`. +- `npm exec biome -- check .tmp-biome-style/function-component.tsx` — fail с + `lint/nursery/useReactFunctionComponentDefinition` для React component, + объявленного как function declaration. +- `npm exec biome -- check .tmp-biome-style/default-export.tsx` — fail с + `lint/style/noDefaultExport` для `export default`. +- `npm exec biome -- check .tmp-biome-style/export-order.tsx` — fail с + `lint/style/useExportsLast` для export перед module declarations. + +Temporary samples удалены после proof и не входят в delivered source. + +## Review-Only Convention Checks + +Manual scans после migration: + +- `rg -n "^export function|^export const|^export type|^export interface|^export default|^function [A-Z][A-Za-z0-9]*" src scripts --glob '*.{ts,tsx,mjs}'` — + остались только generated OpenAPI declarations. +- `rg -n "\\}: \\{[^}]+\\}\\) =>|\\}: \\{[^}]+\\}\\)" src --glob '*.tsx'` — + не нашёл inline destructured component props matches. +- `rg --pcre2 -n "^const [A-Z][A-Za-z0-9]*(?![^=]*:\\s*FC\\b)[^=]*=" src --glob '*.tsx'` — + нашёл только `lazy(...)` route bindings и `AuthContext`, а не const-valued + React function component declarations. +- `rg -n "type \\w+Props = PropsWithChildren;" src --glob '*.tsx'` — не нашёл + лишних aliases для children-only components. +- `rg -n "^import \\{ .+ \\} from \\\"\\." src/**/index.ts` — не нашёл + чистых barrel imports, которые нужны только для последующего re-export. + +## Verification + +Baseline before structural edits: + +- `npm run typecheck` — pass. +- `npm run test:coverage` — pass, `21` test files и `67` tests passed. + +Targeted checks после migration: + +- `npm run lint` — pass, `Checked 121 files ... No fixes applied`. +- `npm run typecheck` — pass. +- `npm run test:coverage` — pass, `21` test files и `67` tests passed; + coverage summary: statements `86%`, branches `78.28%`, functions `79.09%`, + lines `86.64%`. +- `npm run knip` — pass. +- `npm run deps` — pass, `4` dependency-cruiser config tests passed и + `no dependency violations found`. +- `npm run build` — pass. + +Canonical frontend gate: + +- `npm run verify` под Node `26.7.0` — blocked by runtime preflight as + expected: repository требует Node `22.x`. +- `PATH=/Users/ksv741/.nvm/versions/node/v22.21.0/bin:$PATH npm run verify` — + pass; runtime verified Node `22.21.0`, OpenAPI check passed, repository + hygiene passed, lint/typecheck/coverage/knip/deps/build/bundle checks passed, + bundle check reported `Initial JavaScript payload: 510523 bytes`. + +OpenSpec structural validation: + +- `openspec validate codify-frontend-component-style --type change --strict --store wealth-ledger-specs` — + pass. + +Independent read-only review: + +- Separate read-only review reported no Critical or Important findings. +- Minor findings about mixed language and stale implementation-summary status + were addressed before workspace dispatcher. + +Workspace dispatcher: + +- `PATH=/Users/ksv741/.nvm/versions/node/v22.21.0/bin:$PATH bash specs/scripts/verify-workspace-change.sh codify-frontend-component-style` — + pass; Delivery Map verified `frontend=affected backend=N/A specs=affected + docs=N/A`, Contract Gate Decision verified `runtime-openapi=N/A`, main specs + `20 passed, 0 failed`, frontend canonical verification passed and workspace + verification passed. + +## Semantic Verification + +Implementation соответствует утверждённым Proposal, Delta Spec, Design и Tasks +как style/tooling change с уточнением Change Owner: children-only components +используют `FC` без лишнего alias, а чистые barrels +используют прямые re-exports без промежуточных imports. Изменены +declaration/export shape и documentation. User-visible behavior, routes, +localized text keys, API contract, backend behavior, generated API source +ownership и product documentation остаются вне изменённого scope. diff --git a/openspec/changes/codify-frontend-component-style/proposal.md b/openspec/changes/codify-frontend-component-style/proposal.md new file mode 100644 index 0000000..590da18 --- /dev/null +++ b/openspec/changes/codify-frontend-component-style/proposal.md @@ -0,0 +1,67 @@ +## Why + +Frontend сейчас содержит несколько допустимых способов объявлять React +components, props и exports. Из-за этого соседние files выглядят по-разному: +часть компонентов объявлена как function declarations, часть может появляться +как const-valued components, props иногда типизируются inline, а exported API +может быть смешан с body файла. + +Эта неоднородность делает review менее механическим и ухудшает AI-assisted +changes: future edits могут копировать любой локальный пример и при этом +расширять разброс стилей. Проблема стала видимой после ручного приведения +account heading component к более явному стилю, который лучше отделяет props +contract, component body и module export surface. + +Это нужно закрепить сейчас, пока frontend ещё достаточно мал для одной +behavior-preserving миграции. После фиксации единого стиля новые изменения +будут проще читать, сравнивать и проверять, а будущие agents получат один +канонический пример вместо конкурирующих local patterns. + +## What Changes + +- Установить один frontend-wide стиль для React components: component + declaration должна быть const-valued, props contract должен быть named type + рядом с компонентом или в предназначенном для типов module, а props + destructuring должен происходить внутри component body. +- Свести обычный module export surface к одному named export block внизу + authored source file, а чистые barrels оформлять прямыми re-exports без + промежуточных imports, сохраняя читаемый public API и запрещая default export + для frontend source. +- Сделать автоматически проверяемую часть этого стиля частью frontend + verification gate. +- Зафиксировать оставшуюся непроверяемую часть как mandatory frontend + convention для maintainers, reviewers и AI agents, не выдавая её за hard + guardrail. +- Мигрировать существующий frontend source к единому стилю без изменения + user-visible behavior, routes, localized text, API contract, data fetching, + state selection, rendering states или visual design. +- Non-goal: добавлять custom style checker, если existing frontend tooling не + поддерживает конкретный preferred convention. +- Non-goal: менять frontend architecture boundaries, Feature-Sliced Design + imports, generated API types, backend behavior, durable data behavior или + product requirements. +- Non-goal: вводить новые product-visible states, labels, interactions or + capabilities. + +## Capabilities + +### New Capabilities + +_Нет._ + +### Modified Capabilities + +- `frontend-quality-guardrails`: frontend quality gate must include the + automatically enforceable part of the unified React component style. + +## Impact + +Users должны видеть то же приложение до и после change. Изменение затрагивает +frontend maintainers, reviewers и AI agents: authored React source получает +единый style contract, а frontend verification начинает отклонять нарушения +той части contract, которую можно проверить existing local tooling. + +Affected implementation area — frontend source style, frontend-local +conventions и frontend verification. Specs отслеживает SDD lifecycle и меняет +frontend quality guardrail requirement. Backend и product-facing +documentation не должны меняться. diff --git a/openspec/changes/codify-frontend-component-style/specs/frontend-quality-guardrails/spec.md b/openspec/changes/codify-frontend-component-style/specs/frontend-quality-guardrails/spec.md new file mode 100644 index 0000000..3d936fa --- /dev/null +++ b/openspec/changes/codify-frontend-component-style/specs/frontend-quality-guardrails/spec.md @@ -0,0 +1,47 @@ +## ADDED Requirements + +### Requirement: Frontend проверяет автоматически проверяемый стиль React components + +Authored frontend source MUST сохранять автоматически проверяемую часть +документированного стиля React components внутри документированного frontend +verification gate. Gate MUST отклонять default exports в authored frontend +source, named React components, объявленные вне документированной const-valued +component form, и module exports, размещённые перед declarations тела module. +Style preferences, для которых нет детерминированной локальной проверки, MUST +оставаться review conventions и MUST NOT описываться как gate-blocking hard +guardrails. + +#### Scenario: Authored source соблюдает проверяемый component style + +- **WHEN** authored frontend source объявляет named React components через + документированную const-valued component form, не использует default exports + и держит exports после declarations тела module +- **THEN** документированный frontend verification gate не падает из-за React + component style + +#### Scenario: Named component использует запрещённую форму объявления + +- **WHEN** authored frontend source объявляет named React component вне + документированной const-valued component form +- **THEN** документированный frontend verification gate падает и сообщает + нарушение React component style + +#### Scenario: Authored source использует default export + +- **WHEN** authored frontend source открывает module API через default export +- **THEN** документированный frontend verification gate падает и сообщает + нарушение module export style + +#### Scenario: Authored source экспортирует до declarations тела module + +- **WHEN** authored frontend source размещает module export перед + non-exported declarations тела module +- **THEN** документированный frontend verification gate падает и сообщает + нарушение module export ordering + +#### Scenario: Convention не имеет детерминированной проверки + +- **WHEN** documented frontend style convention нельзя проверить existing + deterministic local tooling +- **THEN** frontend documentation трактует её как mandatory review convention, + а не как gate-blocking hard guardrail diff --git a/openspec/changes/codify-frontend-component-style/tasks.md b/openspec/changes/codify-frontend-component-style/tasks.md new file mode 100644 index 0000000..e48bc88 --- /dev/null +++ b/openspec/changes/codify-frontend-component-style/tasks.md @@ -0,0 +1,26 @@ +## 1. Tooling Guardrail + +- [x] 1.1 Настроить frontend Biome rules для enforceable subset component style: const-valued named React components, no default exports и exports после declarations тела module. Verification: `npm run lint` сообщает настроенные rules и падает на representative local violations. +- [x] 1.2 Доказать новый hard guardrail одним compliant fixture или временным authored source sample и representative failing samples для запрещённой component declaration, default export и export ordering. Verification: записать exact Biome commands и observed pass/fail behavior в implementation summary перед archive. + +## 2. Frontend Conventions + +- [x] 2.1 Обновить frontend-local conventions и описать полный React component style: `FC` typing, named props types только когда они несут real props shape, no inline props typing, props destructuring внутри component body, один нижний named export block для обычных modules и прямые re-exports для чистых barrels. Verification: `frontend/docs/CONVENTIONS.md` явно разделяет Biome-enforced guardrails и mandatory review conventions. +- [x] 2.2 При необходимости обновить frontend routing guidance для AI agents, чтобы будущие frontend edits находили component style convention до implementation. Verification: affected local guidance указывает на documented convention без дублирования product architecture. + +## 3. Source Migration + +- [x] 3.1 Мигрировать authored production React components в `frontend/src` на const-valued `FC` component declarations без изменения JSX, hook order, route behavior, state selection, localized text keys или API calls. Verification: TypeScript и existing frontend tests проходят после migration. +- [x] 3.2 Заменить inline props typing в authored React components на named props types, использовать `FC` без alias для children-only components, использовать `PropsWithChildren<{ ... }>` для components с `children` и дополнительными props и не создавать empty props type для components без props. Verification: review migrated source на documented convention и запуск `npm run typecheck`. +- [x] 3.3 Перенести React component props destructuring из function parameters в component bodies. Verification: review migrated source на documented convention и запуск `npm run typecheck`. +- [x] 3.4 Свести обычные authored module exports к одному нижнему named export block, а чистые barrels — к прямым re-exports с сохранением public APIs и import paths. Verification: `npm run lint`, `npm run knip` и `npm run deps` проходят. +- [x] 3.5 Оставить generated OpenAPI source вне manual style migration. Verification: generated files остаются owned by existing OpenAPI workflow и не редактируются руками ради component style. + +## 4. Verification + +- [x] 4.1 Запустить targeted frontend checks после migration. Verification: `npm run lint`, `npm run typecheck`, `npm run test:coverage`, `npm run knip`, `npm run deps` и `npm run build` проходят из `frontend`. +- [x] 4.2 Запустить frontend canonical gate. Verification: `npm run verify` проходит из `frontend` под Node 22. +- [x] 4.3 Проверить OpenSpec change структурно. Verification: `openspec validate codify-frontend-component-style --type change --strict --store wealth-ledger-specs` проходит из `specs`. +- [x] 4.4 Выполнить semantic verification против Proposal, Delta Spec, Design и Tasks. Verification: implementation summary подтверждает, что user-visible behavior, routes, localization, API contract, backend behavior, generated API source ownership и product documentation не изменились вне approved scope. +- [x] 4.5 Выполнить independent read-only review перед archive. Verification: review не содержит Critical или Important findings. +- [x] 4.6 Запустить workspace change dispatcher после завершения affected tasks. Verification: `bash specs/scripts/verify-workspace-change.sh codify-frontend-component-style` проходит из workspace root.