mirror of
https://github.com/Ku6epXBOCTuK/easy-png-tools.git
synced 2026-09-14 13:36:36 +00:00
docs: update plans\docs - actual info
This commit is contained in:
@@ -0,0 +1,214 @@
|
||||
# План: аудит верности `preview/*` против `refs-html` (DOM → CSS)
|
||||
|
||||
> **СТАТУС: РЕАЛИЗОВАН (2026-09-07) — устарел.** Скрипты
|
||||
> `web/scripts/audit-dom.mjs`, `audit-css.mjs`, `audit-cdp.mjs`,
|
||||
> `audit-cdp-responsive.mjs` и npm-скрипты `refs-dom-audit` /
|
||||
> `refs-css-audit` / `refs-cdp-audit` на месте; старый текстовый `audit.mjs`
|
||||
> удалён. Актуальная доводка верности и статус пунктов F — в
|
||||
> `plan-design-fix.md`.
|
||||
|
||||
> Статус: к выполнению. Ветка `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) — не
|
||||
аудируем вслепую.
|
||||
@@ -0,0 +1,596 @@
|
||||
# План: составные типы параметров и полная типизация pipeline
|
||||
|
||||
> **СТАТУС: ЗАКРЫТ (2026-09-07).** Типизация pipeline, составные типы
|
||||
> (dimension/color-pair/offset/position9/font-style/plate/gradient),
|
||||
> `schema.layout`, изоляция веток и вся миграция инструментов (121/125,
|
||||
> остальные 4 — отложены решениями) выполнены. Ключевые архитектурные решения
|
||||
> (параллельная сборка, правило копий, изоляция old↔preview) закреплены в
|
||||
> `AGENTS.md`.
|
||||
> **Незакрытое из этого плана:** шаг 37 — физический переезд старых файлов
|
||||
> в папки `old/` (перенесён в `backlog.md`), и удаление старого кода —
|
||||
> по `plan-redesign.md` (C17–C21). Отложенные инструменты —
|
||||
> `watermark-image-png` (overlay), `png-info`, `reduce-to-size` — в `backlog.md`.
|
||||
> Ниже — полное состояние на момент закрытия.
|
||||
|
||||
> Статус: **в реализации.** Фаза 0 (фундамент) ✔, Фаза 1 (рабочий инструмент
|
||||
> в preview) ✔, Фаза 2 (простые инструменты без составных типов) — переведены
|
||||
> все одиночные инструменты (18 шт). Фаза 3 — **составной тип `dimension`
|
||||
> полностью переведён** (12 инструментов: create-empty, single-color,
|
||||
> random-noise, linear-gradient, color-spectrum, random-colors, draw-grid,
|
||||
> placeholder, fit-on-background, change-canvas-size, resize, crop); **составной
|
||||
> тип `color-pair` полностью переведён** (4 инструмента: blend-two,
|
||||
> step-colors, linear-gradient, two-colors); **составной тип `offset`
|
||||
> полностью переведён** (4 инструмента: circle-mask, square-mask,
|
||||
> star-mask, wavy-mask); **составной тип `position9` — переведены
|
||||
> add-text-png и date-stamp-png** (водяной знак-картинка — отдельный шаг:
|
||||
> требует overlay-механику в новом превью); **составной тип `font-style`
|
||||
> полностью переведён** (text-to-png, add-text, date-stamp); **составной тип
|
||||
> `plate` полностью переведён** (add-text, date-stamp); **составной тип
|
||||
> `gradient` полностью переведён** (linear-gradient): вместо горизонтального/
|
||||
> вертикального select — **угол 0..360°** (новый виджет `AngleControl`:
|
||||
> slider + кнопки-пресеты 0°/90°/180°/270°, переиспользуемый компонент);
|
||||
> preview
|
||||
> научен применять **генераторы** (`executeGenerate`, кнопка Generate) и
|
||||
> рендерить все kinds схемы (slider/number/color/select/checkbox/dimension/
|
||||
> color-pair/offset/position9/font-style/plate/gradient). **Пер-инструмент
|
||||
> раскладка `schema.layout` реализована** (шаг 32): тип `ToolSchemaLayout`
|
||||
> {groups: [{title?, cols?, fields}]}, рендер групп в `SchemaFields.svelte`
|
||||
> (заголовок группы + сетка колонок, неупомянутые поля — в общей группе),
|
||||
> пилот — `add-text` (группы Text/Placement/Plate). **UI-макеты расставлены
|
||||
> по переведённым инструментам** (шаг 33): geometry (fit-on-background,
|
||||
> change-canvas-size, resize, crop), маски alpha (Shape/Position), generate
|
||||
> (create-empty, linear-gradient, color-spectrum, random-colors, draw-grid,
|
||||
> step-colors, placeholder, text-to-png), text (add-text, date-stamp),
|
||||
> filters (randomize-pixels, add-noise) — везде, где канвас отделён от
|
||||
> параметров эффекта либо фигура от позиции.
|
||||
> Следующее закрыто в `archive/plan-migrate-remaining.md` (2026-09-07):
|
||||
> перевод оставшихся инструментов выполнен (всего 121/125 в preview; 4 хвоста —
|
||||
> `watermark-image-png`, `png-info`, `reduce-to-size-png`, `compress-png` —
|
||||
> отложены решениями в `backlog.md`). Незакрытые продолжения: переезд старых
|
||||
> компонентов в папки `old/` (шаг 37 ниже), закрытие долгов и сбор фидбека —
|
||||
> см. также `plan-design-fix.md`.
|
||||
>
|
||||
> Ключевые файлы нового registry: `web/src/lib/registry-new/{types,index,*}.ts`
|
||||
> (по файлу на категорию: geometry/alpha/convert/analyze/filters/color/generate)
|
||||
>
|
||||
> - `web/src/lib/preview/categories.ts`,
|
||||
> `web/src/lib/registry-schema.ts` (kind `dimension`), `kit/fields/DimensionField.svelte`,
|
||||
> `preview/executor/index.ts` (`executeGenerate`), `SchemaFields.svelte`
|
||||
> (полный рендер kinds). Старый `web/src/lib/registry.ts` разбит по категориям
|
||||
> в `web/src/lib/registry/` (см. `registry.ts` — тонкий баррель).
|
||||
|
||||
## Ключевая стратегия: параллельная сборка, старый UI не трогаем
|
||||
|
||||
Старый UI (группа `(old)/`, `ParamForm.svelte` + старый pipeline) **продолжает
|
||||
работать как сейчас, без рефакторинга**. Новый `ToolSchema<P>` строится
|
||||
рядом и служит источником для **нового UI**. Инструменты переписываются под
|
||||
новый registry по мере нужды — для нового UI.
|
||||
|
||||
Это та же логика, что в `plan-redesign.md` применена к дизайну: параллельная
|
||||
сборка, изоляция от старого, затем новый становится основным и старый
|
||||
удаляется вместе со старым дизайном.
|
||||
|
||||
**Правило копий (важно!):** если для нового UI/registry нужно внести изменения
|
||||
в файл, который **прямо или косвенно** уже используется `(old)` веткой, — этот
|
||||
файл **НЕ трогаем**. Вместо этого делаем **копию** в новом месте (например,
|
||||
в `lib/preview/`) и правим копию. Это приводит к дублированию, но
|
||||
**гарантированно не задевает старую ветку сайта**. Пример: категории — новый
|
||||
`lib/preview/categories.ts` (object as const) копирует и заменяет собой
|
||||
`../categories` для нового кода, старый `categories.ts` обслуживает `(old)` и
|
||||
остаётся без изменений. После перехода (Фаза 5) копия становится основной,
|
||||
а исходник продолжает обслуживать `(old)` UI (он не удаляется — см. шаг 35).
|
||||
|
||||
**Разделение схем: две независимые схемы.**
|
||||
|
||||
| Схема | Источник | Использование |
|
||||
| --------------------- | ------------------------------------------------- | ---------------------------------------------------------------- |
|
||||
| Старый `ParamDef[]` | В `ToolEntry.params` — **остаётся без изменений** | Старый UI, старый pipeline (`ParamForm.svelte`, executor, chain) |
|
||||
| Новый `ToolSchema<P>` | Новое поле `ToolEntry.schema` — добавляется рядом | Новый UI (kit/`ParamControl`, новый pipeline) |
|
||||
|
||||
- Старая и новая схемы **не выводятся друг из друга** — это два независимых
|
||||
описания инструмента.
|
||||
- Пока новый UI не готов — `schema` просто отсутствует у большинства
|
||||
инструментов, старый UI ничего не замечает.
|
||||
- Удаление старого UI (после перехода) попутно удаляет и старый `ParamDef[]`
|
||||
|
||||
### Разделение registry по UI (новое решение, Фаза 2+)
|
||||
|
||||
Помимо двух схем, registry тоже разделён по UI (по факту миграции):
|
||||
|
||||
- **Старый registry** (`web/src/lib/registry.ts` + `registry/` +
|
||||
`registry-helpers.ts`) — работает на старом UI, использует `ParamDef[]`.
|
||||
**Не трогаем**; остаётся обслуживать `(old)/` маршруты (см. шаг 35).
|
||||
- **Новый registry** (`web/src/lib/registry-new/`) — строится **с нуля «как надо»**:
|
||||
`ToolEntry<P>` с обязательным `schema`, типизированный `run`, **без** `ParamDef[]`
|
||||
и **без** связи со старым. Импортирует core-функции (`expandCanvas`,
|
||||
`strokeImage`, …) напрямую. Наполняется **по-инструментно** по мере миграции
|
||||
(не разовым переводом всех 130). Preview показывает только переведённые
|
||||
инструменты.
|
||||
|
||||
Preview (`catalog.ts`, маршруты `preview/**`, `SchemaToolView`, `SchemaFields`)
|
||||
импортируют из `$lib/registry-new`. После удаления старого UI `registry-new/`
|
||||
переименуется в `registry`.
|
||||
|
||||
**Executor/worker для нового UI (осознанное дублирование):** общий
|
||||
`executor.worker.ts` резолвит инструменты по `id` в **старом** registry — для
|
||||
новых инструментов их там нет. Поэтому `executor.ts` + `executor.worker.ts`
|
||||
**скопированы** в `web/src/lib/preview/` и переписаны под `registry-new` +
|
||||
`sanitizeSchemaParams`. Старый executor/worker под старый UI остаются
|
||||
нетронутыми; `SchemaToolView` использует `$lib/preview/executor`. Это
|
||||
дублирование необходимо до ухода старого UI (после — preview-executor заменяет
|
||||
общий).
|
||||
|
||||
## Проблема (текущая)
|
||||
|
||||
- `ParamDef` — плоский union из 6 примитивов (`number | slider | select |
|
||||
checkbox | color | text`). Параметры инструмента — отдельные записи в плоском
|
||||
массиве.
|
||||
- `run()`/`generate()` получают `Record<string, unknown>`; значения достаются
|
||||
через `num(p, "fromColor")` — runtime string lookup **без type safety**,
|
||||
легко напутать id.
|
||||
- Поля инструмента **слабо связаны** (например «ширина» и «цвет» рамки — два
|
||||
независимых поля без общего контракта) → общий генерализованный рендер, нет
|
||||
возможности сделать идеальный пер-инструмент UI.
|
||||
- Preview-система (`tool-views.ts`) **дублирует** registry (FieldDef vs
|
||||
ParamDef) — одни и те же инструменты описаны дважды.
|
||||
|
||||
## Решения (принятые в обсуждении)
|
||||
|
||||
1. **Полная типизация.** `interface Params` (runtime-тип) объявляется явно в
|
||||
каждом инструменте. `run`/`generate` типизируются прямо на него, а не на
|
||||
`Record<string, unknown>`.
|
||||
2. **Отдельная типизированная схема для UI/дефолтов/валидации.** Два понятия
|
||||
на инструмент: runtime-тип + схема. Схема проверяется компилятором на
|
||||
соответствие `Params` (чтобы не рассинхронизировались).
|
||||
3. **Общий рендер,** но с пер-инструмент layout: общие компоненты рисуют поля
|
||||
по схеме; схема инструмента задаёт layout и группировку. Без отдельного
|
||||
Svelte-компонента на каждый инструмент.
|
||||
4. **Составные типы** (`gradient`, `color-pair`, `dimension`, `offset`,
|
||||
`position9`, `font-style`, `plate`) остаются в плане — это способ выразить
|
||||
в схеме связанную группу полей и переиспользовать её на нескольких
|
||||
инструментах и в UI.
|
||||
5. Рано или поздно `tool-views.ts` поглощается registry (preview/lede/layout
|
||||
переезжают в meta инструмента) — но это отдельный шаг, см. ниже.
|
||||
6. **Старый UI не рефакторим.** Две независимые схемы: старый `ParamDef[]`
|
||||
остаётся источником для старого UI без изменений; новый `ToolSchema<P>`
|
||||
строится рядом для нового UI (см. «Ключевая стратегия» выше).
|
||||
|
||||
## Целевая архитектура (на примере «добавить рамку»)
|
||||
|
||||
Цель: «добавить рамку: ширина и цвет» — **один интерфейс**, а не два
|
||||
слабо связанных поля.
|
||||
|
||||
```ts
|
||||
// 1. Runtime-тип — что получает run/generate
|
||||
interface AddBorderParams {
|
||||
thickness: number; // рамка: ширина
|
||||
color: string; // рамка: цвет
|
||||
}
|
||||
|
||||
// 2. Типизированная UI-схема, проверяемая компилятором против Params
|
||||
// Ключи схемы обязаны совпадать с полями Params; default совместим с типом.
|
||||
const addBorderSchema = toolSchema<AddBorderParams>({
|
||||
thickness: field.slider({ min: 1, max: 500, default: 5 }),
|
||||
color: field.color({ default: "#000000" }),
|
||||
layout: { group: "border", cols: 2 }, // пер-инструмент раскладка
|
||||
});
|
||||
|
||||
// 3. Инструмент в registry
|
||||
const addBorder: ToolEntry<AddBorderParams> = {
|
||||
id: "add-border-png",
|
||||
title: "Add border to PNG",
|
||||
category: "geometry",
|
||||
schema: addBorderSchema,
|
||||
run: (img, p) => expandCanvas(img, 4x..., p.color), // p.thickness — type safe
|
||||
};
|
||||
```
|
||||
|
||||
### Что даёт
|
||||
|
||||
- `p.thickness`, `p.color` — IDE autocomplete + compile-time ошибка при ошибке.
|
||||
- `run()` нельзя передать лишний/чужой ключ.
|
||||
- Схема — единый источник для дефолтов, валидации и рендера.
|
||||
- Схема одного инструмента может задавать layout, которого нет в общем рендере.
|
||||
|
||||
## Типовая система (набросок)
|
||||
|
||||
```ts
|
||||
// ─── Поле схемы ───
|
||||
interface NumberField { kind: "number"; default: number; min?: number; max?: number; step?: number }
|
||||
interface SliderField { kind: "slider"; default: number; min: number; max: number; step?: number }
|
||||
interface ColorField { kind: "color"; default: string }
|
||||
interface SelectField { kind: "select"; default: string; options: { value: string; label: string }[] }
|
||||
interface TextField { kind: "text"; default: string; placeholder?: string }
|
||||
interface CheckboxField { kind: "checkbox"; default: boolean }
|
||||
|
||||
type FieldDef =
|
||||
| NumberField | SliderField | ColorField | SelectField | TextField | CheckboxField;
|
||||
|
||||
// ─── «поле» описывает тип значения + схему вместе, чтобы не рассинхронизировать ───
|
||||
interface Field<T> {
|
||||
schema: FieldDef;
|
||||
// T — ожидаемый runtime-тип этого поля (number|string|boolean)
|
||||
}
|
||||
|
||||
// ─── Builder: field.slider({...}) возвращает Field<number> с типизированной схемой ───
|
||||
const field = {
|
||||
slider: (s: SliderField): Field<number> => ({ schema: s }),
|
||||
number: (s: NumberField): Field<number> => ({ schema: s }),
|
||||
color: (s: ColorField): Field<string> => ({ schema: s }),
|
||||
select: <V extends string>(s: SelectField & { options: {value: V}[] }): Field<V> => ({ schema: s }),
|
||||
text: (s: TextField): Field<string> => ({ schema: s }),
|
||||
checkbox: (s: CheckboxField): Field<boolean> => ({ schema: s }),
|
||||
};
|
||||
|
||||
// ─── toolSchema<P>: проверяет, что ключи схемы === полям P и типы сходятся ───
|
||||
type FieldsOf<T> = { [K in keyof T]: Field<T[K]> } & {
|
||||
layout?: { group?: string; cols?: number };
|
||||
};
|
||||
function toolSchema<P>(fields: FieldsOf<P>): ToolSchema<P> { ... }
|
||||
// TS выдаст ошибку, если schema содержит ключ, отсутствующий в Params, или
|
||||
// если type поля не совпадает с типом Params[K].
|
||||
```
|
||||
|
||||
Ключевая идея связки: **builder `field.x<T>()` связывает runtime-тип поля с его
|
||||
схемой на этапе компиляции**, а `toolSchema<P>` заставляет ключи схемы
|
||||
совпадать с ключами `P`. Это и есть защита от рассинхрона.
|
||||
|
||||
## Составные типы (для переиспользования и группировки)
|
||||
|
||||
| Тип | Под-поля | Инструментов |
|
||||
| ------------ | -------------------------- | ------------ |
|
||||
| `dimension` | width + height | 12 |
|
||||
| `color-pair` | from + to | 4 |
|
||||
| `offset` | x + y | 4 |
|
||||
| `gradient` | from + to + dir (+ type?) | 1 |
|
||||
| `position9` | position (3×3 grid) | 3 |
|
||||
| `font-style` | font + size + bold + color | 3-4 |
|
||||
| `plate` | enabled + color + opacity | 2-3 |
|
||||
|
||||
Составной тип = группы полей, которые в интерфейсе `Params` могут разворачиваться
|
||||
во вложенный объект или плоские ключи — решается на этапе реализации.
|
||||
Пример для `gradient`:
|
||||
|
||||
```ts
|
||||
interface GradientParams {
|
||||
from: string;
|
||||
to: string;
|
||||
dir: string;
|
||||
}
|
||||
// схема: field.color(from), field.color(to), field.select(dir)
|
||||
// плюс annotation в схеме: { group: "gradient" } → UI рисует как цветовой переход
|
||||
```
|
||||
|
||||
## Инвентарь: какие инструменты мигрировать
|
||||
|
||||
### dimension (12)
|
||||
|
||||
resize-png*, crop-png*, fit-on-background-png, create-empty-png,
|
||||
single-color-png, random-noise-png, linear-gradient-png, color-spectrum-png,
|
||||
random-colors-png, draw-grid-png, placeholder-png, change-canvas-size-png
|
||||
|
||||
(\* у resize/crop — не просто dimension, есть специфика: keepAspect, x/y и т.д.)
|
||||
|
||||
### color-pair (4)
|
||||
|
||||
blend-two-png, step-colors-png, linear-gradient-png, two-colors-png
|
||||
|
||||
### offset (4)
|
||||
|
||||
circle-mask-png, square-mask-png, star-mask-png, wavy-mask-png
|
||||
|
||||
### position9 (3)
|
||||
|
||||
add-text-png, date-stamp-png, watermark-image-png
|
||||
|
||||
### font-style (3-4)
|
||||
|
||||
text-to-png, add-text-png, date-stamp-png
|
||||
|
||||
### plate (2-3)
|
||||
|
||||
add-text-png, date-stamp-png
|
||||
|
||||
## Пошаговый план перевода инструментов
|
||||
|
||||
> Каждый шаг — **атомарное, маленькое изменение** (в идеале 1 инструмент =
|
||||
> 1 diff, укладывается в правило «коммиты < ~500 строк»). После каждого шага —
|
||||
> ревью; параллельно можно делать UI нового инструмента.
|
||||
>
|
||||
> **Принцип малых шагов:** перевод инструмента не меняет поведение старой
|
||||
> схемы (`params` остаётся), не ломает старый UI. Инструмент получает ДОПОЛНИТЕЛЬНО
|
||||
> новое поле `schema` + `interface Params`, старое `run`/`params` не трогаем.
|
||||
> Это гарантирует, что старый UI продолжает работать без регресса.
|
||||
>
|
||||
> **Новый подход (решение пользователя):** сначала делаем **один полноценный
|
||||
> рабочий инструмент в preview** — не эталон «на бумаге», а реально юзабельный,
|
||||
> чтобы руками оценить, насколько это рабочее решение и какие возникнут сложности.
|
||||
> Оцениваем, при необходимости правим инфраструктуру, и только потом переводим
|
||||
> остальные инструменты тем же проверенным паттерном.
|
||||
|
||||
### Фаза 0 — фундамент ✔ (сделано)
|
||||
|
||||
Typed field builders + `Field<T>` + `toolSchema<P>()` + `ToolSchema<P>` —
|
||||
в `web/src/lib/registry-schema.ts`. `ToolEntry<P>` generic + опциональное
|
||||
поле `schema`. Новые `defaultSchemaParams`/`sanitizeSchemaParams`.
|
||||
Старый `ParamDef[]`/pipeline не тронуты. Проверено: compile-time-защита
|
||||
(лишний/неверный/отсутствующий ключ ловит TS), тесты, svelte-check без новых
|
||||
ошибок.
|
||||
|
||||
### Фаза 1 — ОДИН полноценный рабочий инструмент в preview (срез) ✔ (сделано)
|
||||
|
||||
Инструмент переехал в `registry-new` и рендерится через `SchemaToolView`
|
||||
(`SchemaFields`/`SchemaPreview`); применение реально работает end-to-end до
|
||||
картинки. Паттерн подтверждён. `find-contour-png` — следующий в этой фазе-2.
|
||||
|
||||
5. **Инструмент «добавить рамку» (`add-border-png`)** — сделать полностью
|
||||
рабочим в preview:
|
||||
- `interface AddBorderParams { thickness: number; color: string }`
|
||||
- `addBorderSchema = toolSchema<AddBorderParams>(...)` + `schema: ...` в entry
|
||||
(старый `params`/`run` остаются — старый UI не регрессит).
|
||||
- **Новый рендер в preview** (`kit/ToolView` → `ParamControl` или новый
|
||||
компонент схемы): читает `ToolSchema<P>`, рисует поля рамки, применение
|
||||
реально работает (end-to-end до картинки).
|
||||
- Дефолты/валидация/значения — из схемы (`defaultSchemaParams`,
|
||||
`sanitizeSchemaParams`).
|
||||
- **Ревью-оценка:** удобно ли это, что сломалось/усложнилось, что поправить
|
||||
в инфраструктуре до масштабирования. Принимается макет/решение.
|
||||
|
||||
Результат: один реально рабочий инструмент на новой системе = доказываем паттерн,
|
||||
прежде чем переводить остальных.
|
||||
|
||||
### Фаза 2 — простые инструменты без составных типов (1 инструмент = 1 шаг)
|
||||
|
||||
Одиночные/небольшие инструменты, где `interface Params` + `schema` не требуют
|
||||
составных типов. Каждый — отдельный маленький diff (~15-30 строк), тем самым
|
||||
проверенным в Фазе 1 паттерном. **Переезжают в `registry-new`.**
|
||||
|
||||
Переведено: `add-border-png` (Фаза 1), `add-stroke-png`, `find-contour-png`, плюс
|
||||
весь блок простых инструментов ниже (пункты 8-22). Следующее — Фаза 3.
|
||||
|
||||
6. ~~add-stroke-png (color + slider)~~ → переведён в `registry-new` ✔
|
||||
7. ~~find-contour-png (color + slider)~~ → переведён в `registry-new` ✔
|
||||
8. ~~convert-png-to-jpg (color + slider)~~ → переведён в `registry-new` ✔
|
||||
9. ~~convert-png-to-webp (slider)~~ → переведён в `registry-new` ✔
|
||||
10. ~~remove-color-from-png (color + slider)~~ → переведён в `registry-new` ✔
|
||||
11. ~~extract-color-from-png (color + slider)~~ → переведён в `registry-new` ✔
|
||||
12. ~~add-noise-png (slider + select + number)~~ → переведён в `registry-new` ✔
|
||||
13. ~~randomize-pixels-png (slider + number)~~ → переведён в `registry-new` ✔
|
||||
14. ~~pixelate-png (slider)~~ → переведён в `registry-new` ✔
|
||||
15. ~~vignette-png (slider)~~ → переведён в `registry-new` ✔
|
||||
16. ~~gamma-png (slider)~~ → переведён в `registry-new` ✔
|
||||
17. ~~temperature-png (slider)~~ → переведён в `registry-new` ✔
|
||||
18. ~~tint-png (color + slider)~~ → переведён в `registry-new` ✔
|
||||
19. ~~quantize-png (slider)~~ → переведён в `registry-new` ✔
|
||||
20. ~~custom-palette-png (text)~~ → переведён в `registry-new` ✔
|
||||
21. ~~dithering-png (slider + select)~~ → переведён в `registry-new` ✔
|
||||
22. ~~jpeg-artifacts-png (slider)~~ → переведён в `registry-new` ✔
|
||||
|
||||
> **Замечание (конвертеры):** старые `convert-png-to-jpg`/`convert-png-to-webp`
|
||||
> несли `output`-метаданные (`mime`/`ext`/`qualityParamId`) для выбора формата
|
||||
> вывода. В новом `ToolEntry<P>` поля `output` пока нет, поэтому при переводе
|
||||
> эти метаданные не перенесены — preview пока отдаёт результат как PNG.
|
||||
> Механика выбора формата/качества в новом UI — отдельный шаг (не блокирует Фазу 2).
|
||||
|
||||
### Фаза 3 — инструменты с составными типами (по типу, затем по инструментам)
|
||||
|
||||
24. `dimension` — **переведены все 12 инструментов** ✔ (`create-empty-png`,
|
||||
`single-color-png`, `random-noise-png`, `linear-gradient-png`,
|
||||
`color-spectrum-png`, `random-colors-png`, `draw-grid-png`,
|
||||
`placeholder-png` — генераторы в `registry-new/generate.ts`;
|
||||
`fit-on-background-png`, `change-canvas-size-png`, `resize-png`,
|
||||
`crop-png` — в `registry-new/geometry.ts`). Составной тип во всех видах:
|
||||
вложенный объект `size: { width, height }` в Params + `field.dimension`,
|
||||
виджет `kit/fields/DimensionField.svelte`.
|
||||
Preview: генераторы применяются через `executeGenerate` (кнопка Generate),
|
||||
`SchemaFields` рендерит все kinds схемы (slider/number/color/select/
|
||||
checkbox/dimension). Тесты: 592 passed.
|
||||
25. `color-pair` — **все 4 инструмента переведены** ✔ (`blend-two-png`,
|
||||
`step-colors-png`, `linear-gradient-png` — генераторы в
|
||||
`registry-new/generate.ts`, `two-colors-png` — run в `registry-new/color.ts`).
|
||||
Составной тип во всех видах: вложенный объект `pair: { from, to }` +
|
||||
`field.colorPair`, виджет `kit/fields/schema/ColorPairControl.svelte`,
|
||||
kind `color-pair` в схеме (default/sanitize). Тесты: +4 (596 passed).
|
||||
26. `offset` — **все 4 инструмента переведены** ✔ (`circle-mask-png`,
|
||||
`square-mask-png`, `star-mask-png`, `wavy-mask-png` — run в
|
||||
`registry-new/alpha.ts`). Составной тип во всех видах: вложенный объект
|
||||
`offset: { x, y }` + `field.offset`, виджет
|
||||
`kit/fields/schema/OffsetControl.svelte`, kind `offset` в схеме
|
||||
(default/sanitize). Тесты: +4 (600 passed).
|
||||
27. `position9` — **переведены `add-text-png` и `date-stamp-png`** ✔ (run в
|
||||
`registry-new/text.ts`, оба `domOnly`). Составной тип во всех видах:
|
||||
значение — строка `Position9`, kind `position9` в схеме (default/sanitize
|
||||
по `POSITION9_VALUES`), виджет 3×3 `kit/fields/schema/PositionControl.svelte`.
|
||||
В `ToolEntry` добавлен флаг `domOnly` (превью-executor держит такие
|
||||
инструменты вне worker). `watermark-image-png` — отдельный под-шаг:
|
||||
ему нужен overlay-source (`getOverlay`/store), которого в новом превью пока
|
||||
нет. Тесты: +2 (602 passed). `font-style` и `plate` на этих инструментах
|
||||
сводятся в шаги 28-29.
|
||||
28. `font-style` — **все 3 инструмента переведены** ✔ (`text-to-png` —
|
||||
генератор в `registry-new/generate.ts` (domOnly), `add-text-png`/
|
||||
`date-stamp-png` — рефакторинг в `registry-new/text.ts`). Составной тип
|
||||
во всех видах: вложенный объект `style: { font, size, bold, color }` +
|
||||
`field.fontStyle`, виджет `kit/fields/schema/FontStyleControl.svelte`,
|
||||
kind `font-style` в схеме (default/sanitize, clamp размера к min/max).
|
||||
Тесты: +1 (603 passed).
|
||||
29. `plate` — **все 2 инструмента переведены** ✔ (`add-text-png`,
|
||||
`date-stamp-png` — рефакторинг в `registry-new/text.ts`). Составной тип
|
||||
во всех видах: вложенный объект `plate: { enabled, color, opacity }` +
|
||||
`field.plate`, виджет `kit/fields/schema/PlateControl.svelte` (чекбокс +
|
||||
цвет + слайдер непрозрачности, деактивируется при выключенной плашке),
|
||||
kind `plate` в схеме (default/sanitize, clamp opacity к 0..100).
|
||||
Покрытие тестов расширено (дефолты/sanitize plate в существующих тестах).
|
||||
30. `gradient` — **переведён** ✔ (linear-gradient-png). Направление градиента
|
||||
— не горизонталь/вертикаль, а **произвольный угол**: новый переиспользуемый
|
||||
виджет `kit/fields/schema/AngleControl.svelte` (слайдер 0..360° + кнопки
|
||||
стандартных углов 0°/90°/180°/270° с активным состоянием). Составной тип
|
||||
`gradient: { from, to, angle }` во всех видах (`field.gradient`, kind
|
||||
`gradient` в схеме — default/sanitize, clamp угла к 0..360, валидация
|
||||
цветов; виджет `kit/fields/schema/GradientControl.svelte` — пара цветов +
|
||||
AngleControl). Рендер по углу — локальный `angleGradient` в
|
||||
`registry-new/generate.ts` (core-`gradientImage` не трогали: он умеет
|
||||
только horizontal/vertical и используется старым UI). Семантика: 0° слева
|
||||
направо, 90° сверху вниз, рост угла по часовой (ось Y вниз); нормализация
|
||||
по полному диапазону проекции на ось (180°/270° разворачивают градиент).
|
||||
Тесты: дефолты, направление по углу (0°/90°/180°), sanitize/кламп.
|
||||
|
||||
### Фаза 4 — масштаб UI на остальные инструменты
|
||||
|
||||
31. По мере перевода инструментов в Фазе 2-3 — рендер схемы (из Фазы 1)
|
||||
покрывает их автоматически; составные виджеты (dimension, color-pair,
|
||||
offset, position9, font-style, plate, gradient) — по одному, каждый с ревью.
|
||||
32. Пер-инструмент layout (`schema.layout`) — **реализован** ✔.
|
||||
`ToolSchemaLayout.groups: { title?, cols?, fields[] }` в registry-schema;
|
||||
`SchemaFields.svelte` рендерит группы (подпись + сетка `1..n` колонок,
|
||||
неупомянутые поля — в общей группе в конце, без заголовка). Пилот —
|
||||
`add-text-png` (Text: text+style; Placement: position+margin; Plate).
|
||||
Тесты: schema-layout в registry-schema.test.ts; полные запуски чисты.
|
||||
33. Для каждого переведённого инструмента — UI-макет **расставлен** ✔.
|
||||
Layout-группы заданы там, где группировка содержательна (канвас отдельно
|
||||
от параметров эффекта, фигура отдельно от позиции; составные виджеты —
|
||||
внутри групп целиком):
|
||||
- geometry: fit-on-background (Canvas/Background), change-canvas-size
|
||||
(Canvas/Anchor), resize (Canvas/Scaling), crop (Offset/Crop area);
|
||||
- alpha-маски: circle/square/star/wavy (Shape — с 2 колонками где плотно,
|
||||
Position — offset);
|
||||
- generate: create-empty (Canvas/Fill), linear-gradient (Canvas/Colors),
|
||||
color-spectrum (Canvas/Spectrum), random-colors (Canvas/Random),
|
||||
draw-grid (Canvas/Grid), step-colors (Colors/Output), placeholder
|
||||
(Canvas/Colors/Text), text-to-png (Text/Background/Padding);
|
||||
- text: add-text, date-stamp (Text/Placement/Plate);
|
||||
- filters: randomize-pixels (Blocks), add-noise (Noise/Seed).
|
||||
Инструменты с 1–3 простыми полями остались без layout (одна общая группа).
|
||||
|
||||
### Фаза 5 — изоляция старого UI
|
||||
|
||||
34. Поглощение `tool-views.ts` (preview/lede/layout → meta инструмента) ✔.
|
||||
Отдельного `tool-views.ts` в репо нет: preview сразу строился на
|
||||
`registry-new`. Meta инструмента живёт в `ToolEntry` (`title`, `description`,
|
||||
`category`, `run`/`generate`), layout — в `schema.layout` (шаг 32), рендер —
|
||||
`SchemaToolView`/`SchemaFields`/`SchemaPreview`. Дублирующей системы нет.
|
||||
35. Старый UI **не удаляется**, а выносится в `(old)/`-маршруты и остаётся там
|
||||
временно (посмотреть, как работает, сравнить с новым; старые тесты
|
||||
продолжают проходить):
|
||||
- маршруты `(old)/{+page,demo,list-tools,tools/[id]}` — старый дизайн,
|
||||
тянет `old.css` (не `design2.css`), старый header/footer;
|
||||
- старый `registry.ts`/`registry/` + `ParamDef[]` обслуживают только
|
||||
`(old)/`-инструменты — не удаляются, не рефакторятся;
|
||||
- удаление происходит позже, отдельным решением (когда новый UI покроет
|
||||
все инструменты и ревью завершено).
|
||||
36. **Линтер-изоляция веток** (гарантия, что old и preview не смешиваются) ✔.
|
||||
Кастомный ESLint-плагин `web/eslint-plugins/isolation/no-mixed-imports`
|
||||
Резолвит каждый импорт (и `$lib/...`, и относительные `./`/`../`) до
|
||||
реального файла, классифицирует источник и цель по фактическому пути и
|
||||
ругается на old→new и new→old. Конфигурация (`old`/`new` glob-паттерны,
|
||||
`root`, `alias`) вынесена в настройки правила — единая точка правды:
|
||||
- старое: `routes/(old)/**`, `lib/registry.ts`, `lib/registry/**`,
|
||||
`lib/registry-helpers.ts`, `lib/categories.ts`, `lib/tools/**`,
|
||||
`lib/components/**` (кроме `kit/`);
|
||||
- новое: `routes/preview/**`, `lib/registry-new/**`, `lib/preview/**`,
|
||||
`lib/registry-schema.ts`, `lib/registry-schema.test.ts`,
|
||||
`lib/components/kit/**`;
|
||||
- общее (разрешено обоим): всё прочее — `core/`, `i18n/`, `theme`,
|
||||
`assets/`, корневой `lib` (`index.ts`, тесты).
|
||||
Достигнутая полная изоляция (одиночные пересечения устранены):
|
||||
- старые пилоты `registry/geometry.ts` (`add-border`) и `registry/alpha.ts`
|
||||
(`add-stroke`) получали `schema` из нового `registry-schema` — убрано;
|
||||
оба инструмента работают в старом UI через `params: ParamDef[]`,
|
||||
в preview — через свои schema-версии в `registry-new/`;
|
||||
- `registry.ts` больше не импортирует `ToolSchema` из `registry-schema`;
|
||||
- preview `list-tools` тянул `TOOL_ICONS` из старого `lib/tools/tool-icons`
|
||||
→ создана копия `lib/preview/tool-icons.ts` (правило копий).
|
||||
37. Перенос всех старых компонентов/библиотек в папки `old/` (**следующее**):
|
||||
переезд не трогает плагин — достаточно дописать один glob-паттерн в
|
||||
настройку правила (например `lib/old/**`, `lib/components/old/**`), а сама
|
||||
проверка работает по фактическим путям автоматически.
|
||||
|
||||
### Как ревьюить каждый шаг
|
||||
|
||||
- Diff **только один инструмент** (или фундамент без инструментов).
|
||||
- Старый `params`/`run`/`defaultParams`/`sanitizeParams` — **не изменены** (кроме
|
||||
добаление `schema`, если это пилот).
|
||||
- Старый UI и старые тесты продолжают работать — запустить
|
||||
`pnpm --dir web test` и `pnpm --dir web exec svelte-check`.
|
||||
- Порядок полей в `interface Params` и в `schema` совпадает; дефолты в сeme
|
||||
равны старым `default` из `params`.
|
||||
- Пилот из Фазы 1 проверяется **руками в preview**: применяется рамка,
|
||||
дефолты/валидация/смена значений работают end-to-end.
|
||||
|
||||
## Поток данных (runtime)
|
||||
|
||||
### Старый pipeline (не трогаем)
|
||||
|
||||
Всё как сейчас: старое `defaultParams`/`sanitizeParams`/executor/chain работают
|
||||
на `ParamDef[]` и `Record<string, unknown>`. Никаких изменений.
|
||||
|
||||
### Новый pipeline (строим рядом)
|
||||
|
||||
- **Хранилище значений** — типизировано на схему; у нового UI значения полей
|
||||
собираются в `Params` по `ToolSchema<P>`.
|
||||
- **defaultParams / sanitizeParams (новые)** — выводятся из `ToolSchema<P>`,
|
||||
дефолты лежат в схеме. Старые функции не трогаем, добавляем новые рядом.
|
||||
- **Executor/worker (новые)** — получает `Record<string, unknown>`, собирает
|
||||
`Params` по схеме, вызывает `run`. Остаётся serializable.
|
||||
- **Pipeline/chain (новый)** — шаги хранят значения, типизируются по схеме.
|
||||
|
||||
## Изменения в UI-компонентах
|
||||
|
||||
- **Старый UI** (`ParamForm.svelte`, группа `(old)/`) — **не трогаем.** Работает
|
||||
на `ParamDef[]` как раньше.
|
||||
- **Новый UI** (kit/`SchemaToolView` + `SchemaFields` + `SchemaPreview`, читает
|
||||
`ToolSchema<P>`) — сейчас рендерит поля по схеме (number/slider/color).
|
||||
- `tool-views.ts` отсутствует — preview/lede/layout уже живут в meta
|
||||
инструмента (`ToolEntry` + `schema.layout`), дублирования нет.
|
||||
|
||||
## Оценка трудозатрат
|
||||
|
||||
| Часть | Сложность | Время |
|
||||
| ----------------------------------------------------- | ------------------------ | ---------------------- |
|
||||
| Typed field builders + toolSchema\<P\> | Средняя | ~2ч |
|
||||
| Новые defaultParams/sanitizeParams под схему | Средняя | ~2-3ч |
|
||||
| ToolEntry\<P\> generic + поле schema (рядом с params) | Низкая-Средняя | ~1-2ч |
|
||||
| Новый executor/worker/pipeline под схему | Средняя | ~2-3ч |
|
||||
| Миграция инструментов на interface Params + схемы | Механическая, но крупная | ~8-12ч |
|
||||
| Новый рендер (kit/ParamControl) + составные виджеты | Средняя | ~4-5ч |
|
||||
| Поглощение tool-views.ts | Средняя | ~1-2ч |
|
||||
| Вынос старого UI в (old)/ + адаптация маршрутов | Средняя-Низкая | ~2-3ч |
|
||||
| **Итого** | | **~22-32ч** (поэтапно) |
|
||||
|
||||
> Оценка выросла по сравнению с ранней версией плана, потому что принят путь
|
||||
> «явный interface Params + схема + общий рендер с пер-инструмент layout» —
|
||||
> это полный рефакторинг pipeline, а не только добавление составных типов.
|
||||
>
|
||||
> Старый UI/`ParamDef[]`/старый pipeline в смету **не входят** — они не
|
||||
> рефакторятся, а продолжают работать на `(old)/`-маршрутах до перехода
|
||||
> (затем убираются отдельным решением — см. шаг 35).
|
||||
|
||||
## Порядок реализации (кратко)
|
||||
|
||||
Детальный пошаговый план с атомарными изменениями и порядком ревью — в разделе
|
||||
«Пошаговый план перевода инструментов» выше. Здесь краткая сводка фаз:
|
||||
|
||||
1. **Фаза 0** — фундамент: builders + `toolSchema<P>` + `ToolEntry<P>.schema` +
|
||||
новые default/sanitize. Ни одного инструмента не изменено. ✔
|
||||
2. **Фаза 1** — **один полноценный рабочий инструмент в preview**
|
||||
(«добавить рамку»): schema + рендер + end-to-end применение. Ручная оценка
|
||||
работоспособности решения/сложностей.
|
||||
3. **Фаза 2** — простые/одиночные инструменты (проверенный паттерн Фазы 1),
|
||||
1 инструмент = 1 диф.
|
||||
4. **Фаза 3** — инструменты с составными типами (dimension → color-pair →
|
||||
offset → position9 → font-style → plate → gradient), пилот каждого типа
|
||||
отдельно.
|
||||
5. **Фаза 4** — масштаб UI на остальные: составные виджеты, пер-инструмент
|
||||
layout (для каждого инструмента — UI-макет).
|
||||
6. **Фаза 5** — изоляция: старый UI на `(old)/`-маршруты (не удаляется,
|
||||
остаётся для ревью); preview на `registry-new` полностью;
|
||||
линтер-изоляция веток (плагин `isolation`), затем перенос старых
|
||||
компонентов/библиотек в папки `old/`.
|
||||
|
||||
## Зависимости
|
||||
|
||||
- **UI сперва доделать** — текущая активная работа; типизация params ожидает
|
||||
готовности нового дизайна.
|
||||
- Ветка redesign (`plan-redesign.md`) — параллельная, учесть при миграции
|
||||
kit/`ParamControl.svelte` и `overlay-store`.
|
||||
- Старый pipeline остаётся рабочим и шippable на всём протяжении перехода
|
||||
(аналогично стратегии `plan-redesign.md`).
|
||||
@@ -0,0 +1,141 @@
|
||||
# План: доводка дизайна до верности (фикс по refs)
|
||||
|
||||
> **СТАТУС: ЗАКРЫТ (2026-09-07).** Актуальный дизайн (`preview/*`) ушёл вперёд
|
||||
> от референсов `refs-html/` — сверка с ними больше не информативна
|
||||
> (новые компоненты `SchemaToolView`/`SchemaPreview`/каталог не имеют
|
||||
> рефовой копии). Аудит против рефа прекращается; качество дизайна дальше
|
||||
> гоняется ревью руками и специализированными проверками
|
||||
> (`lint:all` — stylelint/design-tokens/токен-аудит). Порядок удаления старого
|
||||
> и перехода — по `plan-redesign.md` (C17–C21).
|
||||
|
||||
> Статус: к выполнению. Ветка `feat/redesign`.
|
||||
> Источники: `docs/archive/demo-fidelity.md` (архивная копия старого отчёта по
|
||||
> верности, неактуальна), `docs/plan-redesign.md` (общая стратегия, фазы 1–4
|
||||
> выполнены до C16). Актуальный аудит верности — `pnpm refs-cdp-audit` /
|
||||
> `pnpm refs-css-audit` / `pnpm refs-dom-audit` → `web/audit/audit-report.md`
|
||||
> (текущий список скриптов — в `web/package.json`, см. `refs-*audit`).
|
||||
|
||||
## Контекст
|
||||
|
||||
- Новый дизайн собран параллельно под `preview/*` (см. `plan-redesign.md`),
|
||||
старый сайт `(old)/` не трогаем до C17.
|
||||
- `refs/` и `refs-html/` приведены к консистентному виду — текущая «правда по
|
||||
пикселям». Механизм синхронизации ref→код не создаётся (см. §9 plan-redesign).
|
||||
- Аудит верности автоматизирован: `pnpm refs-cdp-audit` (1440×900,
|
||||
`scripts/audit-cdp.mjs`) пишет
|
||||
`web/audit/audit-report.md` — light/dark токены + поэлементный дифф стилей;
|
||||
пиксель-дифф не используется. Палитры токенов уже сверены с рефом (light
|
||||
совпадает, dark собран). Темы в `refs-html` переключаются — скрипт инжектится
|
||||
в `scripts/extract-static.mjs`.
|
||||
- Исходный отчёт `demo-fidelity.md` перенесён в `docs/archive/` как утративший
|
||||
актуальность; его данные актуализированы ниже (Этап 1).
|
||||
- Зафиксированные решения (подтверждены автором):
|
||||
- **F1 — плоский фон холста**, без blueprint-сетки (как в рефе).
|
||||
- **F4 — preview-stack в одну колонку** (как в рефе).
|
||||
|
||||
## Принципы
|
||||
|
||||
- Никаких правок старого дизайна `(old)/` и старого `ui/`.
|
||||
- Компоненты пишем в `kit/` (переименование в `ui/` — на C19 по plan-redesign).
|
||||
- Коммиты атомарные (< ~500 строк), по 1–2 компонента; после каждого —
|
||||
`svelte-check` + `lint` + `build` + `refs-cdp-audit` (diff% не должен расти без причины).
|
||||
- Коммиты делает автор (агент не коммитит).
|
||||
|
||||
## Этап 0 — Рефакторинг (SRP / дизайн-обёртки), без изменения визуала
|
||||
|
||||
Гигантские страницы (`demo` ~513, `tools/[id]` ~451 строк) дробим на
|
||||
компоненты единой ответственности; убираем дублирующиеся inline-стили.
|
||||
|
||||
Новые обёртки в `src/lib/components/kit/`:
|
||||
|
||||
- `FieldGrid` — сетка полей (заменяет `.controls` / `.controls.compact`).
|
||||
- `ToggleRow` — лейбл + `Toggle` (дублировался в `demo` и `tools/[id]`).
|
||||
- `PreviewStack` — контейнер превью-плиток, **одна колонка** (реализует F4 в
|
||||
одном месте).
|
||||
- `SectionLabel` — label + strong + actions (вместо `.pipeline-head` /
|
||||
`.preview-top`).
|
||||
- `FileChip` — пилюля файла.
|
||||
- `PipelineFooter` — reset + auto-note.
|
||||
- `WorkspaceLayout` — двухпанельная сетка (settings | preview) со sticky +
|
||||
брейкпоинт 800px.
|
||||
|
||||
Декомпозиция страниц:
|
||||
|
||||
- `tools/[id]`: вынести `ParamControl` (диспетчер полей по `ParamDef`),
|
||||
`SettingsPanel` (левая панель), `PreviewPanel` (правая панель) —
|
||||
колоцированно в `routes/preview/tools/[id]/`. Страница сжимается до
|
||||
состояния + глёв.
|
||||
- `demo`: переписать на те же обёртки + `WorkspaceHeader` (eyebrow + h1 + lede +
|
||||
file-chip).
|
||||
|
||||
Побочный баг, устраняемый тут же: в `demo` поля сейчас обёрнуты в
|
||||
`<div class="control-block"><MonoLabel>…</MonoLabel><SliderField label>` —
|
||||
**двойной лейбл**, т.к. `ColorField`/`SliderField`/`SelectField`/`NumberField`
|
||||
уже содержат `Field` (MonoLabel) изнутри. При рефакторе убираем внешний
|
||||
`MonoLabel`, оставляем само поле.
|
||||
|
||||
Критерий Этапа 0: визуал идентичен (audit diff% без регрессии), старые тесты
|
||||
зелёные.
|
||||
|
||||
## Этап 1 — Верность (по refs-html; статус на момент плана)
|
||||
|
||||
Аудит: `pnpm refs-cdp-audit` (1440×900) пишет `web/audit/audit-report.md` —
|
||||
light/dark токены + поэлементный дифф стилей. Пиксель-дифф не используется.
|
||||
Палитры токенов уже сверены с рефом: light совпадает (`--background`,
|
||||
`--panel`, `--foreground`, `--muted`, `--line`, `--blue`), dark собран (16
|
||||
токенов). Темы в `refs-html` переключаются (скрипт инжектится в
|
||||
`extract-static.mjs`).
|
||||
|
||||
Открытые / решённые пункты (исходник — `docs/archive/demo-fidelity.md`):
|
||||
|
||||
- **B1** (открыт, P1): `button, input, select, textarea` рендерятся Arial —
|
||||
добавить в reset `design2.css` `font-family: inherit`. Влияет на все страницы.
|
||||
- **B2** (решён — проверить): двойного топбара нет. `preview/+layout.svelte`
|
||||
рендерит ровно один `TopBar`; `demo` свой `.topbar` не рендерит. Подтвердить
|
||||
аудитом/визуально при Этапе 1.
|
||||
- **F1** (решено — плоский фон): наш `--background` уже `#EEF1F4` (как реф);
|
||||
видимое отличие было в blueprint-сетке поверх фона. Фикс: убрать `BlueprintGrid`
|
||||
из `AppShell`.
|
||||
- **F2** (сделано C16): тип шага (`StepCard`) — приглушённый mono-текст, не
|
||||
синий бейдж; индекс шага — синий `#1769D2`.
|
||||
- **F3** (сделано C16): подпись превью-тайла (`PreviewTile`) — синяя `#1769D2`.
|
||||
- **F4** (решено — одна колонка): реализуется в `PreviewStack` (Этап 0),
|
||||
колонка = `1fr`.
|
||||
- **F5** (открыт): вес `MonoLabel` 500 → 400 (как в рефе).
|
||||
- **F6** (открыт): мета-подписи (MetaRow/MetaList, DIMENSIONS/FORMAT/SIZE)
|
||||
fs 10px → 9px.
|
||||
- **F7** (низкий): копирайт/стиль — у нас `+ Add tool` (реф `Add tool`);
|
||||
кнопка `Download result` — сверить с рефом (синяя, ~478×42). Проверить
|
||||
визуально (замеры NOT FOUND — артефакт вложенного чипа размера).
|
||||
- **F8** (низкий): `h1` line-height чуть выше рефа (150px vs 115px).
|
||||
- **F9** (открыт): вертикальный ритм — подровнять паддинги секций под реф
|
||||
(pipeline-head и пр.).
|
||||
- **F10** (открыт): внутренние отступы preview-панели (паддинг панели).
|
||||
|
||||
После Этапа 1 — повторный `refs-cdp-audit`, зафиксировать остаточный diff (sub-pixel /
|
||||
структурный) и обновить статус пунктов выше прямо в этом файле.
|
||||
|
||||
## Этап 2 — plan-redesign.md (C17–C21), ОТЛОЖЕНО
|
||||
|
||||
Выполняется **только после** того, как новый дизайн доведён до приемлемого
|
||||
уровня верности (Этапы 0–1). Сами пункты — в `plan-redesign.md` §10 Шаг 5–6:
|
||||
|
||||
- **C17**: `preview/*` → реальные маршруты, удалить `(old)/`, корневой
|
||||
`+layout.svelte` ← `design2.css`, поправить `newCode` в `eslint.config.js`
|
||||
(`src/routes/preview/**` → `src/routes/**`).
|
||||
- **C18**: правки импортов/редиректов, проверка билда.
|
||||
- **C19**: удалить старое `ui/` + `app.css`, `kit/` → `ui/` (или оставить),
|
||||
раскрыть ESLint `recommended` на весь код.
|
||||
- **C20**: брейкпоинты 1200/1100/800/480; build без предупреждений; `grep` по
|
||||
старым токенам/классам пуст.
|
||||
- **C21**: опц. удалить `refs/` и `refs-html/` из репо.
|
||||
|
||||
## Порядок коммитов (Этап 0)
|
||||
|
||||
1. `kit/`: `FieldGrid`, `ToggleRow`, `PreviewStack`, `SectionLabel`, `FileChip`,
|
||||
`PipelineFooter`, `WorkspaceLayout` — по 1–2 на коммит.
|
||||
2. `tools/[id]`: `ParamControl` → `SettingsPanel` + `PreviewPanel` → тонкая
|
||||
страница.
|
||||
3. `demo`: переписать на обёртки.
|
||||
|
||||
После каждого коммита: `svelte-check` + `lint` + `build` + `refs-cdp-audit`.
|
||||
@@ -0,0 +1,76 @@
|
||||
# План: закрытие EASY/MEDIUM-пробелов из сравнения с onlinepngtools
|
||||
|
||||
> **СТАТУС: ВЫПОЛНЕН (2026-09-07) — устарел.** Все волны W1–W9 закрыты.
|
||||
> Идеи из раздела «Вне очереди» (region-инструменты, мультифайловый вывод,
|
||||
> анимационные, HARD-серия) перенесены в `backlog.md`.
|
||||
|
||||
> Статус: черновик на ревью
|
||||
|
||||
## Принципы
|
||||
|
||||
- Волны собираются вокруг общего математического ядра: одна волна = один движок + N инструментов на нём.
|
||||
- Каждая волна оформляется отдельным планом при старте; здесь — очередь и состав.
|
||||
- Порядок: сначала кластеры, дающие много инструментов малой кровью, затем одиночные эффекты, MEDIUM-пакеты в конце.
|
||||
- Полный список параметров каждого инструмента живёт в карте инструментов (tools-map.md); здесь только состав и ядро.
|
||||
|
||||
## Очередь волн
|
||||
|
||||
### W1. Палитры — ВЫПОЛНЕНА (11 инструментов)
|
||||
|
||||
Ядро: RGB↔HSL + гармонии цветового круга. Вывод: свотч-полотно (генератор).
|
||||
Состав: color-wheel, complementary, monochromatic, analogous, triadic, tetradic, similar-shades, sort-colors.
|
||||
Плюс утилиты смешения тем же ядром: mix-colors, average-color, blend-two, step-between (+4, итого 12).
|
||||
|
||||
### W2. Каналы и пространства — ВЫПОЛНЕНА (6 инструментов)
|
||||
|
||||
Ядро: матрицы преобразования RGB→(HSL/HSV/HSI/CMYK/YCbCr/LAB) + визуализация выбранного компонента серым или окрашенно.
|
||||
Общий select «компонент» + select «режим отображения».
|
||||
|
||||
### W3. Маски по свойствам пикселей — ВЫПОЛНЕНА (7 инструментов)
|
||||
|
||||
Ядро: предикат над пикселем → бинарная маска (с инверсией и подсветкой цветом).
|
||||
Состав: show-transparent, show-grayscale, show-color, light-mask, dark-mask, unique-color-mask, extract-by-color.
|
||||
|
||||
### W4. Фигурные маски — ВЫПОЛНЕНА (4 инструмента)
|
||||
|
||||
Ядро: SDF фигуры (круг/квадрат/звезда/волна) → альфа-маска с fit-режимами.
|
||||
Состав: circle-mask, square-mask, star-mask, wavy-mask.
|
||||
|
||||
### W5. Геометрия-добивки — ВЫПОЛНЕНА (5 инструментов)
|
||||
|
||||
Ядро: bbox по альфе (trim) переиспользуется тремя инструментами источника.
|
||||
Состав: trim-empty-space (закрывает remove border/space), change-canvas-size, change-aspect-ratio, landscape↔portrait, symmetric-copy.
|
||||
|
||||
### W6. Эффекты лёгкие — ВЫПОЛНЕНА (6 инструментов; color-blocks покрыт pixelate)
|
||||
|
||||
Состав: pixelate, color-blocks, randomize-pixels (seed), add-noise, feather-edges, clean-edges, silhouette.
|
||||
Shadow/glow — сюда же, если потянет этап: оба = размытая альфа + смещение + цвет (ядро blur уже есть).
|
||||
|
||||
### W7. Конвертеры и генераторы — ВЫПОЛНЕНА (11 инструментов)
|
||||
|
||||
PNG↔bytes, PNG↔rgb-values (текстовые результаты), verify-is-png (анализ сигнатуры),
|
||||
text-to-png (domText без входной картинки), emoji-to-png, placeholder-png,
|
||||
color-spectrum, colorful-random (seed), draw-grid.
|
||||
multi-color-gradient — если успеем новый тип параметра «список цветов», иначе перенос.
|
||||
|
||||
### W8. Цветовые MEDIUM — ВЫПОЛНЕНА (4 инструмента; median-cut + Floyd–Steinberg/Bayer, custom-palette через text-параметр)
|
||||
|
||||
Ядро: квантование (median-cut или k-means).
|
||||
Состав: quantize (k), decrease-color-count (=quantize с пресетами), custom-palette (маппинг на список цветов — нужен тип параметра «список»), dithering (Floyd–Steinberg/Bayer поверх квантования).
|
||||
|
||||
### W9. Сжатие — ВЫПОЛНЕНА (3 инструмента; optimize/change-quality осознанно не выделены — покрыты пресетами compress и W8)
|
||||
|
||||
Без wasm честное управление размером PNG ограничено: реальный рычаг — квантование (W8) + обрезка метаданных + итеративный подбор под целевой KB.
|
||||
Состав: compress (пресеты усилий), reduce-to-size (целевой KB, бинарный поиск по k), optimize (re-encode), change-quality/low-quality — переосмыслить как пресеты W8; jpeg-artifacts уже покрывает «испортить».
|
||||
|
||||
## Вне очереди
|
||||
|
||||
- Region-инструменты (censor/erase/pixelate-area/blur-area/sharpen-area/reverse-area) — ждут UI выделения области на превью; отдельное решение.
|
||||
- Мультифайловый вывод (split-parts, gif-frames, separate-colors) — ждут механизм «результат = набор файлов».
|
||||
- Анимационные (slow-reveal/fade/scrolling) — выход не PNG; отдельное решение о формате.
|
||||
- HARD: glitch, barcode, signature-extract, handwritten-digital.
|
||||
- Нишевые серии logo/icon/stamp/signature — не копируем.
|
||||
|
||||
## Оценка масштаба
|
||||
|
||||
W1–W7 дают **~47 новых инструментов**, почти все EASY. W8 добавляет ядро квантования (открывает custom-palette/dithering и улучшает W9). Суммарно каталог достигает ~120+ при текущих 71.
|
||||
@@ -0,0 +1,54 @@
|
||||
# План: параметры между исходником и результатом
|
||||
|
||||
> **СТАТУС: УСТАРЕЛ (2026-09-07).** Раскладка описывалась для старого
|
||||
> `ToolPage`; перекрыта редизайном (`plan-redesign.md`) — страницы инструментов
|
||||
> теперь `SchemaToolView`/`SchemaPreview` в новом дизайне. Сама идея «инлайн:
|
||||
> исходник → параметры → результат» живёт в `backlog.md` (№2).
|
||||
|
||||
> Статус: план к выполнению.
|
||||
|
||||
## 1. Что получается
|
||||
|
||||
Альтернативная раскладка этапа инструмента: три колонки «Исходник | Параметры | Результат» на широких экранах, вертикальный стек на узких. Цель — отдать вариант дизайнеру как отправную точку. Откат к текущей раскладке — замена одной строки в `ToolPage`.
|
||||
|
||||
## 2. Решения
|
||||
|
||||
- Два взаимозаменяемых компонента этапа с одинаковым набором пропсов; `ToolPage` рендерит один тег. Попробовать новое — поменять компонент в этом теге; вернуть старое — поменять обратно. Никаких флагов и конфигов.
|
||||
- Общая типизация пропсов в одном файле рядом с компонентами; сами карточки (`SourceCard`, `ResultCard`, `TextInputCard`, `ParamsCard`) переиспользуются как есть — вся логика исполнения, маски, пипетки и автозапуска остаётся в `ToolPage` и приходит сверху.
|
||||
- Chain-звенья используют ту же панельную систему «Вход | Параметры | Результат» — общие классы `.pane` / `.pane-legend` / `.pane-params` вынесены в app.css, вертикальные поля в узкой колонке заданы один раз глобально.
|
||||
- Один порог отзывчивости: три колонки от ~75rem, ниже — стек «исходник → параметры → результат». Промежуточные перестроения не придумываем до фидбека дизайнера.
|
||||
|
||||
## 3. Компоненты
|
||||
|
||||
```txt
|
||||
src/lib/components/stage/
|
||||
stage-props.ts # интерфейс StageProps + сборщик дефолтов
|
||||
ToolStageClassic.svelte # текущая разметка .tool-stage, перенесена 1:1 из ToolPage
|
||||
ToolStageInline.svelte # новая сетка [source | params | result]
|
||||
```
|
||||
|
||||
- `ToolStageClassic` — вырезка существующего шаблона со стилями; после извлечения страница должна быть пиксель-в-пиксель прежней.
|
||||
- `ToolStageInline` — grid-колонки `minmax(0, 1fr) | фикс-ширина параметров | minmax(0, 1fr)`; легенда «Параметры» над средней колонкой в том же стиле edge-legend.
|
||||
- Модификаторы состояний через классы на корне компонента: `no-params` (инструменты без параметров → две колонки), `no-source` (генераторы → параметры и результат), текстовый вход до декодирования занимает левую ячейку как сейчас.
|
||||
|
||||
## 4. Этапы и проверки
|
||||
|
||||
Каждый этап заканчивается зелёными test/check/build и ревью.
|
||||
|
||||
- **A. Извлечение классики** — перенос разметки и стилей этапа из `ToolPage` в `ToolStageClassic` без изменений поведения; пропсы собираются в одном объекте. Критерий: страница неотличима, diff по логике `ToolPage` нулевой.
|
||||
- **B. Инлайн-раскладка** — `ToolStageInline`, переключение в `ToolPage` на новый компонент, прогон смоука §5. Ревью в браузере; решение дизайнера — потом, код уже позволяет жить в обоих вариантах.
|
||||
|
||||
## 5. Смоук
|
||||
|
||||
1. Замена компонента этапа — одна строка; обе версии собираются без предупреждений.
|
||||
2. Инструмент без параметров (например Автоконтраст): две колонки, пустой середины нет.
|
||||
3. Генератор без исходника: параметры и результат в две колонки.
|
||||
4. Текстовый инструмент до декодирования: вход слева, параметры в центре, пустое состояние результата справа.
|
||||
5. Маска, пипетка, слайдеры в средней колонке; изменение параметра перезапускает инструмент как раньше.
|
||||
6. Узкий экран: стек без горизонтального скролла, порядок исходник → параметры → результат.
|
||||
7. Легенды присутствуют у всех трёх зон в обеих раскладках.
|
||||
|
||||
## 6. Что сознательно не делаем
|
||||
|
||||
- Редизайн визуального языка (цвета, типографика, формы) — материал для дизайнера, не для этого плана.
|
||||
- Новая логика: исполнение, автосохранение, пресеты — раскладка ничего не знает про исполнение.
|
||||
@@ -0,0 +1,102 @@
|
||||
# Plan: `/preview/list-tools` → design2 (catalog)
|
||||
|
||||
> **СТАТУС: ВЫПОЛНЕН (2026-09-07) — устарел.** Каталог
|
||||
> `/preview/list-tools` переписан на `CatalogHeader` / `CatalogToolbar` /
|
||||
> `CatalogGroup` / `ToolCard` (kit), поиск и фильтры функциональны.
|
||||
> Доводка верности против рефа остаётся в рамках `plan-design-fix.md`.
|
||||
|
||||
Ветка `feat/redesign`. Цель — привести каталог инструментов к рефу
|
||||
`refs-html/list-tools.html`. Сейчас страница вообще не на design2: по
|
||||
мульти-аудиту `onlyOurs=257, onlyRef=54` (демо-страница — `18/11`).
|
||||
|
||||
## Текущее состояние
|
||||
|
||||
- `src/routes/preview/list-tools/+page.svelte` использует собственную вёрстку
|
||||
(`.catalog` / `.cat-head` / `.cat-grid`) + `Panel` / `PanelHeading` /
|
||||
`ToolCard` (kit). Нет поиска, фильтров, счётчика, навигация по категориям
|
||||
сделана через `Panel`-блоки, а не через `.catalog-groups`.
|
||||
- Реф (`list-tools.html`) строит:
|
||||
- `.catalog-page` → `.catalog-head` (`.eyebrow`, `h1` «Tool catalog»,
|
||||
`p` lede, `.catalog-total` «NN TOOLS AVAILABLE» с большим числом),
|
||||
- `.catalog-toolbar` (`.catalog-search` + `.catalog-filters` с кнопками),
|
||||
- `.catalog-groups` (grid `repeat(2,minmax(0,1fr))`, gap `56px 28px`) →
|
||||
`.catalog-group` (`.group-title` «CAT · NN TOOLS») → `.tool-cards` →
|
||||
`.tool-card`.
|
||||
- `.tool-card` (реф) — `display:grid`, колонки `42px minmax(0,1fr) 24px 18px`,
|
||||
`min-height:106px`, `gap:14px`, `padding:16px`: `.tool-icon` (42×42
|
||||
синий размытый квадрат), `.tool-copy` (`strong` 13px mono + `span` 12px mono
|
||||
muted), `.tool-index` (10px mono, сверху), `.tool-arrow` (появляется при hover).
|
||||
- Токены `design2.css` уже совпадают с рефом (`--background #eef1f4`,
|
||||
`--panel #f8fafb`, `--foreground #17212b`, `--muted #6d7883`, `--line #cbd3da`,
|
||||
`--blue #1769d2`, `--radius 4px`) — править токены не нужно.
|
||||
|
||||
## Шаги
|
||||
|
||||
1. **Restyle `kit/ToolCard.svelte`** под реф (меняется «на месте» — используется
|
||||
ещё в `preview/+page` и `preview/kit`, стиль унифицируется, ок):
|
||||
- grid-колонки `42px minmax(0,1fr) 24px 18px`, `align-items:center`,
|
||||
`gap:14px`, `min-height:106px`, `padding:16px`, `border:1px solid line`,
|
||||
`background:panel`, `border-radius:radius`.
|
||||
- `.tool-icon`: 42×42 `place-items:center`, `background:color-mix(in srgb,
|
||||
var(--blue) 12%, transparent)`, `color:var(--blue)` (реф использует
|
||||
`--panel-strong`; вместо нового токена берём этот `color-mix`).
|
||||
- `.tool-copy strong`: `600 13px var(--font-mono)`; `span`: `12px/1.5 mono`
|
||||
`muted`.
|
||||
- `.tool-index`: `10px mono muted`, `align-self:start`.
|
||||
- `.tool-arrow`: `color:var(--blue)`, `opacity:0` → `1` + `translate(2px,-2px)`
|
||||
на `.tool-card:hover`.
|
||||
- DOM-порядок как в рефе: `icon, copy, index, arrow`.
|
||||
- `href` через `resolve` из `$app/paths` (убрать `svelte-ignore`).
|
||||
|
||||
2. **`kit/CatalogHeader.svelte`**: `.eyebrow` «EASY-PNG-TOOLS / CATALOG»,
|
||||
`h1` «Tool catalog», lede; `.catalog-total` с динамическим `TOOLS.length`
|
||||
(реф показывает `32`).
|
||||
|
||||
3. **`kit/CatalogToolbar.svelte`**: `.catalog-search` (иконка + `<input>`) и
|
||||
`.catalog-filters` (кнопки по категориям). Состояние `query` / `category`
|
||||
поднимается наружу через `bind:` (или callback-пропы), чтобы страница
|
||||
фильтровала список. Фильтры функциональные (клиентский фильтр по названию +
|
||||
категории), не декоративные.
|
||||
|
||||
4. **`kit/CatalogGroup.svelte`**: `.group-title` (uppercase label категории +
|
||||
«NN TOOLS») + слот со списком карточек; сетка `.tool-cards` — `gap:10px`
|
||||
(реф), на мобиле (`@media max-width:800px`) всё схлопывается в 1 колонку.
|
||||
|
||||
5. **Переписать `preview/list-tools/+page.svelte`** как тонкую композицию:
|
||||
- обёртка `.catalog-page` (`padding:60px clamp(24px,4vw,72px) 72px`);
|
||||
- `<CatalogHeader />`;
|
||||
- `<CatalogToolbar bind:query bind:category />`;
|
||||
- `.catalog-groups` из отфильтрованных групп (по `CATEGORIES`, пересчитав
|
||||
`tools` с учётом `query`/`category`);
|
||||
- внутри `<CatalogGroup>` — `<ToolCard>` на каждый tool (`href=
|
||||
"/preview/tools/{id}"`, `icon` из `tool-icons`, `index` по порядку,
|
||||
`title`/`description` из registry);
|
||||
- убрать `Panel`/`PanelHeading` из этого маршрута.
|
||||
|
||||
6. **Категории/локализация**: labels привести к рефу (реф: ALL / CONVERT /
|
||||
TRANSPARENCY / COLOR / GEOMETRY / FILTERS / ANALYZE). Наши `CATEGORIES`
|
||||
(`convert/alpha/color/geometry/filters/text/analyze/generate`) — сопоставить
|
||||
(`alpha`→TRANSPARENCY и т.п.); точное сопоставление уточнить по рефу и
|
||||
`i18n/ru.ts`. Фильтры = ALL + те же label'ы. Тексты заголовков/lede — по
|
||||
рефу (англ. «Tool catalog» / «Focused utilities for working with PNG…»),
|
||||
либо оставить RU-вариант, если это сознательное отклонение (зафиксировать).
|
||||
|
||||
7. **Адаптив**: перенести правила рефа (`@media max-width:800px`) —
|
||||
`.catalog-groups` → 1fr, `.tool-card` → `38px minmax(0,1fr) 18px`, скрыть
|
||||
`.tool-index`/показать `.tool-arrow`, `.catalog-head` в колонку и т.д.
|
||||
|
||||
## Верификация
|
||||
|
||||
- `pnpm lint`, `pnpm exec svelte-check --tsconfig ./tsconfig.json`,
|
||||
`pnpm build` — без ошибок.
|
||||
- `pnpm refs-audit` (теперь мульти-маршрутный) → глянуть секцию
|
||||
`/preview/list-tools`: `onlyOurs`/`onlyRef` должны упасть с `257/54`
|
||||
примерно до уровня demo (`18/11`). Остаточный diff (если будет) зафиксировать
|
||||
в этом файле.
|
||||
|
||||
## Примечания
|
||||
|
||||
- `ToolCard` правится на месте — влияет на `preview/+page` и `preview/kit`
|
||||
(там тоже карточки каталога, унификация уместна).
|
||||
- Старый дизайн (`routes/(old)/**`, `components/search/**`) не трогать.
|
||||
- Коммиты делает автор (агент не коммитит).
|
||||
@@ -0,0 +1,182 @@
|
||||
# План: дожать перевод инструментов в preview и переезд ветки `old/`
|
||||
|
||||
> **СТАТУС: ЗАКРЫТ (2026-09-07).** Перевод каталога завершён — 121 из 125
|
||||
> инструментов в `registry-new`; 4 хвоста отложены решениями и перенесены в
|
||||
> `backlog.md` (`watermark-image-png` → overlay-райз, `png-info` → exif-райз,
|
||||
> `reduce-to-size-png` → download-райз, `compress-png` → закрыт дубликатом).
|
||||
> Дальнейшие «хвосты плана» — переезд старых компонентов в папки `old/`,
|
||||
> закрытие долгов и сбор фидбека — ~уже учитываются в `plan-composite-params.md`
|
||||
> (шаги 36–37) и `plan-design-fix.md`, поэтому план закрыт, а не продолжен.
|
||||
|
||||
> Архивная копия, ниже — состояние на момент закрытия.
|
||||
|
||||
> Статус: **переведено 121 из 125; остальные 4 — отложены/закрыты решениями.**
|
||||
> Текущее
|
||||
> состояние: **121 инструмент переведён в `registry-new`**, старый UI уже живёт
|
||||
> на `(old)/`-маршрутах, линтер-изоляция old↔preview на месте
|
||||
> (плагин `isolation/no-mixed-imports`). Здесь — что сделать, чтобы:
|
||||
>
|
||||
> 1. в preview был **весь** каталог инструментов (плюс закрыты известные долги);
|
||||
> 2. физически перенести старую ветку в папки `old/` (шаг 37 основного плана);
|
||||
> 3. после переезда провести детальную проверку в реальных условиях и собрать
|
||||
> отзывы пользователей (правки поверх — отдельными задачами).
|
||||
>
|
||||
> Порядок: перевод остатка → починить известные долги → переезд в `old/` →
|
||||
> пользовательская проверка/фидбек → (позже, отдельным решением) удаление
|
||||
> старого кода.
|
||||
|
||||
## Текущая картина (факты)
|
||||
|
||||
- `registry.ts` + `registry/` — **125 инструментов**; `registry-new/` — **121**.
|
||||
Не переведены только: `png-info` (отложен, хвост-фича с exif), `compress-png`
|
||||
и `reduce-to-size-png` (закрыто решениями, см. ниже), `watermark-image-png`
|
||||
(overlay — единственный реальный остаток).
|
||||
- Preview (`/preview`) показывает переведённые; старый UI работает
|
||||
на `/`-маршрутах (группа `(old)/`): `+page`, `demo`, `list-tools`,
|
||||
`tools/[id]`. Переведено всё кроме четырёх хвостов: color 18, filters 3,
|
||||
geometry 13, alpha 13 (+watermark-image в остатке), generate 10 (mix/sort),
|
||||
analyze 12 (6 масок + verify + 5 вердиктов), convert 14 (jpg/webp/bmp +
|
||||
5 png→texт + 6 text→png), text `watermark-tile-png`.
|
||||
- Плагин `isolation/no-mixed-imports` (`web/eslint-plugins/isolation/`)
|
||||
полностью разделяет ветки: старый `lib/registry.ts|/registry/**`,
|
||||
`lib/registry-helpers.ts`, `lib/categories.ts`, `lib/tools/**`,
|
||||
`lib/components/**`(кроме `kit/`) vs новый `routes/preview/**`,
|
||||
`lib/registry-new/**`, `lib/preview/**`, `lib/registry-schema.ts`,
|
||||
`lib/components/kit/**`. Общее: `core/`, `i18n/`, `theme`, `assets/`,
|
||||
корневой `lib`.
|
||||
- Правило копий (из основного плана): файл, используемый `(old)`, не трогаем —
|
||||
делаем копию в `lib/preview/` (пример: `lib/preview/tool-icons.ts`).
|
||||
- Известные долги (предсуществующие, не создавались нами): ошибка
|
||||
`ToolCard.svelte:19:36` (svelte-check), фейл `palette.test.ts` (ENOENT
|
||||
`src/app.css` — из-за него `pnpm test` красный при 607 passed), ~335
|
||||
design-tokens ошибок `lint:all` в старых kit-компонентах, a11y-warning в
|
||||
`ColorField.svelte`, пустой ruleset в `Toggle.svelte`, неиспользуемые токены
|
||||
в `preview.css`.
|
||||
|
||||
## Скоуп перевода: переведено всё переводимое (отложено 4 хвоста)
|
||||
|
||||
Каталог дожат до предела текущих решений: переведено 121 из 125. Оставшиеся
|
||||
4 инструмента **отложены осознанно** (не делаем сейчас):
|
||||
|
||||
- **`watermark-image-png` (alpha)** — **отложен**. Нужна overlay-механика
|
||||
(второе изображение-знак через `getOverlay`/store + параметры
|
||||
scale/opacity/position/margin/aspect); решается отдельно, вместе с вопросом,
|
||||
как второй источник изображения вписывается в schema-driven preview.
|
||||
- **`png-info`** — **отложен осознанно**: будет серьёзно дорабатываться
|
||||
отдельным райзом (exif-теги, редактирование, структурированный вывод),
|
||||
для него нужен свой отдельный случай в UI, не «ещё один text/verdict».
|
||||
- **`compress-png`** — **закрыт как дубликат** `decrease-color-count-png`:
|
||||
пресеты сжатия 192/96/44 добавлены прямо в схему `decrease-color-count`.
|
||||
- **`reduce-to-size-png`** — **отложен** до достройки кнопки Download
|
||||
(backlog №7, «экспорт с лимитом размера»).
|
||||
|
||||
### Переведено в этом проходе (text-механика)
|
||||
|
||||
- **Типы:** `ToolEntry.input: "file" | "text" | "none"`,
|
||||
`ToolEntry.result: "image" | "text" | "verdict"`, `OutputFormat` (mime/ext/
|
||||
qualityParamId) для download.
|
||||
- **Executor:** `executeFromText` / `executeToText` / `executeTextToText`
|
||||
(прямые, без worker), экспорт в `preview/executor/index.ts`.
|
||||
- **UI:** `SchemaTextSource` (textarea + Render), `SchemaTextResult`
|
||||
(кол-блок с Copy/Download .txt или бейдж-вердикт по префиксу Yes/No),
|
||||
интеграция в `SchemaPreview`/`SchemaToolView` (режимы file/text/generate,
|
||||
рендер image/text/verdict, скрытие image-download для text-результата).
|
||||
- **Инструменты:** convert 5×png→text (base64/data-uri/hex/bytes/rgb-values),
|
||||
6×text→png (base64/data-uri/hex/bytes/rgb-values/svg, +width), analyze
|
||||
verify-is-png (text→text) и 5 вердиктов (grayscale/file-size/transparent/
|
||||
orientation). Доработка: `base64ToBytes` сужен до `Uint8Array<ArrayBuffer>`
|
||||
(совместимость с `decodeBytes`).
|
||||
- **Гейты:** eslint 0, prettier чист, svelte-check — только предсуществующий
|
||||
ToolCard, тесты — только предсуществующий фейл `palette.test.ts`.
|
||||
|
||||
## Известные долги перед переездом (завести tasks и закрыть)
|
||||
|
||||
По AGENTS.md техдолг «чинится только по заведённым tasks, не игнорируется»:
|
||||
`lint:all` (~335 design-tokens в kit), неиспользуемые токены preview.css,
|
||||
a11y `ColorField`, пустой ruleset `Toggle`. Плюс два, мешающих зелёной
|
||||
проверке до/после переезда: svelte-check error `ToolCard.svelte:19:36` и фейл
|
||||
`palette.test.ts` (ENOENT `src/app.css`). После их закрытия целевое состояние
|
||||
проверок перед переездом: `pnpm --dir web exec svelte-check` → 0 errors,
|
||||
`pnpm --dir web test` → зелёный, `pnpm --dir web lint:all` → только задокумент.
|
||||
ированный остаток (или 0).
|
||||
|
||||
## Шаги перевода остатка (порядок)
|
||||
|
||||
1. **Простые пачки** — **выполнено**: color (18) → filters (3) → geometry (13)
|
||||
→ alpha (13/14, кроме watermark-image) → generate (10, включая mix/sort
|
||||
через kind `colors`) → analyze-маски (6) → text `watermark-tile-png`.
|
||||
2. **Text-механика** — **выполнено**: типы `input`/`result` + output-формат,
|
||||
executor `executeFromText`/`toText`/`textToText`, UI
|
||||
`SchemaTextSource`/`SchemaTextResult` + интеграция в preview, перевод
|
||||
конвертеров convert (5 png→text + 6 text→png) и analyze (verify + 5
|
||||
вердиктов). `png-info` отложен отдельным райзом (exif).
|
||||
3. **Шаг про overlay не делается** — `watermark-image-png` **отложен решением**
|
||||
(см. «Скоуп перевода» выше), отдельным райзом вместе с вопросом второго
|
||||
источника изображения в schema-driven preview.
|
||||
4. После категории — `schema.layout` для сгруппированных инструментов
|
||||
(шаг 32-33 паттерн) и ручная проверка в `/preview`.
|
||||
|
||||
## Переезд в `old/` (шаг 37 основного плана)
|
||||
|
||||
> Скоп-сдвиг: «Scoped-пути двигаются вместе с папками» — обновляется
|
||||
> `eslint.config.js` (isolation-паттерны и design-tokens glob), не код правила.
|
||||
|
||||
1. **Что переезжает:** `lib/registry.ts`, `lib/registry/**`,
|
||||
`lib/registry-helpers.ts`, `lib/categories.ts`, `lib/tools/**`,
|
||||
`lib/components/**` (кроме `kit/`) → папки `old/` (например
|
||||
`lib/old/registry …`, `lib/old/tools`, `lib/old/components`) — то есть всё
|
||||
старое в одном поддереве. `routes/(old)/` остаётся как есть (уже отделено).
|
||||
2. **Обновить конфиг плагина** `isolation/no-mixed-imports`: старые glob-ы
|
||||
`lib/registry/**…` заменить на `lib/old/**` (+ `lib/old/components/**` и
|
||||
т.п.), «новое» и «общее» не меняются. Проверить, что правило по-прежнему
|
||||
разделяет (тест фолс-позитивов: временный импорт old↔new ловится).
|
||||
3. **Импорты внутри старого кода** — относительные пути остаются рабочими
|
||||
(папки двигаются целиком); поправить только то, что ссылалось на `$lib/...`
|
||||
в другом стиле. Старые тесты (`registry.test.ts` и др.) переезжают вместе с
|
||||
исходниками и продолжают проходить.
|
||||
4. **Гейты переезда:** `svelte-check` 0 errors, `pnpm --dir web test` зелёный,
|
||||
`prettier --check .` чистый, `lint:all` = задокументированный остаток,
|
||||
обе ветки открываются (старая `/`, новая `/preview`).
|
||||
|
||||
## Проверка и сбор отзывов после переезда
|
||||
|
||||
1. **Регресс-чеклист по каталогу** — пройти все инструменты в `/preview`:
|
||||
дефолты совпадают со старыми, валидация/клампы поведения не меняют,
|
||||
результат эквивалентен старому UI (сверить на одинаковых входах).
|
||||
2. **Тяжёлые кейсы руками:** генераторы, маски, font-style/plate/gradient на
|
||||
всем наборе, text-source, output-формат. (`watermark-image` — позже, после
|
||||
overlay-райза.)
|
||||
3. **Сбор фидбека** — отдельные задачи на каждый найденный фикс; чеклист
|
||||
зафиксировать в этом документе по мере находок (или ссылкой на tasks).
|
||||
4. Сверка каталога с `tools-map.md` (покрытие/дедупликация
|
||||
`convert-png-to-jpg`/`webp`), при необходимости — обновить cards/мета.
|
||||
5. **Решение об удалении старого** — после завершения ревью и фидбека,
|
||||
отдельным шагом: тогда чистятся `lib/old/**`, `routes/(old)/**`,
|
||||
`old.css`, `exports`, изоляционный плагин (glob-ы больше не нужны) и
|
||||
открываются «правило копий»-дубли.
|
||||
|
||||
## Как ревьюить каждый шаг (гайды)
|
||||
|
||||
- Диф на **один инструмент** (или фундамент без инструментов) < ~500 строк.
|
||||
- Старый `params`/`run`/`defaultParams`/`sanitizeParams` не изменены;
|
||||
в `registry-new` копия логики + `schema` (дефолты равны старым).
|
||||
- Старый UI и старые тесты продолжают работать: `pnpm --dir web test`,
|
||||
`pnpm --dir web exec svelte-check`.
|
||||
- Новый инструмент проверяется **руками в `/preview`** end-to-end (применение +
|
||||
сброс дефолтов + смена значений).
|
||||
- После переезда каждый move-шаг гоняет 4 гейта (см. выше).
|
||||
|
||||
## Оценка трудозатрат
|
||||
|
||||
| Часть | Сложность | Оценка |
|
||||
| ---------------------------------------------------------------------- | -------------- | ------------------------- |
|
||||
| Простые инструменты (color/filters/geometry/alpha/generate/маски/text) | Низкая-Средняя | **выполнено** (62 шт) |
|
||||
| Text-механика + конвертеры/вердикты | Средняя | **выполнено** (16 шт) |
|
||||
| watermark-image / png-info / reduce-to-size (отложены решениями) | Высокая | отложено, отдельные райзы |
|
||||
| Долги (ToolCard, palette.test.ts, lint:all остаток) | Средняя | ~2-4ч |
|
||||
| Переезд в `old/` + конфиг плагина + гейты | Средняя-Низкая | ~2-4ч |
|
||||
| Проверка/фидбек + правки | Зависит | ~3-6ч |
|
||||
| **Итого** | | **~9-18ч** по фазам |
|
||||
|
||||
> Оценки ориентировочные; особые случаи — с ревью решения, объём финальных
|
||||
> правок по фидбеку не предсказуем.
|
||||
Reference in New Issue
Block a user