Files

8.2 KiB
Raw Permalink Blame History

Context

См. motivation в proposal.md и behavioral contract в specs/frontend-quality-guardrails/spec.md. Baseline под Node 22.21.0: npm run verify проходит (17 test files, 55 tests), coverage составляет 82.46% statements, 75.68% branches, 75.47% functions и 83.41% lines, но Vite создаёт один JS entry asset размером 628,212 bytes minified.

Текущий dependency-cruiser проверяет только направление FSD layers. Он не включает no-circular и не различает public index.ts от внутренних files другого slice. shared/i18n/index.ts -> zod.ts -> index.ts образует cycle; app/ui/App.tsx и features/auth/ui/AuthForm.tsx обходят public API routes и i18n. AppRoutes статически импортирует все page components. Vitest собирает coverage, но не задаёт thresholds.

Baseline bundle measurement

Vite manifest при текущем build содержит один isEntry JavaScript asset assets/index-DEUAdr-U.js размером 628,212 bytes. Bundle gate будет читать manifest и filesystem, а не парсить human-oriented Vite output.

Goals / Non-Goals

Goals:

  • Сделать существующий npm run verify источником enforced structural, bundle и coverage guarantees.
  • Дать routes lazy-loading boundary без ручного vendor chunk policy.
  • Сделать изменение policy проверяемым через focused regression tests и deterministic build artifacts.

Non-Goals:

  • Не закрывать WL-P3-04: не менять lang, ARIA/live-region policy или application accessibility semantics. Для loading fallback используется Mantine component без нового собственного accessibility primitive.
  • Не вводить CI/CD, new runtime dependencies, API/backend changes, route contract changes или manual manualChunks policy.
  • Не повышать coverage ради процента и не создавать general-purpose architecture linter вне существующего dependency-cruiser gate.

Decisions

Dependency-cruiser остаётся единственным structural graph gate

Configuration задаёт severity: error для всех FSD rules (default dependency-cruiser severity — warn) и добавляет no-circular и rule с capture groups: она запрещает dependency из одного slice во внутренний module другого slice, но разрешает внутренние imports того же slice и target public index.ts. Это охватывает application и colocated tests, не создавая allowlist для известных violations.

Текущий i18n initialisation переезжает в leaf module; public index.ts сохраняет только consumer-facing validation helper, а zod.ts импортирует leaf напрямую. Route boundary реэкспортируется из public routes API; application потребляет его оттуда. Это устраняет текущие violations, не оставляя неиспользуемый public symbol.

Альтернатива — отдельный regex linter для import strings. Она отвергнута, поскольку duplicate resolver легко расходится с TypeScript alias и не даёт cycle analysis. Альтернатива с постоянными exception paths отвергнута: она сделает public API policy необязательной.

Initial budget считается по Vite manifest import graph

Vite build пишет manifest. bundle:check находит isEntry JavaScript entry, рекурсивно суммирует только его imports, читает sizes assets с диска и завершается ошибкой выше 500 * 1024 bytes. dynamicImports намеренно не попадают в initial payload.

Route pages загружаются через React.lazy; единственный route-level Suspense fallback использует Mantine loader. После выделения страниц manifest показал, что protected application shell всё ещё достижим из entry graph; поэтому ProtectedLayout также становится lazy route boundary. Это снизило measured initial graph с 549,665 до 509,165 bytes. Script отдельно сохраняет размер каждого dynamic route boundary — page и ProtectedLayout — в committed baseline JSON. При последующем build script падает, если существующий lazy route chunk стал больше baseline; обновление baseline выполняется только отдельной явной командой после review.

Альтернатива — опираться на Vite chunkSizeWarningLimit. Она отвергнута: warning не является gate и измеряет отдельный asset, а не весь eagerly loaded entry graph. Альтернатива с manualChunks отвергнута как premature vendor optimization.

Coverage thresholds являются floors, а не delivery metric

Vitest получает global thresholds, округлённые вниз от observed baseline: 82/75/75/83, и instrumentирует весь committed application source src/**/*.{ts,tsx} (исключая test/support source). Любое уменьшение metrics останавливает test:coverage stage; scenario tests по-прежнему определяют достаточность поведения.

Альтернатива — exact current percentages. Она отвергнута, поскольку малое изменение числа instrumented lines создаёт шум без полезного risk signal.

Remediation lifecycle сохраняет фактический scope

До реализации registry помечает данный change active только для WL-P3-02, WL-P3-03, WL-P3-05 и WL-P3-06. WL-P3-04 остаётся planned, так как owner исключил application accessibility work из MVP scope. После успешного archive registry фиксирует terminal evidence и называет tighten-backend-request-feedback следующей единственной recommended task.

Risks / Trade-offs

  • [Lazy routes кратко показывают fallback] → использовать единственный Mantine loader без изменения domain, route или application accessibility policy.
  • [Vite manifest shape меняется при обновлении tooling] → bundle script валидирует required manifest fields и сообщает format error вместо false green result.
  • [Новый lazy route не имеет baseline] → explicit baseline command создаёт его только после review; normal verify не переписывает committed baseline.
  • [Structural rule ловит корректный generated import] → exception добавляется только с documented generator constraint и regression evidence.
  • [Coverage floor блокирует полезный refactor] → добавить scenario coverage либо принять отдельное согласованное изменение baseline, а не обходить gate.

Migration Plan

  1. Сделать dependency, public-API и route-loading behavior testable; затем изменить минимальные source/config files, пока tests and structural gate не станут green.
  2. Добавить manifest-based bundle tooling, сформировать reviewed lazy baseline и включить bundle check в verify.
  3. Добавить coverage floors, выполнить full Node 22 frontend verify и OpenSpec/docs gates.
  4. Rollback — independent Git revert в frontend/specs/docs. Нет persisted data migration, API migration или browser storage migration.