mirror of
https://github.com/Ku6epXBOCTuK/easy-png-tools.git
synced 2026-09-14 21:46:35 +00:00
docs: add new audit plan - resolve old audit flow
This commit is contained in:
@@ -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-список не
|
||||||
|
заводим**. Сравниваем всё дерево от `<body>` (без пропуска служебного, кроме
|
||||||
|
Svelte-атрибутов и комментов гидрации), чтобы получить полное структурное
|
||||||
|
соответствие, а не только контента. Шапка/футер/переключатель языка — часть
|
||||||
|
верности; позже, после полного фикса дизайна, всё равно планируется рефакторинг
|
||||||
|
компонентов, где оболочку приведём в порядок и унифицируем. Аудит на этом этапе
|
||||||
|
просто показывает все расхождения, включая оболочечные.
|
||||||
|
|
||||||
|
### Дифф
|
||||||
|
|
||||||
|
Для каждого маршрута — набор дельт:
|
||||||
|
|
||||||
|
- `added` — у нас, нет в рефе;
|
||||||
|
- `removed` — в рефе, нет у нас;
|
||||||
|
- `moved` — тот же текст/тег, другой путь (перепутанный порядок/родитель);
|
||||||
|
- `textMismatch` — нормализованный текст различается (в т.ч. RU vs EN).
|
||||||
|
|
||||||
|
Класс-мисматч — **только информационно**, не блокирует (классы свои по
|
||||||
|
решению №2). Счётчики: `added / removed / moved / textMismatch`.
|
||||||
|
|
||||||
|
### Рендеринг
|
||||||
|
|
||||||
|
- **наше:** dev-сервер на `5179` (как сейчас в `audit.mjs`).
|
||||||
|
- **реф:** `pathToFileURL` к `refs-html/<ref>.html` (`file://`). Реф —
|
||||||
|
самодостаточный (инлайн `<style>`), рендерится полностью. Шрифты для фазы A
|
||||||
|
не важны (сравниваем структуру/текст, не растеризацию).
|
||||||
|
- Переиспользуем из текущего `audit.mjs`: `discoverRoutes()`, `EXCLUDE`
|
||||||
|
(`/preview`), `DYNAMIC` (`tools/[id]` → `gradient.html` /
|
||||||
|
`background-remover.html`), `refNameFor()`.
|
||||||
|
|
||||||
|
### Шаги (на каждом маршруте)
|
||||||
|
|
||||||
|
1. Прогнать `pnpm refs-dom-audit`, получить дельты.
|
||||||
|
2. Поправить структуру Svelte под реф (добавить/убрать/переставить узлы,
|
||||||
|
вынести тексты в `t()` с EN-базой).
|
||||||
|
3. Перезапустить, пока `added/removed/moved/textMismatch` ≃ 0 (кроме `IGNORE`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Фаза B — CSS-аудит по стабильному DOM (`scripts/audit-css.mjs`)
|
||||||
|
|
||||||
|
**Цель:** на совпадающем дереве сравнить вычисленные стили по тому же
|
||||||
|
структурному пути и получить «элемент по пути X: свойство → наше/реф».
|
||||||
|
|
||||||
|
### Сравниваемые свойства (computed)
|
||||||
|
|
||||||
|
`borderRadius`, позиция/размер (`getBoundingClientRect`: x/y/w/h),
|
||||||
|
`padding` (все стороны), `margin`, `color`, `backgroundColor`,
|
||||||
|
`borderTop/Right/Bottom/Left Width|Color`, `fontFamily`, `fontSize`,
|
||||||
|
`fontWeight`, `letterSpacing`, `lineHeight`, `gap`, `display`, `boxShadow`.
|
||||||
|
|
||||||
|
### Дифф
|
||||||
|
|
||||||
|
Для каждого состыкованного по пути элемента — список свойств с дельтой
|
||||||
|
(наше значение → реф). Элементы, не совпавшие по пути (остаток из фазы A),
|
||||||
|
идут в `unmatched`. Отчёт: элемент → свойство → наше/реф. Правится одной
|
||||||
|
строкой CSS, без брутфорса.
|
||||||
|
|
||||||
|
### Шаги
|
||||||
|
|
||||||
|
1. Прогнать `pnpm refs-css-audit` (только после того, как фаза A по маршруту
|
||||||
|
дала ≃ 0 структурных расхождений).
|
||||||
|
2. Поправить стили в `design2.css` / стилях компонентов.
|
||||||
|
3. Перезапустить, пока delta% по свойствам ≃ 0.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Порядок маршрутов и особенности
|
||||||
|
|
||||||
|
1. **`/preview/demo`** (↔ `demo.html`) — уже ближе всего; идеальная «обкатка»
|
||||||
|
тулинга фаз A и B. Здесь же проверим, что `Download result` стоит в шапке
|
||||||
|
`.preview-top` (как в рефе), а не внизу панели.
|
||||||
|
2. **`/preview/list-tools`** (↔ `list-tools.html`) — объёмнее; **зависит от
|
||||||
|
`docs/plan-list-tools.md`**: реальный аудит имеет смысл только после того,
|
||||||
|
как страница переписана на `CatalogHeader`/`CatalogToolbar`/`CatalogGroup`
|
||||||
|
(шаги 1–5 плана list-tools). То есть фаза A/B для list-tools стартует после
|
||||||
|
landing этих шагов.
|
||||||
|
3. **`tools/linear-gradient-png`** (↔ `gradient.html`) и
|
||||||
|
**`tools/remove-background-png`** (↔ `background-remover.html`) — **последние**.
|
||||||
|
Известные расхождения: наши инструменты могут иметь **другие наборы
|
||||||
|
параметров / UI**, чем конкретные рефовые тулзы. Ожидаются структурные и
|
||||||
|
стилевые дельты, которые не сводятся к «подровнять паддинг». Для них —
|
||||||
|
триаж после прогона:
|
||||||
|
- реальный разрыв верности → фиксим;
|
||||||
|
- принципиальное отличие (параметр, которого нет в реф-тулзе; иной контрол)
|
||||||
|
→ либо аккуратно приводим наш UI параметра к рефу, либо **документируем
|
||||||
|
как допустимое расхождение / заносим в allowlist** конфига аудита
|
||||||
|
(селектор + причина). «Скипы/фиксы» для инструментов оформляем явно, чтобы
|
||||||
|
аудит не шумел на осознанных отличиях.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Структура скриптов
|
||||||
|
|
||||||
|
- `scripts/audit-dom.mjs` — фаза A (нормализация + структурный дифф).
|
||||||
|
- `scripts/audit-css.mjs` — фаза B (матчинг по пути + дифф стилей).
|
||||||
|
- `package.json`: `refs-dom-audit`, `refs-css-audit` (опционально
|
||||||
|
`refs-audit`, прогоняющий обе фазы).
|
||||||
|
- Переиспользуем из старого `audit.mjs`: `discoverRoutes`, `EXCLUDE`,
|
||||||
|
`DYNAMIC`, `refNameFor`, `waitFor` (fast-fail на 4xx/5xx).
|
||||||
|
- Отчёты: `web/audit/dom-report.{md,json}` и `web/audit/css-report.{md,json}`
|
||||||
|
(старый `audit-report.*` заменяем).
|
||||||
|
|
||||||
|
## Верификация (после каждого коммита)
|
||||||
|
|
||||||
|
- `pnpm lint`, `pnpm exec svelte-check --tsconfig ./tsconfig.json`,
|
||||||
|
`pnpm build` — без ошибок.
|
||||||
|
- `pnpm refs-dom-audit` (фаза A) / `pnpm refs-css-audit` (фаза B) по
|
||||||
|
затронутому маршруту: счётчики расхождений не растут без причины.
|
||||||
|
|
||||||
|
## Риски / заметки
|
||||||
|
|
||||||
|
- **Стабильность матчинга:** нужна аккуратная нормализация (сортировка
|
||||||
|
классов, игнор svelte-атрибутов/`data-*`, игнор whitespace-текстовых узлов,
|
||||||
|
игнор внутренностей SVG-иконок). Подстраивается по первым прогонам demo.
|
||||||
|
- **Оболочка** (TopBar/footer/язык) сейчас отличается по дизайну — сводим к рефу
|
||||||
|
в рамках полного соответствия (без IGNORE); финальная унификация компонентов —
|
||||||
|
отдельным рефакторингом позже.
|
||||||
|
- **Динамические маршруты:** `DYNAMIC` уже есть; оставляем.
|
||||||
|
- **Hover / адаптив / тёмная тема** — вне объёма статического аудита (initial
|
||||||
|
state, 1440×900, light). Отмечаем как будущие прогоны.
|
||||||
|
- **list-tools** зависит от landing `plan-list-tools.md` (шаги 1–5) — не
|
||||||
|
аудируем вслепую.
|
||||||
Reference in New Issue
Block a user