From 9ff732f2cfa0b4e5d24a4376103681940063c7b5 Mon Sep 17 00:00:00 2001 From: Ku6epXBOCTuK Date: Sat, 29 Aug 2026 06:39:56 +0500 Subject: [PATCH] chore: add new audit-dom script and make dom audit --- web/audit/dom-report.json | 416 ++++++++++++++++++++++++++++++++++++++ web/audit/dom-report.md | 95 +++++++++ web/package.json | 3 +- web/scripts/audit-dom.mjs | 388 +++++++++++++++++++++++++++++++++++ 4 files changed, 901 insertions(+), 1 deletion(-) create mode 100644 web/audit/dom-report.json create mode 100644 web/audit/dom-report.md create mode 100644 web/scripts/audit-dom.mjs diff --git a/web/audit/dom-report.json b/web/audit/dom-report.json new file mode 100644 index 0000000..36c87e8 --- /dev/null +++ b/web/audit/dom-report.json @@ -0,0 +1,416 @@ +{ + "generatedAt": "2026-08-29T01:35:12.843Z", + "reports": [ + { + "route": "/preview/demo", + "ref": "demo.html", + "deltas": [ + { + "path": "body > div:1", + "type": "tagMismatch", + "ours": "div", + "ref": "main" + }, + { + "path": "body > div:1 > div:1 > header:1", + "type": "added", + "tag": "header", + "text": "", + "nodes": 20 + }, + { + "path": "body > div:1 > div:1 > div:2", + "type": "tagMismatch", + "ours": "div", + "ref": "section" + }, + { + "path": "body > div:1 > div:1 > div:2 > div:1", + "type": "added", + "tag": "div", + "text": "", + "nodes": 1 + }, + { + "path": "body > div:1 > div:1 > div:2 > div:2", + "type": "textMismatch", + "ours": "", + "ref": "PNG PROCESSING WORKSPACE" + }, + { + "path": "body > div:1 > div:1 > div:2 > div:2 > div:1", + "type": "added", + "tag": "div", + "text": "", + "nodes": 203 + }, + { + "path": "body > div:1 > div:1 > div:2 > div:2 > span:1", + "type": "removed", + "tag": "span", + "text": "/", + "nodes": 1 + }, + { + "path": "body > div:1 > div:1 > div:2 > div:2", + "type": "removed", + "tag": "div", + "text": "", + "nodes": 8 + }, + { + "path": "body > div:1 > div:1 > div:2 > div:3", + "type": "removed", + "tag": "div", + "text": "", + "nodes": 7 + }, + { + "path": "body > div:1 > div:1 > div:2 > div:4", + "type": "removed", + "tag": "div", + "text": "", + "nodes": 97 + }, + { + "path": "body > div:1 > div:1 > div:2 > div:5", + "type": "removed", + "tag": "div", + "text": "", + "nodes": 5 + }, + { + "path": "body > div:1 > div:1 > footer:3", + "type": "added", + "tag": "footer", + "text": "", + "nodes": 3 + }, + { + "path": "body > div:1 > div:1 > section:2", + "type": "removed", + "tag": "section", + "text": "", + "nodes": 65 + }, + { + "path": "body > div:1 > header:1", + "type": "removed", + "tag": "header", + "text": "", + "nodes": 21 + }, + { + "path": "body > div:1 > footer:3", + "type": "removed", + "tag": "footer", + "text": "", + "nodes": 6 + } + ], + "counts": { + "added": 4, + "removed": 8, + "tagMismatch": 2, + "textMismatch": 1, + "nodesAdded": 227, + "nodesRemoved": 210 + } + }, + { + "route": "/preview/list-tools", + "ref": "list-tools.html", + "deltas": [ + { + "path": "body > div:1", + "type": "tagMismatch", + "ours": "div", + "ref": "main" + }, + { + "path": "body > div:1 > div:1 > header:1", + "type": "added", + "tag": "header", + "text": "", + "nodes": 20 + }, + { + "path": "body > div:1 > div:1 > div:2 > div:1", + "type": "added", + "tag": "div", + "text": "", + "nodes": 1 + }, + { + "path": "body > div:1 > div:1 > div:2 > div:2 > section:1", + "type": "added", + "tag": "section", + "text": "", + "nodes": 963 + }, + { + "path": "body > div:1 > div:1 > div:2 > div:2 > div:1", + "type": "removed", + "tag": "div", + "text": "EASY-PNG-TOOLS / CATALOG", + "nodes": 1 + }, + { + "path": "body > div:1 > div:1 > div:2 > div:2 > h1:2", + "type": "removed", + "tag": "h1", + "text": "Tool catalog", + "nodes": 1 + }, + { + "path": "body > div:1 > div:1 > div:2 > div:2 > p:3", + "type": "removed", + "tag": "p", + "text": "Focused utilities for working with PNG. Inspect, transform, and export — locally in your browser.", + "nodes": 1 + }, + { + "path": "body > div:1 > div:1 > div:2 > div:2", + "type": "removed", + "tag": "div", + "text": "", + "nodes": 4 + }, + { + "path": "body > div:1 > div:1 > footer:3", + "type": "textMismatch", + "ours": "", + "ref": "ALL OPERATIONS RUN LOCALLY YOUR FILES NEVER LEAVE THIS DEVICE" + }, + { + "path": "body > div:1 > div:1 > footer:3 > span:1", + "type": "added", + "tag": "span", + "text": "v0.1.0", + "nodes": 1 + }, + { + "path": "body > div:1 > div:1 > footer:3 > span:2", + "type": "added", + "tag": "span", + "text": "© 2026", + "nodes": 1 + }, + { + "path": "body > div:1 > div:1 > footer:3 > span:1", + "type": "removed", + "tag": "span", + "text": "•", + "nodes": 1 + }, + { + "path": "body > div:1 > div:1 > div:2", + "type": "removed", + "tag": "div", + "text": "", + "nodes": 13 + }, + { + "path": "body > div:1 > div:1 > div:3", + "type": "removed", + "tag": "div", + "text": "", + "nodes": 255 + }, + { + "path": "body > div:1 > header:1", + "type": "removed", + "tag": "header", + "text": "", + "nodes": 21 + } + ], + "counts": { + "added": 5, + "removed": 8, + "tagMismatch": 1, + "textMismatch": 1, + "nodesAdded": 986, + "nodesRemoved": 297 + } + }, + { + "route": "/preview/tools/linear-gradient-png", + "ref": "gradient.html", + "deltas": [ + { + "path": "body > div:1", + "type": "tagMismatch", + "ours": "div", + "ref": "main" + }, + { + "path": "body > div:1 > div:1 > header:1", + "type": "added", + "tag": "header", + "text": "", + "nodes": 20 + }, + { + "path": "body > div:1 > div:1 > div:2", + "type": "textMismatch", + "ours": "", + "ref": "PNG PROCESSING SINGLE TOOL" + }, + { + "path": "body > div:1 > div:1 > div:2 > div:1", + "type": "added", + "tag": "div", + "text": "", + "nodes": 1 + }, + { + "path": "body > div:1 > div:1 > div:2 > div:2", + "type": "added", + "tag": "div", + "text": "", + "nodes": 162 + }, + { + "path": "body > div:1 > div:1 > div:2 > span:1", + "type": "removed", + "tag": "span", + "text": "/", + "nodes": 1 + }, + { + "path": "body > div:1 > div:1 > footer:3", + "type": "added", + "tag": "footer", + "text": "", + "nodes": 3 + }, + { + "path": "body > div:1 > div:1 > div:2", + "type": "removed", + "tag": "div", + "text": "", + "nodes": 6 + }, + { + "path": "body > div:1 > div:1 > div:3", + "type": "removed", + "tag": "div", + "text": "", + "nodes": 66 + }, + { + "path": "body > div:1 > header:1", + "type": "removed", + "tag": "header", + "text": "", + "nodes": 21 + }, + { + "path": "body > div:1 > footer:3", + "type": "removed", + "tag": "footer", + "text": "", + "nodes": 5 + } + ], + "counts": { + "added": 4, + "removed": 5, + "tagMismatch": 1, + "textMismatch": 1, + "nodesAdded": 186, + "nodesRemoved": 99 + } + }, + { + "route": "/preview/tools/remove-background-png", + "ref": "background-remover.html", + "deltas": [ + { + "path": "body > div:1", + "type": "tagMismatch", + "ours": "div", + "ref": "main" + }, + { + "path": "body > div:1 > div:1 > header:1", + "type": "added", + "tag": "header", + "text": "", + "nodes": 20 + }, + { + "path": "body > div:1 > div:1 > div:2", + "type": "textMismatch", + "ours": "", + "ref": "PNG PROCESSING SINGLE TOOL" + }, + { + "path": "body > div:1 > div:1 > div:2 > div:1", + "type": "added", + "tag": "div", + "text": "", + "nodes": 1 + }, + { + "path": "body > div:1 > div:1 > div:2 > div:2", + "type": "added", + "tag": "div", + "text": "", + "nodes": 162 + }, + { + "path": "body > div:1 > div:1 > div:2 > span:1", + "type": "removed", + "tag": "span", + "text": "/", + "nodes": 1 + }, + { + "path": "body > div:1 > div:1 > footer:3", + "type": "added", + "tag": "footer", + "text": "", + "nodes": 3 + }, + { + "path": "body > div:1 > div:1 > div:2", + "type": "removed", + "tag": "div", + "text": "", + "nodes": 6 + }, + { + "path": "body > div:1 > div:1 > div:3", + "type": "removed", + "tag": "div", + "text": "", + "nodes": 71 + }, + { + "path": "body > div:1 > header:1", + "type": "removed", + "tag": "header", + "text": "", + "nodes": 21 + }, + { + "path": "body > div:1 > footer:3", + "type": "removed", + "tag": "footer", + "text": "", + "nodes": 5 + } + ], + "counts": { + "added": 4, + "removed": 5, + "tagMismatch": 1, + "textMismatch": 1, + "nodesAdded": 186, + "nodesRemoved": 104 + } + } + ] +} \ No newline at end of file diff --git a/web/audit/dom-report.md b/web/audit/dom-report.md new file mode 100644 index 0000000..90e70c6 --- /dev/null +++ b/web/audit/dom-report.md @@ -0,0 +1,95 @@ +# DOM audit (phase A) + +_2026-08-29T01:35:12.853Z_ + +## Сводка + +| route | ref | added | removed | tag | text | +|---|---|---|---|---|---| +| `/preview/demo` | demo.html | 4 | 8 | 2 | 1 | +| `/preview/list-tools` | list-tools.html | 5 | 8 | 1 | 1 | +| `/preview/tools/linear-gradient-png` | gradient.html | 4 | 5 | 1 | 1 | +| `/preview/tools/remove-background-png` | background-remover.html | 4 | 5 | 1 | 1 | + +## /preview/demo (vs demo.html) + +- added: **4** (≈227 узл.), removed: **8** (≈210 узл.), tag: **2**, text: **1** + +### Расхождения (топ 15) + +- `body > div:1` **tag**: ours `div` → ref `main` +- `body > div:1 > div:1 > header:1` **added** (`header` · 20 узл.) +- `body > div:1 > div:1 > div:2` **tag**: ours `div` → ref `section` +- `body > div:1 > div:1 > div:2 > div:1` **added** (`div` · 1 узл.) +- `body > div:1 > div:1 > div:2 > div:2` **text**: ours `` → ref `PNG PROCESSING WORKSPACE` +- `body > div:1 > div:1 > div:2 > div:2 > div:1` **added** (`div` · 203 узл.) +- `body > div:1 > div:1 > div:2 > div:2 > span:1` **removed** (`span` "/" · 1 узл.) +- `body > div:1 > div:1 > div:2 > div:2` **removed** (`div` · 8 узл.) +- `body > div:1 > div:1 > div:2 > div:3` **removed** (`div` · 7 узл.) +- `body > div:1 > div:1 > div:2 > div:4` **removed** (`div` · 97 узл.) +- `body > div:1 > div:1 > div:2 > div:5` **removed** (`div` · 5 узл.) +- `body > div:1 > div:1 > footer:3` **added** (`footer` · 3 узл.) +- `body > div:1 > div:1 > section:2` **removed** (`section` · 65 узл.) +- `body > div:1 > header:1` **removed** (`header` · 21 узл.) +- `body > div:1 > footer:3` **removed** (`footer` · 6 узл.) + + +## /preview/list-tools (vs list-tools.html) + +- added: **5** (≈986 узл.), removed: **8** (≈297 узл.), tag: **1**, text: **1** + +### Расхождения (топ 15) + +- `body > div:1` **tag**: ours `div` → ref `main` +- `body > div:1 > div:1 > header:1` **added** (`header` · 20 узл.) +- `body > div:1 > div:1 > div:2 > div:1` **added** (`div` · 1 узл.) +- `body > div:1 > div:1 > div:2 > div:2 > section:1` **added** (`section` · 963 узл.) +- `body > div:1 > div:1 > div:2 > div:2 > div:1` **removed** (`div` "EASY-PNG-TOOLS / CATALOG" · 1 узл.) +- `body > div:1 > div:1 > div:2 > div:2 > h1:2` **removed** (`h1` "Tool catalog" · 1 узл.) +- `body > div:1 > div:1 > div:2 > div:2 > p:3` **removed** (`p` "Focused utilities for working with PNG. Inspect, transform, and export — locally in your browser." · 1 узл.) +- `body > div:1 > div:1 > div:2 > div:2` **removed** (`div` · 4 узл.) +- `body > div:1 > div:1 > footer:3` **text**: ours `` → ref `ALL OPERATIONS RUN LOCALLY YOUR FILES NEVER LEAVE THIS DEVICE` +- `body > div:1 > div:1 > footer:3 > span:1` **added** (`span` "v0.1.0" · 1 узл.) +- `body > div:1 > div:1 > footer:3 > span:2` **added** (`span` "© 2026" · 1 узл.) +- `body > div:1 > div:1 > footer:3 > span:1` **removed** (`span` "•" · 1 узл.) +- `body > div:1 > div:1 > div:2` **removed** (`div` · 13 узл.) +- `body > div:1 > div:1 > div:3` **removed** (`div` · 255 узл.) +- `body > div:1 > header:1` **removed** (`header` · 21 узл.) + + +## /preview/tools/linear-gradient-png (vs gradient.html) + +- added: **4** (≈186 узл.), removed: **5** (≈99 узл.), tag: **1**, text: **1** + +### Расхождения (топ 11) + +- `body > div:1` **tag**: ours `div` → ref `main` +- `body > div:1 > div:1 > header:1` **added** (`header` · 20 узл.) +- `body > div:1 > div:1 > div:2` **text**: ours `` → ref `PNG PROCESSING SINGLE TOOL` +- `body > div:1 > div:1 > div:2 > div:1` **added** (`div` · 1 узл.) +- `body > div:1 > div:1 > div:2 > div:2` **added** (`div` · 162 узл.) +- `body > div:1 > div:1 > div:2 > span:1` **removed** (`span` "/" · 1 узл.) +- `body > div:1 > div:1 > footer:3` **added** (`footer` · 3 узл.) +- `body > div:1 > div:1 > div:2` **removed** (`div` · 6 узл.) +- `body > div:1 > div:1 > div:3` **removed** (`div` · 66 узл.) +- `body > div:1 > header:1` **removed** (`header` · 21 узл.) +- `body > div:1 > footer:3` **removed** (`footer` · 5 узл.) + + +## /preview/tools/remove-background-png (vs background-remover.html) + +- added: **4** (≈186 узл.), removed: **5** (≈104 узл.), tag: **1**, text: **1** + +### Расхождения (топ 11) + +- `body > div:1` **tag**: ours `div` → ref `main` +- `body > div:1 > div:1 > header:1` **added** (`header` · 20 узл.) +- `body > div:1 > div:1 > div:2` **text**: ours `` → ref `PNG PROCESSING SINGLE TOOL` +- `body > div:1 > div:1 > div:2 > div:1` **added** (`div` · 1 узл.) +- `body > div:1 > div:1 > div:2 > div:2` **added** (`div` · 162 узл.) +- `body > div:1 > div:1 > div:2 > span:1` **removed** (`span` "/" · 1 узл.) +- `body > div:1 > div:1 > footer:3` **added** (`footer` · 3 узл.) +- `body > div:1 > div:1 > div:2` **removed** (`div` · 6 узл.) +- `body > div:1 > div:1 > div:3` **removed** (`div` · 71 узл.) +- `body > div:1 > header:1` **removed** (`header` · 21 узл.) +- `body > div:1 > footer:3` **removed** (`footer` · 5 узл.) diff --git a/web/package.json b/web/package.json index 7cb6aac..aba6f5b 100644 --- a/web/package.json +++ b/web/package.json @@ -13,7 +13,8 @@ "test": "vitest run", "format": "prettier --write .", "lint": "eslint .", - "refs-audit": "node scripts/audit.mjs" + "refs-audit": "node scripts/audit.mjs", + "refs-dom-audit": "node scripts/audit-dom.mjs" }, "devDependencies": { "@eslint/js": "^10.0.1", diff --git a/web/scripts/audit-dom.mjs b/web/scripts/audit-dom.mjs new file mode 100644 index 0000000..a14fce3 --- /dev/null +++ b/web/scripts/audit-dom.mjs @@ -0,0 +1,388 @@ +// Фаза A: структурный (DOM) аудит. Для каждого preview-маршрута снимает +// нормализованное дерево элементов нашей страницы и соответствующего refs-html +// файла, сравнивает по структурному пути (без IGNORE) и пишет отчёт о +// расхождениях (added / removed / tagMismatch / textMismatch). +// Запуск: pnpm refs-dom-audit (опции: --route --ref --no-serve --ours ) +import { chromium } from "playwright"; +import { spawn } from "node:child_process"; +import { readFileSync, writeFileSync, mkdirSync, readdirSync } from "node:fs"; +import { pathToFileURL } from "node:url"; +import { resolve, join } from "node:path"; + +const args = (() => { + const a = {}; + for (let i = 0; i < process.argv.length; i++) { + const v = process.argv[i]; + if (v === "--no-serve") a.noServe = true; + else if (v === "--ours") a.ours = process.argv[++i]; + else if (v === "--ref") a.ref = process.argv[++i]; + else if (v === "--route") a.route = process.argv[++i]; + } + return a; +})(); + +const WEB = process.cwd(); +const PORT = 5179; +const REFS_DIR = resolve(WEB, "../refs-html"); + +// Динамические сегменты: явный список значений + сопоставление id → файл рефа. +const DYNAMIC = { + "tools/[id]": { + ids: ["linear-gradient-png", "remove-background-png"], + refFor: (id) => + id === "linear-gradient-png" + ? "gradient.html" + : id === "remove-background-png" + ? "background-remover.html" + : null, + }, +}; + +// Маршруты без релевантного рефа (refs-html/index.html — зеркало demo из Next, не реф). +const EXCLUDE = new Set(["/preview"]); + +const refNameFor = (route) => { + if (route === "/preview") return "index.html"; + const last = route.split("/").filter(Boolean).pop(); + return `${last}.html`; +}; + +function discoverRoutes() { + const base = resolve(WEB, "src/routes/preview"); + const out = []; + const walk = (dir) => { + let ents; + try { + ents = readdirSync(dir, { withFileTypes: true }); + } catch { + return; + } + for (const e of ents) { + const p = join(dir, e.name); + if (e.isDirectory()) walk(p); + else if (e.name === "+page.svelte") { + const rel = p.slice(base.length).replace(/\\/g, "/"); + const parts = rel.split("/").filter(Boolean); + parts.pop(); + out.push(parts.length ? `/preview/${parts.join("/")}` : "/preview"); + } + } + }; + walk(base); + return out; +} + +function refsList() { + try { + return readdirSync(REFS_DIR).filter((f) => f.endsWith(".html")); + } catch { + return []; + } +} +function refExists(name) { + return refsList().includes(name); +} + +function buildTargets() { + if (args.route) { + const ref = args.ref || refNameFor(args.route); + return [{ route: args.route, ref }]; + } + const targets = []; + const seen = new Set(); + const add = (route, ref) => { + if (!ref || seen.has(route)) return; + if (!refExists(ref)) { + console.warn(`[dom-audit] ref not found, skipped: ${route} -> ${ref}`); + return; + } + seen.add(route); + targets.push({ route, ref }); + }; + for (const route of discoverRoutes()) { + if (route.includes("[")) continue; + if (EXCLUDE.has(route)) { + console.log(`[dom-audit] excluded (no relevant ref): ${route}`); + continue; + } + add(route, refNameFor(route)); + } + for (const [seg, cfg] of Object.entries(DYNAMIC)) { + for (const id of cfg.ids) { + add(`/preview/${seg.replace("[id]", id)}`, cfg.refFor(id)); + } + } + const routed = new Set(targets.map((t) => t.ref)); + for (const f of refsList()) { + if (!routed.has(f)) console.warn(`[dom-audit] ref без маршрута: ${f}`); + } + return targets; +} + +const waitFor = async (url, ms = 60000) => { + const t = Date.now(); + while (Date.now() - t < ms) { + try { + const r = await fetch(url); + if (r.ok) return; + if (r.status >= 400) { + throw new Error( + `route ${url} returned HTTP ${r.status} (server is up, page failed to render)`, + ); + } + } catch (e) { + if (e instanceof Error && e.message.includes("returned HTTP")) throw e; + } + await new Promise((r) => setTimeout(r, 500)); + } + throw new Error("dev server not up: " + url); +}; + +// Нормализованное дерево элементов: тег, отсортированные классы, собственный +// текст (без SVG-иконок), дети. Пропускаем служебное; SVG не спускаемся внутрь. +const snapshot = (page) => + page.evaluate(() => { + const walk = (el) => { + const tag = el.tagName.toLowerCase(); + if (["script", "style", "noscript", "template"].includes(tag)) return null; + // Пропускаем не отображаемые узлы (Next-боилерплейт: