Files
easy-png-tools2/docs/plan-codebase-quality.md
T
2026-09-26 20:10:35 +05:00

670 lines
49 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.
# План: линтеры, тесты и качество кодовой базы
> Статус: **draft — Q0–Q5 и Q6a–Q6f.5 готовы к ревью; Q7 — следующий этап**.
>
> Источник: `docs/backlog.md`, аудит конфигурации `web/`, e2e-тестов и
> инфраструктурных скриптов. Q0–Q5 и Q6a–Q6f.5 выполнены; Q7–Q8 выполняются
> отдельными атомарными шагами.
>
> Цель: сделать качество кодовой базы воспроизводимым, убрать ложную «зелёность»
> проверок, стабилизировать тестовые контракты и только после этого расширять
> автоматические правила.
## 1. Границы и принципы
1. Сначала stabilизировать существующие проверки и тестовые контракты, а не
добавлять новые сложные lint-правила.
2. Старый UI удалён: production работает на корневых маршрутах, а `/v1/**` и
v1-specific код больше не являются частью приложения.
3. Не добавлять новые v1-фичи, не чинить v1-баги и не расширять v1-тесты.
4. `core/` и `theme/` — общие модули. Их изменения проверяются unit-тестами и
smoke-сценариями production-потребителей.
5. Изменения делаются небольшими атомарными частями, в соответствии с
`AGENTS.md`.
6. Каждый новый lint-rule сначала появляется в report-only/warn-only режиме,
получает fixtures и тесты, и только после очистки baseline становится
blocking.
7. Детали реализации правил остаются в `web/eslint-plugins/README.md`;
`AGENTS.md` содержит краткие обязательные политики, понятные агенту без
дополнительных поисков, и ссылки на подробные документы.
### Политика удаления legacy v1
- Q3 — отдельная атомарная cleanup-задача; поддержка v1 не возвращается.
- Старые маршруты и UI-файлы удаляются без redirects и без совместимого
maintenance-режима.
- В рабочей кодовой базе не оставлять UI-v1-пути, исключения или документацию,
которые выглядят как действующий источник совместимости.
- Исторические упоминания допустимы только в `docs/archive/`.
- Будущий API-контракт `POST /v1/process` из `docs/plan-platform.md` не связан с
удалённым UI-v1 и остаётся в планах.
## 2. Текущее состояние quality-контура
- `pnpm verify` объединяет typecheck, unit, rule-тесты, lint, форматирование,
docs и production build; deploy выполняет его перед сборкой.
- `i18n/dict-consistency` даёт 0 warnings на текущем baseline; после Q7 вычищены
4 неиспользуемых design token, token audit тоже чист.
- E2E-контракт использует semantic locators и общий source-aware schema
resolver; активных known-issue `test.fixme` нет.
- `ToolError` хранится как key/vars и переводится только при выводе; смена
локали обновляет уже видимый alert.
- E2E гоняется в четырёх Playwright-проектах: полный Chromium, Firefox и WebKit
для browser-critical спецификаций и mobile Chromium 390×844. Job остаётся
non-blocking, число воркеров ограничено из-за памяти.
## 3. Этапы выполнения
### Q0. Зафиксировать baseline и синхронизировать документацию
**Статус:** готов к ревью. **Оценка:** S. **Зависимости:** нет.
- Проверить фактические команды и зафиксировать их в quality-gate документации.
- Вынести расширенный workflow агента в `docs/agent-workflow.md`, оставив в
`AGENTS.md` только обязательные инварианты и ссылки.
- Обновить `docs/backlog.md`:
- отметить dictionary linter как реализованный, но не доведённый до
zero-warning;
- убрать противоречие по кнопке Generate;
- синхронизировать числа инструментов и тестовых сценариев.
- Восстановить или исправить ссылку на раздел G в
`docs/checklist-manual-testing.md`.
- Убрать устаревшее утверждение про hardcoded debt в `kit` из `AGENTS.md`, если
оно больше не соответствует коду.
- Перенести явно устаревшие `docs/analysis.md` и `docs/sorting-backend.md` в
`docs/archive/`, добавить даты архивирования и удаления через 30 дней.
- Создать `docs/README.md` как индекс активных документов и карту источников
истины; подробные правила не дублировать в корневых файлах.
**Готово, когда:** один документ описывает текущие команды, актуальный baseline
и все известные `test.fixme` имеют ссылки на зарегистрированный баг.
### Q1. Единый quality gate и CI
**Статус:** готов к ревью. **Проверено:** `pnpm verify` проходит; E2E оставлен
отдельным non-blocking job. **Оценка:** S–M. **Зависимости:** Q0.
Добавить read-only `verify`-команду, которая запускает:
```text
pnpm --dir web check
pnpm --dir web test
pnpm --dir web test:rules
pnpm --dir web lint:all
pnpm --dir web exec prettier --check .
pnpm check:docs
pnpm --dir web build
```
E2E оставить отдельным тяжёлым гейтом:
```text
pnpm --dir web test:e2e
```
- Не дублировать вручную аргументы ESLint/Stylelint в нескольких скриптах;
оркестратор должен вызывать package scripts или единый общий модуль.
- Описать фактические команды и semantics ошибок/warnings в
`docs/quality-gates.md`; здесь нужна точная процедура, а не длинный дубль в
`AGENTS.md`.
- На первом этапе явно разделить `errors` и `warnings`.
- После очистки i18n baseline добавить строгий режим `--max-warnings=0`.
- Добавить отдельный workflow для pull request: typecheck, unit, rule-тесты,
lint, format и build.
- E2E сначала запускать отдельным job; сделать его blocking после закрытия
известных ошибок и стабилизации baseline. Решение отложено: условия matrix
пересматриваются после Q6f.3, см. раздел 7 «Отложенное решение».
**Готово, когда:** одна команда воспроизводит все быстрые проверки, а deploy не
может пройти при type/unit/lint/build-ошибке.
### Q2. Политика i18n и zero-warning baseline
**Статус:** готов к ревью. **Проверено:** i18n lint даёт 0 warnings, rule-тесты
и полный `pnpm verify` проходят. **Оценка:** M. **Зависимости:** Q0, Q1.
- `BASE_LOCALE` — единственный источник registry-English fallback для
`tools.*.title` и `tools.*.description`; остальные локали требуют физические
ключи.
- Временно неиспользуемые `tools.*.params` исключены pattern-based policy до
отдельной очистки мёртвого словаря; пустые значения и placeholders не
подавляются.
- Исправлены три реальных RU option-label gaps и удалены неиспользуемые
option-группы из `ru.ts`.
- Исправлен tie-break placeholder parity: при равенстве голосов выбирается
`BASE_LOCALE`.
- `lint-all` запускает ESLint с `--max-warnings=0`; unused-token сообщение
оставалось неблокирующим и закрыто в Q7.
- Plural-формы остаются отдельной задачей после Q2 и не смешиваются с этим
этапом.
### Q3. Удалить UI-v1 (cleanup завершён)
**Статус:** выполнен. **Проверено:** `pnpm verify` и production E2E smoke
проходят; после проверки обновлены конфиги и активные документы. **Оценка:** M.
**Зависимости:** Q0, Q1, Q2.
- Удалены `web/src/routes/v1/**`, `web/src/lib/v1/**`, `app_v1.css`, v1-specific
i18n/tests и fixtures.
- Удалены isolation plugin, его тест и все v1-исключения из lint-конфигураций.
- Убраны v1-ссылки из production-кода и активных документов; исторические
упоминания остаются в `docs/archive/`.
- Проверены production build, verify и отдельный E2E smoke production-маршрутов.
**Результат:** `/v1/**`, UI-v1-файлы и legacy-конфигурация отсутствуют;
следующий этап — Q5.
### Q4. Тестовый контракт e2e
**Статус:** выполнен. **Проверено:** `pnpm verify` и полный Playwright smoke
проходят; 105 тестов passed, 4 known-issue теста skipped. **Оценка:** M.
**Зависимости:** Q0–Q3.
- Добавлен `docs/testing-strategy.md` с locator, fixture, i18n и `test.fixme`
policy.
- В production markup добавлены только разрешённые hooks для source/result/
verdict/empty-state; текстовые элементы получили accessible labels, фильтры —
`aria-pressed`, а каталог — семантические heading/status landmarks.
- E2E переведён на `role`, label и stable `href`; удалены `TOTAL`/`GROUPS`,
exact fuzzy counts, internal CSS selectors, `alt`-locators и полные verdict
strings. Точные verdict-переводы перенесены в unit-тесты.
- Helpers централизованы в `web/e2e/helpers/page.ts`; исправлены stale
theme/source/locale checks. Theme fixme снят.
- Q5-фиксы resize/crop, generator, Reset и error-сценария не смешивались с этим
этапом.
**Результат:** изменение формулировки, класса или порядка элементов не ломает
поведенческий тест без изменения пользовательского контракта; следующий этап —
Q6.
### Q5. Закрыть известные preview-баги
**Статус:** выполнен. **Проверено:** unit, Q5 E2E smoke и полный `pnpm verify`
проходят; активных known-issue `test.fixme` нет. **Оценка:** M–L.
**Зависимости:** Q4.
- Добавлен общий opt-in `defaultFromSource` resolver в `registry-schema`: он
принимает source dimensions, обновляет только source-aware поля при upload и
используется в UI, executor и worker. `resize-png` и `crop-png` получают
размеры текущего source; static/generator/text schemas не затронуты.
- Reset сохраняет source/text, восстанавливает source-aware defaults и запускает
deferred auto-run через debounce.
- Генераторы проверены в auto-run без кнопки Generate; кнопка не добавляется.
- `ToolError` хранится как key/vars, переводится derived-значением при выводе и
обновляется при смене локали. E2E использует независимый invalid pixel-count
сценарий.
- Resize/crop, generator, Reset и localization fixme сняты; Q6a расширяет
покрытие, не смешиваясь с исправлением этих контрактов.
**Результат:** Q5 known issues закрыты и отражены в `docs/backlog.md` и
`docs/checklist-manual-testing.md`; Q6a выполнен, следующий этап — Q6b.
### Q6. Расширение unit и browser-покрытия
**Оценка:** M–L. **Зависимости:** Q4, Q5.
#### Q6a. Executor protocol, fallback и debounce
**Статус:** выполнен. Unit-тесты покрывают debounce, worker image/string/
verdict/files protocol, `ToolError` transport, constructor/postMessage/onerror/
malformed fallback, source-aware sanitization и DOM metadata. Browser smoke
проверяет rapid auto-run и no-worker fallback; deferred transport fallback
покрыт unit-тестами.
- `execute(tool, ctx)` сохраняет публичный API; `createExecutor` и protocol
handler являются внутренними test seams.
- `ToolError` не запускает direct retry повторно; transport/protocol failures
отключают worker и переходят в direct fallback.
- DOM-зависимые registry entries помечены `domOnly`, поэтому зелёный smoke не
скрывает нормальный worker fallback.
- `debounce` покрыт fake timers; E2E подтверждает последние значения при быстрых
последовательных изменениях генератора.
#### Q6b. FileResult → ZIP → download
**Статус:** выполнен. Q6a закрыл worker-транспорт; Q6b добавил проверяемый ZIP
seam, unit round-trip для `FileResult`, защиту пустого результата и error/busy
контракт download. Отдельный browser-сценарий скачивает архив и проверяет имена
частей и PNG-сигнатуры.
- `buildZipEntries` не зависит от DOM; `downloadZip` остаётся UI-обёрткой.
- `SchemaToolView` переиспользует `downloadBlob`, ловит ошибки кодирования и
блокирует повторный download на время сборки.
- E2E использует `downloadResultFile` и `unzipSync`, не добавляя новые
`data-testid`; files-инструменты не проходят через image-smoke.
#### Q6c. Output MIME/quality
**Статус:** выполнен. Q6c покрывает текущий фиксированный output-контракт
JPG/WebP/BMP, не превращаясь в UX-райз кнопки Download. Registry проверяет
согласованность MIME/extension/quality-поля, unit — границы quality, browser —
реальные сигнатуры файлов и разницу размера при изменении quality.
- `ToolEntry.output` остаётся декларативным описанием формата; новый селектор
форматов не добавляется.
- `validateOutputQuality` отделяет проверку 0..1 от DOM-рендеринга; JPEG/WebP
используют quality `1..100` из schema, BMP не имеет quality-параметра.
- E2E проверяет JPEG `FF D8 FF`, WebP `RIFF/WEBP`, BMP `BM` и PNG-фолбэк.
- Ошибки кодирования и unsupported MIME используют существующие i18n-ключи.
#### Q6d. Malformed и special PNG fixtures
**Статус:** выполнен. Runtime-fixtures покрывают CRC, truncated, palette и
16-bit без бинарных файлов в git. E2E фиксирует браузерный контракт: critical
CRC/truncated отклоняются без падения, ancillary CRC и хвост после `IEND`
допускаются, palette/16-bit декодируются. Unit фиксирует контракт анализаторов
для RGBA-данных, которые браузер отдаёт после нормализации.
- `web/e2e/helpers/fixtures.ts` собирает indexed, 16-bit, CRC-mutation,
truncation и tail-варианты из уже существующих `crc32`/`chunk`.
- Отклонение битых файлов — свойство декодера движка, а не единый продуктовый
контракт: Q6f.3 зафиксировал, что Firefox и WebKit декодируют `idat-cut` и
`truncated-no-iend`, а WebKit ещё и `crc-bad-idat`. Это ожидаемое поведение,
которое закреплено per-engine в `web/e2e/png-fixtures.spec.ts`.
- Обязательное требование при любом исходе: файл не роняет страницу, ошибка
показывается alert-ом, а не «пустым» результатом.
- Поведение `createImageBitmap` является частью browser-контракта, а не
самостоятельного PNG-парсера product-кода.
- Сырое сообщение браузера при decode-ошибке не локализуется в Q6d; это
отдельная задача в backlog.
#### Q6e. Registry-wide smoke
**Статус:** выполнен. Smoke расширен до всех 122 production tools из registry:
image→image, image→text, text→image, generator input→image и verdict. FileResult
и malformed/special PNG не дублируются — они уже покрыты Q6b/Q6d.
- `tools-smoke.spec.ts` расширен image/text-output кейсами, включая все
незакрытые color/geometry инструменты.
- `generators.spec.ts` использует единый data-harness для всех генераторов;
`single-color-png` сохраняет отдельную проверку auto-run/размеров.
- Text-input сценарии используют общий `renderText` helper, включая
`data-uri-to-png`.
- Component-тесты не добавлены: Vitest работает в node без DOM/Testing Library,
а текущий e2e-контракт уже покрывает поведение preview. Выбор test seam или
DOM-окружения перенесён в Q6f.
#### Q6f.1. Layout resolver seam
**Статус:** выполнен. Чистая логика группировки полей вынесена из
`SchemaFields.svelte` в `resolveLayoutGroups` и покрыта unit-тестами на
дедупликацию, неизвестные поля, пустые группы, `cols` и default-хвост.
- Компонент использует общий resolver, поэтому unit-тесты блокируют изменения
layout-логики без DOM/Testing Library.
- `SchemaPreview` view-model и DOM-компоненты не менялись; их seam остаётся
отдельным решением Q6f.2.
- Новые зависимости, vitest-конфиг и browser matrix не добавлялись.
#### Q6f.2. SchemaPreview model seam
**Статус:** выполнен. Вычисления `sourceValue`, `resultValue`, `formatValue` и
`hasResult` вынесены в `buildSchemaPreviewModel`; Svelte-компонент использует
модель, а unit-тесты проверяют все input/result-ветки без DOM.
- Translator передаётся в модель явно, поэтому unit-тесты не зависят от locale
state и проверяют только view-model.
- image/text/verdict/files и пустые результаты покрыты в `pnpm verify`.
- Новые зависимости, vitest-конфиг и browser matrix не добавлялись.
#### Q6f.3. Browser matrix и mobile viewport
**Статус:** выполнен. Playwright запускает четыре проекта: полный Chromium,
Firefox и WebKit для browser-critical спецификаций и mobile Chromium 390×844 с
`hasTouch` для навигации, каталога, генераторов и pipeline.
- `web/playwright.config.ts`: `browserContractTests` — `pipeline.spec.ts`,
`png-fixtures.spec.ts`, `text-and-verdicts.spec.ts`; `mobileTests` —
`navigation.spec.ts`, `catalog.spec.ts`, `generators.spec.ts`,
`pipeline.spec.ts`.
- `workers` — 1 локально и 2 в CI. Browser matrix ограничена памятью: при 12
воркерах на машине с 16 GB RAM браузеры не успевают поднять worker, и тесты
падают без ошибок (проверено и в Chromium). Локально matrix поэтому медленная,
но почти не нагружает компьютер. Per-project лимита воркеров в Playwright нет.
- Per-project `expect.timeout` не задаётся: Firefox и WebKit отдают результат
так же быстро, а расхождения объясняются памятью, а не скоростью движка.
- Декодеры движков расходятся, и это зафиксировано как ожидаемое поведение:
`png-fixtures.spec.ts` хранит список движков, отклоняющих каждый битый файл, и
не пропускает проверки. В любом браузере файл не должен ронять страницу, а
отказ декодера показывается alert-ом.
- CI устанавливает `chromium firefox webkit`; job остаётся non-blocking.
- Локальный прогон: 276 passed, 0 skipped, 0 failed. Visual snapshots и coverage
thresholds не добавлялись — это Q6f.4.
#### Q6f.4. Coverage thresholds
**Статус:** выполнен для чистой логики. Vitest считает покрытие только по
`src/lib`, с явным списком исключений, и `pnpm verify` падает при падении ниже
порога.
- `web/vitest.config.ts`: provider `v8`, `reporter: text-summary`, пороги
statements 90, branches 82, functions 72, lines 91.
- Пороги поставлены на 1.5–2.5 п.п. ниже измеренного baseline (91.77 / 83.3 /
74.31 / 92.53), поэтому gate ловит регрессию, а не текущий долг.
- Scope — логика, которую node-Vitest вообще может исполнить. Исключены
браузерные модули `core/domText.ts` и `core/io.ts` (canvas,
`createImageBitmap`), data-модули `i18n/en.ts`, `i18n/ru.ts`, `tool-icons.ts`,
rune-файлы `*.svelte.ts`, entry worker-а и type-only `components/define.ts`.
Причина каждого исключения зафиксирована в конфиге и в
`docs/testing-strategy.md`.
- `registry/*` (47–81% statements, 25–47% functions) в scope остаётся: это
обычная непокрытая логика, а не ограничение окружения. Покрывается e2e-smoke
Q6e, но unit-порог её не скрывает.
- `verify` теперь выполняет `test:coverage` вместо `test`: набор тестов тот же,
двойного прогона нет.
- Порог functions самый слабый (74.31% против 91.77%): добавление инструмента с
непокрытым `run` быстрее всего опускает именно его. Это ожидаемо, порог держит
запас.
- DOM-тесты `SchemaToolView`/controls не начаты: браузерные модули исключены из
coverage сознательно, а их перенос в scope — отдельное решение вместе с visual
snapshots, см. Q6f.5.
Не применять один одинаковый smoke-сценарий к image-, text-, generator- и
file-result-инструментам.
#### Q6f.5. Visual snapshots — решение отложено
**Статус:** сознательно не делаем. Это решение, а не долг.
- Механика: `toHaveScreenshot` сравнивает отрендеренную страницу с PNG-baseline,
закоммиченным в `web/e2e/<spec>-snapshots/`. Эталон — картинка из прошлого
коммита, а не замысел дизайнера; обновление идёт через `--update-snapshots` и
ревью картинок.
- Почему отложено:
- визуальный язык сейчас активно меняется, поэтому каждый дизайн-PR
превращается в перегенерацию и ревью картинок, а соблазн обновить снапшот не
глядя появляется постоянно;
- baseline привязан к платформе (ОС, шрифты, особенности рендеринга): снимок с
Windows не совпадёт на `ubuntu-latest`, значит эталоны должны рождаться на
Linux, а локальные прогоны будут постоянно расходиться;
- при матрице из четырёх проектов плюс тема и viewport число файлов Baseline
растёт нелинейно.
- Что закрывает потребность сейчас: e2e-поведение (276 тестов), `lint:tokens` и
token audit, i18n-гейт, ручной чеклист `docs/checklist-manual-testing.md`. Не
закрыто только «элемент визуально наехал» — это ловится чеклистом и ревью.
- Условие возврата: визуальный язык стабилен, изменения редкие, ручной чеклист
перестал справляться. Тогда первым идёт `/kit` в двух темах, а эталоны
генерируются на Linux (docker-образ Playwright или отдельный CI-job).
### Q7. Укрепление lint-инфраструктуры
**Оценка:** M. **Зависимости:** Q1, Q2, Q3.
#### Q7a. Очистка неиспользуемых design tokens
**Статус:** выполнен. Token audit чист: `All app.css tokens are used somewhere`.
- Удалены `--color-text-accent-muted` (light и dark, иначе ломается parity),
`--space-text-s: 0`, `--size-content-max: 880px` и `--duration-m: 0.3s`.
- Проверено, что мёртвые токены — это пыль, а не признак хардкода: дублирующих
значений (`max-width`, `0.3s`, muted-цвет) в `src` нет. Потребители
`space-text-m/xl/2xl`, `size-content-max-wide` и `size-workspace-min`
используются, поэтому шкалы не тронуты.
- Шкалы остались неполными намеренно (`duration-s/l` без `m`, `space-text` без
`s`): мёртвый элемент шкалы хуже, чем «дырявая» шкала, а вернуть его можно
вместе с потребителем.
- Второй пункт («после очистки решить, должен ли unused audit быть
fail-режимом») остаётся открытым: сам audit пока только сообщает.
#### Q7b. Тесты token audit и PostCSS HCT-плагина
**Статус:** выполнен. У обоих не было ни одного теста, хотя HCT-плагин
(`web/scripts/postcss-hct.mjs`, 430 строк) пересчитывает всю палитру.
- `web/scripts/__tests__/postcss-hct.test.mjs` — 17 тестов: literal и
производный `hct()`, seed из `:root` и из hex, цепочка hct-токенов, refs
`h/c/t`, `calc/min/max/clamp`, обёртка hue, клампы tone/chroma, несколько
вызовов в одном объявлении, а также warn-and-keep для неизвестного seed,
циклической ссылки и битого тела `hct()`.
- `web/scripts/__tests__/token-audit.test.mjs` — 15 тестов на parity
(light-only, dark-only, derived, не-цвет), authorship (hct, color-mix,
seed-токены, хардкод с локацией, игнор не-цветов и обычных свойств), unused и
предикаты `isColorValue`/`isDerived`.
- Ожидания не зашивают вывод плагина, а сравнивают его с эквивалентной
литеральной записью: `hct(from var(--seed) h c calc(t + 10))` должен совпасть
с `hct(120 40 60)`, если seed — `hct(120 40 50)`. Иначе тест просто
зафиксировал бы текущее поведение.
- Единственный seam: `checkUnused(root, used = collectUsedTokens())` —
зависимость от чтения `src` стала инъецируемой, production-вызов не изменился.
- `vitest.config.ts` подхватывает `scripts/**/*.test.mjs`, поэтому скрипты
покрываются обычным прогоном и `verify`; `test:rules` не трогали.
- Проверка мутацией выявила дыру: первая версия тестов не ловила кламп tone в
ветке `from` (смена `clamp(t, 0, 100)` на `0, 90` проходила зелёной). Добавлен
отдельный тест на производный кламп; после этого мутация падает.
#### Q7c. Базовый JS recommended для инфраструктуры линтинга
**Статус:** выполнен. `eslint-plugins/**` и `scripts/**` больше не вне линта.
- `web/eslint.config.js`:
`toolingFiles = ["**/eslint-plugins/**/*.js", "**/scripts/**/*.mjs"]` получают
`js.configs.recommended` плюс `languageOptions` с `globals.node` (иначе
`no-undef` ругается на `console`, `process` и `URL` в Node-скриптах).
- Полные TS/svelte-наборы туда не подключаются: это `.mjs` и конфиги, а не
продуктовый код.
- Масштаб оказался небольшим: 28 проблем, из них 26 — `no-undef` на
Node-глобалах и только 2 настоящие: мёртвые импорты `existsSync` и `resolve` в
`scripts/serve-static.mjs`. Линтер сразу нашёл пыль в инфраструктуре, для
которой его раньше не было.
- `eslint-plugins/**` дал ноль проблем, а новые тесты `scripts/__tests__` тоже
попали под правило.
- Проверено пробой: после включения неиспользуемая переменная и вызов
неопределённого глобала в `scripts/` дают ошибки, то есть правило применилось,
а не молча пропустило файлы.
#### Q7d. Решения по оставшимся вопросам
**Статус:** закрыт тремя решениями.
1. **Обычный CSS под те же токен-правила, что Svelte styles — узко.** В `src`
всего один CSS-файл, и он же словарь токенов, поэтому переносить туда
`no-hardcoded-in-svelte` и подобные нечего: stylelint уже проверяет
`src/**/*.css` (префиксы, запрет `!important`, цвет только у brand-токенов),
а `check-tokens.mjs` дублирует проверку цвета. Реальный пробел был один —
`var()` может ссылаться на несуществующий токен, — его закрыл новый модуль
`web/scripts/token-audit/refs.mjs` (см. Q7e). Широкую унификацию механизмов
решено не делать: это лишний слой поверх дублирующихся проверок.
2. **Конфиги под рекомендации — только JS.** `*.config.js`
(`eslint`/`postcss`/`stylelint`) добавлены в `toolingFiles`; по измерению они
дают 0 проблем, поэтому это бесплатно. TS-конфиги (`vite`, `vitest`,
`playwright`) не подключены: им потребовался бы `tseslint`-парсер и запись
`vite.config.ts` в `allowDefaultProject`, а выигрыш близок к нулю. Попутно
удалена мёртвая запись `svelte.config.js` в `allowDefaultProject` — такого
файла нет.
3. **Unused-аудит остаётся предупреждением.** Правило «новый токен — вместе с
потребителем» закреплено в `AGENTS.md`. Причина: `verify` входит в
deploy-workflow, поэтому стилевой долг блокировал бы деплой сайта; кроме
того, чтение токена из JS (`getPropertyValue`) аудит не видит и дал бы ложное
срабатывание. Baseline чистый, пыль вычищена в Q7a.
#### Q7e. Проверка резолва `var()` в `app.css`
**Статус:** выполнен. Аудит падает, если `var(--x)` в `app.css` указывает на
токен, которого нет в этом же файле.
- `web/scripts/token-audit/refs.mjs` + вызов в `check-tokens.mjs`; режим —
ошибка, а не предупреждение, потому что это дефект (объявление молча
отбрасывается), а не стилистический долг.
- `var(--x, fallback)` не считается ошибкой: fallback легален без определения
токена.
- 7 тестов в `scripts/__tests__/token-audit.test.mjs`: резолв в обеих темах,
опечатка с локацией, fallback, дедуп повторов, ссылка внутри определения
токена, `var()` в обычном свойстве.
- Проверено на живом файле: временная опечатка `var(--color-bordr)` дала
`Unresolved var() references` и код выхода 1; `app.css` восстановлен байт в
байт.
#### Q7f. Оставшиеся направления
- Новые правила подключать только после fixtures, `test:rules` и baseline.
- Если в коде появится чтение токенов из JS, `collectUsedTokens` придётся
расширить — иначе unused-аудит начнёт считать используемые токены пылью.
### Q8. Новые автоматические правила
**Оценка:** L. **Зависимости:** Q4, Q7.
#### Дублирование HTML/Svelte-разметки
Сначала провести report-only аудит новых `components/**` и `routes/**`.
Определить:
- минимальный размер клона;
- нормализацию Svelte blocks и динамических классов;
- список допустимых fixture/allowlist-случаев.
Только после оценки false positives выбирать между готовым detector и
собственным AST-правилом. Не включать detector в blocking gate сразу.
#### Пользовательские тексты вне i18n
Сделать warn-only пилот с исключениями для:
- бренда и версии;
- форматов и имён файлов;
- `X`/`Y` и технических значений;
- registry source strings;
- `/kit`;
- тестовых данных и внутренних сообщений.
Blanket-запрет всех string literals не использовать.
#### Согласованность комментариев
Автоматический lint не делать. Это review/process policy: комментарий должен
объяснять причину исключения, а `TODO`/`FIXME` — ссылаться на backlog или issue
и иметь критерий закрытия.
## 4. Что описать в `AGENTS.md`
В `AGENTS.md` добавить только следующие неочевидные правила:
1. Какой набор команд считается полным quality gate и что означает успешный
результат при warnings.
2. Production-scope и политику удаления v1: новые изменения относятся только к
production UI; исторические UI-v1-упоминания остаются в архиве.
3. Политику e2e: semantic locators, допустимый `data-testid`, запрет
бессмысленных counts/classes/alt/exact prose.
4. Политику i18n: новые пользовательские тексты через i18n; registry source,
brand, formats и `/kit` — явные исключения.
5. Жизненный цикл `test.fixme`: только для зарегистрированного бага, с ссылкой;
исправление и удаление теста происходят вместе.
6. Комментарии и `TODO`/`FIXME` должны объяснять «почему» и иметь ссылку на
работу.
7. Shared-модули `core`/`theme` требуют проверки production-потребителей.
Синтаксические списки ESLint-правил и подробные токены остаются в
`web/eslint-plugins/README.md`.
### Политика документации для агентов
`AGENTS.md` не должен превращаться в полный справочник проекта. В нём оставляем
только обязательный рабочий контракт: команды, production-scope, ключевые
инварианты, исключения и ссылки на подробные документы. Если агенту нужно понять
причину или workflow, в `AGENTS.md` должна быть короткая понятная формулировка;
длинные примеры, точные процедуры, таблицы и критерии проверки переносятся в
`docs/` или в README рядом с соответствующей подсистемой.
Принцип выбора места документа:
1. **Поведение агента, которое нельзя пропустить** — кратко в `AGENTS.md`.
2. **Точная команда, скрипт, параметр или критерий качества** — в
`docs/quality-gates.md` или в README соответствующей подсистемы.
3. **Архитектурное решение, поток данных и границы модулей** — в
`docs/architecture-overview.md` или существующем плане.
4. **Реализация конкретного lint-правила и его fixtures** — только в
`web/eslint-plugins/README.md` и исходниках правила.
5. **Продуктовая задача, backlog и ручная приёмка** — в `docs/backlog.md`,
активном плане или `docs/checklist-manual-testing.md`.
Подробные документы не дублируются в нескольких местах: у документа должен быть
один источник истины, а остальные файлы ссылаются на него. Все подробные
материалы хранятся в `docs/`, чтобы не засорять корень репозитория.
Активный документ должен иметь статус (`draft`, `active`, `completed` или
`archived`) и понятную область применения. Архивные планы не используются как
текущие критерии приёмки без отдельной сверки с кодом. Новый документ создаётся
только когда темы нет в существующем файле; иначе обновляется существующий.
### Архивирование и срок хранения
Завершённые, отменённые или явно устаревшие документы переносятся в
`docs/archive/`, а не остаются в корне `docs/` как рабочие планы. При переносе
добавляются дата архивирования, дата удаления и причина. Архивный документ —
временная справка для быстрого просмотра прошлых решений, но не источник текущих
требований.
Срок хранения после архивирования — 30 дней. После этой даты документ можно
удалить после проверки ссылок; если он снова нужен, его восстанавливают из git
history или пересоздают как новый активный документ. Подробная процедура и
шаблон метаданных находятся в `docs/archive/README.md`.
### Какие документы подготовить
| Документ | Назначение | Когда создавать/обновлять |
| ------------------------------- | --------------------------------------------------------------------------------------- | --------------------------------------------------------------------- |
| `docs/README.md` | Индекс активных документов, карта источников истины и краткий порядок чтения для агента | Q0; при добавлении нового активного документа |
| `docs/agent-workflow.md` | Подробный workflow: исследование, изменение, проверка и обновление документации | Q0–Q1; только для процессов, не описанных кратко в `AGENTS.md` |
| `docs/quality-gates.md` | Команды проверки, semantics errors/warnings, порядок запуска и требования CI | Q1; при изменении scripts/config/CI |
| `docs/testing-strategy.md` | Слои тестов, locator policy, i18n assertions, fixtures, `test.fixme` и правила e2e | Q4; при изменении тестовой архитектуры |
| `docs/architecture-overview.md` | Карта `core`, registry, executor, production UI и shared-модулей | Q5 или перед крупным рефакторингом; не дублировать существующие планы |
| `web/eslint-plugins/README.md` | Детали custom lint rules, token policy и fixtures | Уже существует; менять только вместе с правилами |
| `docs/archive/README.md` | Политика временного архива, даты и удаления через 30 дней | Q0; обновлять при изменении архивной политики |
Не создавать отдельные `docs/commands.md`, `docs/known-issues.md` или копию
backlog: команды принадлежат `quality-gates`, известные проблемы — backlog и
checklist. `README.md` в корне остаётся кратким обзором продукта, а не вторым
руководством агента.
## 5. Порядок PR
1. **Q0** — синхронизация backlog/checklist/AGENTS.
2. **Q1** — `verify` и PR quality gate.
3. **Q2** — i18n parity и strict warning gate.
4. **Q3** — удалить v1 отдельной атомарной задачей.
5. **Q4** — тестовый контракт и stale-тесты для production UI.
6. **Q5** — preview-баги и снятие `fixme`.
7. **Q6** — расширенное покрытие.
8. **Q7** — тесты lint-инфраструктуры и unused tokens.
9. **Q8** — report-only duplicate/text audits; blocking — только после baseline.
Каждый PR должен быть атомарным и небольшим; не объединять `SchemaPreview`
refactor, новые i18n-правила и исправление продуктовых багов в одну работу.
## 6. Отдельные направления и что не начинать автоматически
- полноценный browser/visual/coverage matrix;
- полноценная PWA/API/WASM-платформа;
- `SchemaPreview` state/view-model refactor;
- plural-формы до модели словаря;
- универсальный линтер одинаковых комментариев;
- batch/архивы, region-инструменты и остальные продуктовые волны из backlog.
## 7. Решения, которые нужно принять на ревью
- Какие executor/worker/fallback сценарии войдут в первый Q6 coverage PR?
- Нужны ли browser/mobile matrix и visual snapshots до coverage thresholds?
Решено: matrix — Q6f.3, coverage — Q6f.4, visual snapshots отложены с
обоснованием в Q6f.5.
- Какие unused design tokens можно удалить, а какие должны остаться? Решено в
Q7a: удалены все четыре, audit чист.
- Какие browser/e2e проверки должны быть обязательными для PR, а какие —
отдельным ночным прогоном?
### Отложенное решение: условия matrix и blocking e2e
Окончательное решение про `e2e` в CI принимается **не сейчас**, а после
пересмотра условий тестов. К этому моменту нужно вернуться и решить одним шагом:
1. Пересмотреть условия matrix: какие спецификации обязаны идти в
`browserContractTests` и `mobileTests`, нужен ли `png-fixtures` вне Chromium
и что делать с engine-специфичными расхождениями декодеров.
2. Накопить стабильные прогоны `e2e` на раннере (сейчас ориентир — 276 тестов,
~3 мин на job) без падений по таймингу.
3. Только после этого решать, делать ли `e2e` blocking, и если да — снимать
`continue-on-error`, иначе общий conclusion прогона останется зелёным при
красном `e2e` (проверено на run `36225579363`).
До этого решения `e2e` остаётся non-blocking baseline, а его фактический статус
проверяется по отдельному job, а не по conclusion прогона.