## 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.