diff --git a/docs/plan-audit.md b/docs/plan-audit.md new file mode 100644 index 0000000..a3af038 --- /dev/null +++ b/docs/plan-audit.md @@ -0,0 +1,207 @@ +# План: аудит верности `preview/*` против `refs-html` (DOM → CSS) + +> Статус: к выполнению. Ветка `feat/redesign`. +> Источник правды по пикселям: `refs-html/*.html` (статический экспорт рефа). +> Актуальный рабочий аудит сейчас — `pnpm refs-audit` (`scripts/audit.mjs`), но он +> **сломан** (см. «Почему текущий аудит не работает») и подлежит замене. + +## Почему текущий аудит не работает + +`scripts/audit.mjs` сопоставляет элементы **только по тексту** и берёт в +сравнение **только листовые узлы** (у которых нет детей). Контейнеры/карточки/ +панели, несущие `border-radius`, отступы и позиционирование, в сравнение +**вообще не попадают** (`byText` кладёт в мапу только элементы с непустым +`text`). Следствия: + +- разница радиусов у карточек невидима (карточка — родитель без своего текста); +- позиция кнопки (`Download result`) не ловится — это структурная разница; +- `onlyOurs / onlyRef` — это просто счётчик текстовых строк на одной стороне, + **не метрика верности**. + +Плюс он не делает пиксельного сравнения, то есть принципиально не может сказать +«выглядит по-другому». Это текстовый дифф стилей листовых текстов — и всё. + +## Решения (подтверждены автором) + +1. **Копирайт превью — через i18n `t()` с EN-базой.** Сейчас RU-литералы + хардкодом только в `preview/+page.svelte:26-27` и + `preview/list-tools/+page.svelte:31` (эти страницы не используют i18n, в + отличие от `(old)/` и компонентов `DownloadButton`/`ChainToolBlock`/…). + Заводим их через `t()` — совпадёт с рефом (EN). +2. **Имена классов НЕ сводим к рефу.** Матчинг делаем по **структурному пути**, + а не по классу. Компоненты `kit/` остаются идиоматичными, Stage 0 не + откатываем. Требуется только эквивалентность *структуры* (теги, вложенность, + порядок сиблингов, наличие узлов, текст). +3. **Пиксельный дифф (Playwright + pixelmatch) пока НЕ делаем.** DOM+CSS-аудита + достаточно; добавим как сеть позже, если стили что-то упустят. +4. **Порядок прогона маршрутов:** `demo` → `list-tools` → затем 2 инструмента + (`tools/linear-gradient-png` ↔ `gradient.html`, + `tools/remove-background-png` ↔ `background-remover.html`). + +## Принципы + +- Никаких правок старого дизайна `(old)/` и старого `ui/`. +- Коммиты атомарные (< ~500 строк), по 1–2 компонента/скрипта; после каждого — + `lint` + `svelte-check` + `build` + соответствующий аудит (diff не должен + расти без причины). +- Коммиты делает автор (агент не коммитит). +- Шрифты уже совпадают: `design2.css:1-6` грузит IBM Plex Sans/Mono через + `@fontsource`, токены `:34-35` — `"IBM Plex Sans"/"IBM Plex Mono"` (как в рефе + `demo.html:67-68`). Пиксельный шум от шрифтов не предвидится. + +## Подход: две фазы + +Корень проблемы — нет **стабильного ключа сопоставления**. Разбиваем на две +фазы: сначала доводим DOM до структурного равенства с рефом (фаза A), затем на +стабильном DOM сравниваем стили по тому же пути (фаза B). Фаза A сама по себе +ловит именно то, из-за чего возникли претензии (кнопка не в том родителе, +лишние/недостающие обёртки, перепутанный порядок) — и без эстетических решений. + +--- + +## Фаза A — DOM/структурный аудит (`scripts/audit-dom.mjs`) + +**Цель:** Svelte рендерит структуру, идентичную `refs-html` (дерево тегов, +вложенность, порядок сиблингов, наличие узлов, текст). Имена классов могут +отличаться. + +### Нормализация узла (ключ сравнения) + +Для каждого элемента (пропускаем `SCRIPT`/`STYLE`/`NOSCRIPT` и скрытые +поддеревья, игнорируем служебное): + +- **тег** (`tagName` в lower-case); +- **классы** — как отсортированный набор (для инфо, не для матчинга); +- **нормализованный текст** — `trim`, `collapse-whitespace`, без SVG-иконок + внутри (текст иконок игнорируем); +- **порядок детей** — позиция среди сиблингов (`nth-child`). + +Матчинг — по **структурному пути** от корня сравнения: +`tag:nth-child(i) > tag:nth-child(j) …`. Не по классу и не по тексту. + +### Полное соответствие, без IGNORE + +Оболочка (наш `TopBar`/`AppShell`/`preview-footer` vs рефовый +`.topbar`/`.footer`/`.language`) тоже сводим к рефу — **IGNORE-список не +заводим**. Сравниваем всё дерево от `` (без пропуска служебного, кроме +Svelte-атрибутов и комментов гидрации), чтобы получить полное структурное +соответствие, а не только контента. Шапка/футер/переключатель языка — часть +верности; позже, после полного фикса дизайна, всё равно планируется рефакторинг +компонентов, где оболочку приведём в порядок и унифицируем. Аудит на этом этапе +просто показывает все расхождения, включая оболочечные. + +### Дифф + +Для каждого маршрута — набор дельт: + +- `added` — у нас, нет в рефе; +- `removed` — в рефе, нет у нас; +- `moved` — тот же текст/тег, другой путь (перепутанный порядок/родитель); +- `textMismatch` — нормализованный текст различается (в т.ч. RU vs EN). + +Класс-мисматч — **только информационно**, не блокирует (классы свои по +решению №2). Счётчики: `added / removed / moved / textMismatch`. + +### Рендеринг + +- **наше:** dev-сервер на `5179` (как сейчас в `audit.mjs`). +- **реф:** `pathToFileURL` к `refs-html/.html` (`file://`). Реф — + самодостаточный (инлайн `