style: format docs markdown
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
# Allowlist: prettier в корне форматирует только markdown docs/.
|
||||||
|
# web/ и refs/ имеют свои тулинги, refs-html/ — генерат: всё остальное
|
||||||
|
# в корне (включая появившееся позже) игнорируется целиком.
|
||||||
|
/*
|
||||||
|
!/docs
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"proseWrap": "always"
|
||||||
|
}
|
||||||
@@ -11,9 +11,13 @@
|
|||||||
- Сборка: `pnpm --dir web build`, проверка типов: `pnpm --dir web exec svelte-check --tsconfig ./tsconfig.json`,
|
- Сборка: `pnpm --dir web build`, проверка типов: `pnpm --dir web exec svelte-check --tsconfig ./tsconfig.json`,
|
||||||
тесты: `pnpm --dir web test` (Vitest), e2e: `pnpm --dir web test:e2e` (Playwright,
|
тесты: `pnpm --dir web test` (Vitest), e2e: `pnpm --dir web test:e2e` (Playwright,
|
||||||
`web/e2e/`, свой webServer на `pnpm build` + `scripts/serve-static.mjs`, порт 4173).
|
`web/e2e/`, свой webServer на `pnpm build` + `scripts/serve-static.mjs`, порт 4173).
|
||||||
- Форматирование: `pnpm --dir web format` (Prettier + `prettier-plugin-svelte`,
|
- Форматирование: два независимых прогона. Корневой prettier форматирует
|
||||||
|
только markdown `docs/` (`.prettierrc` + allowlist `.prettierignore` в корне,
|
||||||
|
`pnpm format:docs`, проверка — `pnpm check:docs`). Код `web/` форматируется
|
||||||
|
отдельно: `pnpm --dir web format` (Prettier + `prettier-plugin-svelte`,
|
||||||
конфиг `web/.prettierrc`, игнор `web/.prettierignore`). Проверка без записи:
|
конфиг `web/.prettierrc`, игнор `web/.prettierignore`). Проверка без записи:
|
||||||
`pnpm --dir web exec prettier --check .`.
|
`pnpm --dir web exec prettier --check .`. Оба прогона сразу: `pnpm format`
|
||||||
|
в корне.
|
||||||
- Линтинг: `pnpm --dir web lint` (ESLint, flat-конфиг `web/eslint.config.js`).
|
- Линтинг: `pnpm --dir web lint` (ESLint, flat-конфиг `web/eslint.config.js`).
|
||||||
Устроен инкрементально:
|
Устроен инкрементально:
|
||||||
- На **весь код** — парсинг TS/Svelte + правило
|
- На **весь код** — парсинг TS/Svelte + правило
|
||||||
|
|||||||
@@ -51,7 +51,8 @@ Static output is written to `web/build/`.
|
|||||||
| `pnpm --dir web test` | Run unit tests (Vitest) |
|
| `pnpm --dir web test` | Run unit tests (Vitest) |
|
||||||
| `pnpm --dir web check` | Type-check (svelte-check) |
|
| `pnpm --dir web check` | Type-check (svelte-check) |
|
||||||
| `pnpm --dir web lint` | Lint (ESLint) |
|
| `pnpm --dir web lint` | Lint (ESLint) |
|
||||||
| `pnpm --dir web format` | Format (Prettier) |
|
| `pnpm --dir web format` | Format code (Prettier) |
|
||||||
|
| `pnpm format:docs` | Format docs (Prettier) |
|
||||||
| `pnpm --dir web preview` | Preview production build |
|
| `pnpm --dir web preview` | Preview production build |
|
||||||
|
|
||||||
## Project Structure
|
## Project Structure
|
||||||
|
|||||||
+208
-74
@@ -1,9 +1,13 @@
|
|||||||
# Анализ: набор PNG-утилит для браузера
|
# Анализ: набор PNG-утилит для браузера
|
||||||
|
|
||||||
Проект: **easy-png-tools** — набор PNG-утилит, работающий полностью в браузере. Вдохновлён идеей онлайн-сервисов «все PNG-операции в одном месте»: никаких загрузок на сервер, всё считается локально.
|
Проект: **easy-png-tools** — набор PNG-утилит, работающий полностью в браузере.
|
||||||
Стек: **SvelteKit + @sveltejs/adapter-static** (статический экспорт, никакого сервера). Все данные обрабатываются на клиенте, ничего не уходит в сеть.
|
Вдохновлён идеей онлайн-сервисов «все PNG-операции в одном месте»: никаких
|
||||||
|
загрузок на сервер, всё считается локально. Стек: **SvelteKit +
|
||||||
|
@sveltejs/adapter-static** (статический экспорт, никакого сервера). Все данные
|
||||||
|
обрабатываются на клиенте, ничего не уходит в сеть.
|
||||||
|
|
||||||
Источник идеи: типовой перечень утилит, который встречается в онлайн-наборах PNG-инструментов (~296 операций).
|
Источник идеи: типовой перечень утилит, который встречается в онлайн-наборах
|
||||||
|
PNG-инструментов (~296 операций).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -21,63 +25,121 @@
|
|||||||
| Тяжёлая математика без блокировки UI | Web Workers, SharedArrayBuffer, WASM (zlib, квантование, ML) |
|
| Тяжёлая математика без блокировки UI | Web Workers, SharedArrayBuffer, WASM (zlib, квантование, ML) |
|
||||||
| Экономия памяти при больших изображениях | `ImageBitmap`, работа тайлами, downscale до декорирования |
|
| Экономия памяти при больших изображениях | `ImageBitmap`, работа тайлами, downscale до декорирования |
|
||||||
|
|
||||||
**Вывод для архитектуры:** ~90% утилит — это одна и та же связка «декодировать → прогнать через пиксельную функцию → закодировать». Разумно сделать общий ядро (`core/pipeline.ts` + набор операций над `ImageData`) и тонькие страницы-обёртки в SvelteKit. Каждая утилита = отдельный маршрут со своим UI (drag&drop слева, превью, параметры, скачивание справа).
|
**Вывод для архитектуры:** ~90% утилит — это одна и та же связка «декодировать →
|
||||||
|
прогнать через пиксельную функцию → закодировать». Разумно сделать общий ядро
|
||||||
|
(`core/pipeline.ts` + набор операций над `ImageData`) и тонькие страницы-обёртки
|
||||||
|
в SvelteKit. Каждая утилита = отдельный маршрут со своим UI (drag&drop слева,
|
||||||
|
превью, параметры, скачивание справа).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2. Уровень EASY — canvas + ImageData, простые попиксельные операции и композиция
|
## 2. Уровень EASY — canvas + ImageData, простые попиксельные операции и композиция
|
||||||
|
|
||||||
Реализация: загрузка в canvas → `getImageData()` → цикл по пикселям / перекомпоновка / отрисовка на выходной canvas → `toBlob('image/png')`. Без сложных алгоритмов, каждая утилита — часы работы.
|
Реализация: загрузка в canvas → `getImageData()` → цикл по пикселям /
|
||||||
|
перекомпоновка / отрисовка на выходной canvas → `toBlob('image/png')`. Без
|
||||||
|
сложных алгоритмов, каждая утилита — часы работы.
|
||||||
|
|
||||||
### 2.1 Конвертация форматов и текстовые представления
|
### 2.1 Конвертация форматов и текстовые представления
|
||||||
|
|
||||||
- **convert-png-to-jpg / convert-jpg-to-png / convert-webp-to-png / convert-png-to-webp / convert-svg-to-png** — рисование на canvas + `toBlob()` нужного типа
|
- **convert-png-to-jpg / convert-jpg-to-png / convert-webp-to-png /
|
||||||
- **convert-bmp-to-png / convert-ico-to-png / convert-gif-to-png** (первый кадр) — нативное декодирование браузером
|
convert-png-to-webp / convert-svg-to-png** — рисование на canvas + `toBlob()`
|
||||||
|
нужного типа
|
||||||
|
- **convert-bmp-to-png / convert-ico-to-png / convert-gif-to-png** (первый кадр)
|
||||||
|
— нативное декодирование браузером
|
||||||
- **convert-png-to-bmp** — BMP-формат простой, ~30 строк энкодера
|
- **convert-png-to-bmp** — BMP-формат простой, ~30 строк энкодера
|
||||||
- **convert-png-to-base64 / convert-base64-to-png / data-uri (в обе стороны)** — `FileReader.readAsDataURL` + строка
|
- **convert-png-to-base64 / convert-base64-to-png / data-uri (в обе стороны)** —
|
||||||
- **convert-png-to-bytes / convert-bytes-to-png / convert-png-to-hex / convert-hex-to-png / convert-png-to-rgb-values / convert-rgb-values-to-png** — чтение/запись `ImageData` в текст
|
`FileReader.readAsDataURL` + строка
|
||||||
|
- **convert-png-to-bytes / convert-bytes-to-png / convert-png-to-hex /
|
||||||
|
convert-hex-to-png / convert-png-to-rgb-values / convert-rgb-values-to-png** —
|
||||||
|
чтение/запись `ImageData` в текст
|
||||||
- **convert-png-to-gif** (один кадр) — `gifenc`, тривиально
|
- **convert-png-to-gif** (один кадр) — `gifenc`, тривиально
|
||||||
|
|
||||||
### 2.2 Прозрачность и альфа-канал
|
### 2.2 Прозрачность и альфа-канал
|
||||||
|
|
||||||
- **create-transparent-png, remove-color-from-png(простая версия), transparent-logo-maker, remove-logo/icon-background (по цвету)** — замена цвета на прозрачность по порогу близости
|
- **create-transparent-png, remove-color-from-png(простая версия),
|
||||||
- **remove-alpha-channel-from-png, fill-alpha-channel-in-png, replace-alpha-channel-in-png, extract-alpha-channel-from-png, invert-png-alpha-channel** — манипуляции только с A-компонентой
|
transparent-logo-maker, remove-logo/icon-background (по цвету)** — замена
|
||||||
- **generate-png-alpha-mask, show-transparent-png-areas, fill-transparent-png-pixels, convert-semi-transparent-png-to-opaque** — пороги и заливки по альфе
|
цвета на прозрачность по порогу близости
|
||||||
|
- **remove-alpha-channel-from-png, fill-alpha-channel-in-png,
|
||||||
|
replace-alpha-channel-in-png, extract-alpha-channel-from-png,
|
||||||
|
invert-png-alpha-channel** — манипуляции только с A-компонентой
|
||||||
|
- **generate-png-alpha-mask, show-transparent-png-areas,
|
||||||
|
fill-transparent-png-pixels, convert-semi-transparent-png-to-opaque** — пороги
|
||||||
|
и заливки по альфе
|
||||||
- **harden-png-edges** — бинаризация альфы по порогу
|
- **harden-png-edges** — бинаризация альфы по порогу
|
||||||
|
|
||||||
### 2.3 Цвет: замена, тон, каналы, цветовые пространства
|
### 2.3 Цвет: замена, тон, каналы, цветовые пространства
|
||||||
|
|
||||||
- **change-png-color, change-png-color-tone, change-png-opacity, swap-png-colors, change-png-brightness, change-png-contrast, convert-png-to-grayscale, add-sepia-tone-to-png, change-png-hue, invert-png-colors, reverse-png-colors, convert-png-to-two-colors, convert-png-to-black-and-white, create-monochrome-png, generate-single-color-png** — попиксельные арифметики/преобразования
|
- **change-png-color, change-png-color-tone, change-png-opacity,
|
||||||
- **extract-png-color-channels, swap-rgba-color-channels, split-png-into-rgb-components** — перестановка/выделение RGBA-каналов
|
swap-png-colors, change-png-brightness, change-png-contrast,
|
||||||
- **convert-png-to-hsl/hsv/hsi/cmyk/ycbcr/lab-colors** — известные формулы конвертации (Lab — чуть больше математики, но детерминировано)
|
convert-png-to-grayscale, add-sepia-tone-to-png, change-png-hue,
|
||||||
- **generate-\*-color-palette (complementary/monochromatic/analogous/triadic/tetradic), generate-similar-color-shades, color-wheel-generator, mix-multiple-colors, calculate-average-color, simulate-paint-colors, blend-two-colors, step-between-two-colors, convert-color-names-to-png** — генераторы палитр/смешение без работы с изображением
|
invert-png-colors, reverse-png-colors, convert-png-to-two-colors,
|
||||||
- **posterize-png, create-color-spectrum-png, create-multi-color-gradient-png, generate-png-gradient-palette** — простые градиенты/квантование уровней
|
convert-png-to-black-and-white, create-monochrome-png,
|
||||||
|
generate-single-color-png** — попиксельные арифметики/преобразования
|
||||||
|
- **extract-png-color-channels, swap-rgba-color-channels,
|
||||||
|
split-png-into-rgb-components** — перестановка/выделение RGBA-каналов
|
||||||
|
- **convert-png-to-hsl/hsv/hsi/cmyk/ycbcr/lab-colors** — известные формулы
|
||||||
|
конвертации (Lab — чуть больше математики, но детерминировано)
|
||||||
|
- **generate-\*-color-palette
|
||||||
|
(complementary/monochromatic/analogous/triadic/tetradic),
|
||||||
|
generate-similar-color-shades, color-wheel-generator, mix-multiple-colors,
|
||||||
|
calculate-average-color, simulate-paint-colors, blend-two-colors,
|
||||||
|
step-between-two-colors, convert-color-names-to-png** — генераторы
|
||||||
|
палитр/смешение без работы с изображением
|
||||||
|
- **posterize-png, create-color-spectrum-png, create-multi-color-gradient-png,
|
||||||
|
generate-png-gradient-palette** — простые градиенты/квантование уровней
|
||||||
|
|
||||||
### 2.4 Геометрия, композиция, холст
|
### 2.4 Геометрия, композиция, холст
|
||||||
|
|
||||||
- **resize-png, zoom-png, crop-png, cut-png, rotate-png, skew-png, shift-png, change-png-canvas-size, change-png-aspect-ratio, fit-png-in-rectangle, convert-landscape/portrait-png-to-portrait/landscape-png** — операции через `CanvasRenderingContext2D` (`drawImage`, `rotate`, `setTransform`)
|
- **resize-png, zoom-png, crop-png, cut-png, rotate-png, skew-png, shift-png,
|
||||||
- **flip-png-horizontally/vertically, mirror-png, reverse-png** — отражение/поворот на 180°
|
change-png-canvas-size, change-png-aspect-ratio, fit-png-in-rectangle,
|
||||||
- **center-png, center-logo, center-icon** — центровка по bounding box альфа-канала
|
convert-landscape/portrait-png-to-portrait/landscape-png** — операции через
|
||||||
- **duplicate-png, clone-png, multiply-png, create-png-tile, create-symmetric-png, split-png-into-parts** — тайлинг/копирование
|
`CanvasRenderingContext2D` (`drawImage`, `rotate`, `setTransform`)
|
||||||
- **create-round-png, create-square-png, create-star-shaped-png, create-wavy-shaped-png, round-png-corners** — маски-«штампы» на альфе
|
- **flip-png-horizontally/vertically, mirror-png, reverse-png** —
|
||||||
- **add-png-border, add-padding-to-png, add-space-around-png, remove-padding-from-png, remove-space-around-png, remove-png-padding** — расширение/усадка холста
|
отражение/поворот на 180°
|
||||||
- **add-png-background, add-signature/logo/icon/stamp-background** — подложка под прозрачность
|
- **center-png, center-logo, center-icon** — центровка по bounding box
|
||||||
|
альфа-канала
|
||||||
|
- **duplicate-png, clone-png, multiply-png, create-png-tile,
|
||||||
|
create-symmetric-png, split-png-into-parts** — тайлинг/копирование
|
||||||
|
- **create-round-png, create-square-png, create-star-shaped-png,
|
||||||
|
create-wavy-shaped-png, round-png-corners** — маски-«штампы» на альфе
|
||||||
|
- **add-png-border, add-padding-to-png, add-space-around-png,
|
||||||
|
remove-padding-from-png, remove-space-around-png, remove-png-padding** —
|
||||||
|
расширение/усадка холста
|
||||||
|
- **add-png-background, add-signature/logo/icon/stamp-background** — подложка
|
||||||
|
под прозрачность
|
||||||
|
|
||||||
### 2.5 Текст, рамки, простые эффекты
|
### 2.5 Текст, рамки, простые эффекты
|
||||||
|
|
||||||
- **add-text-to-png, convert-text-to-png, create-emoji-png, add-png-watermark, wrap-png-in-polaroid, create-logo-from-text, create-icon-from-text/emoji, create-custom-stamp, create-\*-stamp (round/rect/green/red/number/date-time/monogram/from-text/emoji), create-placeholder-png, create-empty-png, create-custom-png, generate-colorful-png, generate-random-png, generate-white-noise-png, generate-1x1-png, draw-png-grid** — рисование примитивов/случайных данных
|
- **add-text-to-png, convert-text-to-png, create-emoji-png, add-png-watermark,
|
||||||
- **add-noise-to-png, create-glitch-png, pixelate-png, censor-png, erase-part-of-png, randomize-png-pixels** — шум/порча байтов/блоки/кисть
|
wrap-png-in-polaroid, create-logo-from-text, create-icon-from-text/emoji,
|
||||||
|
create-custom-stamp, create-\*-stamp
|
||||||
|
(round/rect/green/red/number/date-time/monogram/from-text/emoji),
|
||||||
|
create-placeholder-png, create-empty-png, create-custom-png,
|
||||||
|
generate-colorful-png, generate-random-png, generate-white-noise-png,
|
||||||
|
generate-1x1-png, draw-png-grid** — рисование примитивов/случайных данных
|
||||||
|
- **add-noise-to-png, create-glitch-png, pixelate-png, censor-png,
|
||||||
|
erase-part-of-png, randomize-png-pixels** — шум/порча байтов/блоки/кисть
|
||||||
- **add-shadow-to-png** — смещение + лёгкое размытие + композиция
|
- **add-shadow-to-png** — смещение + лёгкое размытие + композиция
|
||||||
- **add-artifacts-to-png** — простой вариант: JPEG-раундтрип через canvas
|
- **add-artifacts-to-png** — простой вариант: JPEG-раундтрип через canvas
|
||||||
|
|
||||||
### 2.6 Анализ и проверки
|
### 2.6 Анализ и проверки
|
||||||
|
|
||||||
- **analyze-png, find-png-file-size, find-png-dimensions, check-if-png-is-landscape-or-portrait, find-png-color-count, check-if-png-is-grayscale, check-if-png-is-transparent, verify-if-image-is-png, pick-png-color, pick-logo/icon-color, png-viewer, test-png, preview-png-on-colorful-background, test-png-on-multiple-backgrounds** — чтение заголовков/ImageData/гистограмм, «пипетка» по клику
|
- **analyze-png, find-png-file-size, find-png-dimensions,
|
||||||
|
check-if-png-is-landscape-or-portrait, find-png-color-count,
|
||||||
|
check-if-png-is-grayscale, check-if-png-is-transparent,
|
||||||
|
verify-if-image-is-png, pick-png-color, pick-logo/icon-color, png-viewer,
|
||||||
|
test-png, preview-png-on-colorful-background,
|
||||||
|
test-png-on-multiple-backgrounds** — чтение заголовков/ImageData/гистограмм,
|
||||||
|
«пипетка» по клику
|
||||||
- **sort-colors** — сортировка списка палитры (не изображения)
|
- **sort-colors** — сортировка списка палитры (не изображения)
|
||||||
|
|
||||||
### 2.7 Семейства signature/logo/icon/stamp — простые вариации
|
### 2.7 Семейства signature/logo/icon/stamp — простые вариации
|
||||||
|
|
||||||
Почти все «-maker/-color/-resize/-crop/-rotate/-bg/-space/-padding/-grayscale/-single-color/-white/-black/-semi-transparent/-shadow» для signature/logo/icon/stamp — это переиспользование операций из разделов выше с одним цветом/боксом. Отдельно перечислены только те, что требуют алгоритмов (см. MEDIUM/HARD).
|
Почти все
|
||||||
|
«-maker/-color/-resize/-crop/-rotate/-bg/-space/-padding/-grayscale/-single-color/-white/-black/-semi-transparent/-shadow»
|
||||||
|
для signature/logo/icon/stamp — это переиспользование операций из разделов выше
|
||||||
|
с одним цветом/боксом. Отдельно перечислены только те, что требуют алгоритмов
|
||||||
|
(см. MEDIUM/HARD).
|
||||||
|
|
||||||
> Примерный объём: **≈180–190 утилит уровня EASY.**
|
> Примерный объём: **≈180–190 утилит уровня EASY.**
|
||||||
|
|
||||||
@@ -85,54 +147,96 @@
|
|||||||
|
|
||||||
## 3. Уровень MEDIUM — нужен настоящий алгоритм (свёртки, морфология, квантование, анимация)
|
## 3. Уровень MEDIUM — нужен настоящий алгоритм (свёртки, морфология, квантование, анимация)
|
||||||
|
|
||||||
Реализация: честные алгоритмы на `ImageData`, часто в Web Worker, при необходимости — обвязка вокруг готовых JS-библиотек.
|
Реализация: честные алгоритмы на `ImageData`, часто в Web Worker, при
|
||||||
|
необходимости — обвязка вокруг готовых JS-библиотек.
|
||||||
|
|
||||||
### 3.1 Фильтры и свёртки
|
### 3.1 Фильтры и свёртки
|
||||||
|
|
||||||
- **blur-png, sharpen-png** — box/gaussian blur, ядро свёртки (упрощённо можно `ctx.filter`, но качественно — своя свёртка)
|
- **blur-png, sharpen-png** — box/gaussian blur, ядро свёртки (упрощённо можно
|
||||||
- **refine-png-edges, feather-png-edges, clean-png-edges** — работа с полупрозрачными краями: размытие альфы, де-контуринг, suppression полупрозрачности
|
`ctx.filter`, но качественно — своя свёртка)
|
||||||
|
- **refine-png-edges, feather-png-edges, clean-png-edges** — работа с
|
||||||
|
полупрозрачными краями: размытие альфы, де-контуринг, suppression
|
||||||
|
полупрозрачности
|
||||||
- **add-glow-effect-to-png** — расширение альфы + размытие + цветное наложение
|
- **add-glow-effect-to-png** — расширение альфы + размытие + цветное наложение
|
||||||
|
|
||||||
### 3.2 Морфология и контуры (операции над альфа-маской)
|
### 3.2 Морфология и контуры (операции над альфа-маской)
|
||||||
|
|
||||||
- **add-stroke-to-png, add-outline-to-png, remove-stroke-from-png, remove-outline-from-png, find-png-outline** — dilation/erosion по альфе, извлечение контура
|
- **add-stroke-to-png, add-outline-to-png, remove-stroke-from-png,
|
||||||
- **make-signature-thicker / make-signature-thinner, make-icon-thicker / make-icon-thinner** — dilation/erosion
|
remove-outline-from-png, find-png-outline** — dilation/erosion по альфе,
|
||||||
- **convert-outlined-icon-to-filled-icon** — flood-fill замкнутых областей; **convert-filled-icon-to-outlined-icon** — эрозия
|
извлечение контура
|
||||||
- **remove-png-border, remove-border-around-icon, remove-stamp-border** — детект однотонной/декоративной рамки и вырезание
|
- **make-signature-thicker / make-signature-thinner, make-icon-thicker /
|
||||||
|
make-icon-thinner** — dilation/erosion
|
||||||
|
- **convert-outlined-icon-to-filled-icon** — flood-fill замкнутых областей;
|
||||||
|
**convert-filled-icon-to-outlined-icon** — эрозия
|
||||||
|
- **remove-png-border, remove-border-around-icon, remove-stamp-border** — детект
|
||||||
|
однотонной/декоративной рамки и вырезание
|
||||||
|
|
||||||
### 3.3 Квантование и цветовая кластеризация
|
### 3.3 Квантование и цветовая кластеризация
|
||||||
|
|
||||||
- **quantize-png, decrease-png-color-count, set-custom-png-color-palette, add-dithering-to-png** — median cut / octree / Wu + Floyd–Steinberg; готовые JS-библиотеки (image-q и т.п.)
|
- **quantize-png, decrease-png-color-count, set-custom-png-color-palette,
|
||||||
- **change-png-quality, create-low-quality-png** — симуляция потери качества через квантование + JPEG-раундтрип
|
add-dithering-to-png** — median cut / octree / Wu + Floyd–Steinberg; готовые
|
||||||
- **convert-png-to-color-blocks** — мультипликативный блок-эффект/квантование по блокам
|
JS-библиотеки (image-q и т.п.)
|
||||||
- **extract-png-color-palette, find-logo-color-scheme, extract-icon-colors** — частотная гистограмма + выбор топ-N / кластеризация
|
- **change-png-quality, create-low-quality-png** — симуляция потери качества
|
||||||
- **create-unique/popular/multi-color/outlier-color-mask** — статистика распределения цветов и выделение по маске
|
через квантование + JPEG-раундтрип
|
||||||
|
- **convert-png-to-color-blocks** — мультипликативный блок-эффект/квантование по
|
||||||
|
блокам
|
||||||
|
- **extract-png-color-palette, find-logo-color-scheme, extract-icon-colors** —
|
||||||
|
частотная гистограмма + выбор топ-N / кластеризация
|
||||||
|
- **create-unique/popular/multi-color/outlier-color-mask** — статистика
|
||||||
|
распределения цветов и выделение по маске
|
||||||
|
|
||||||
### 3.4 Выделение объектов по цвету / фону
|
### 3.4 Выделение объектов по цвету / фону
|
||||||
|
|
||||||
- **remove-png-chroma-key** — порог по цвету + деспилл (убрать зелёную окантовку)
|
- **remove-png-chroma-key** — порог по цвету + деспилл (убрать зелёную
|
||||||
- **remove-color-from-png (качественная версия), extract-color-from-png, separate-png-colors, separate-logo-colors, separate-icon-colors** — сегментация по близости цвета
|
окантовку)
|
||||||
- **convert-png-to-silhouette, convert-png-to-stencil** — порог + заливка силуэта
|
- **remove-color-from-png (качественная версия), extract-color-from-png,
|
||||||
- **extract-stamp-from-image (по цвету печати)** — хромакей/частотный фильтр по цвету чернил
|
separate-png-colors, separate-logo-colors, separate-icon-colors** —
|
||||||
- **remove-png-background (вариант без ИИ)** — проверенный цветовой подход, знакомый по популярным онлайн-наборам PNG-утилит. Работает хорошо на однотонном/градиентном фоне и в ряде случаев удобнее ИИ. Опции и алгоритм:
|
сегментация по близости цвета
|
||||||
- **Background Color** — выбор удаляемого цвета: пипеткой по изображению или hex/RGB
|
- **convert-png-to-silhouette, convert-png-to-stencil** — порог + заливка
|
||||||
- **Percentage (похожесть цвета)** — slider; 0% = убрать только точный цвет, >0% = убрать соседние оттенки в окрестности цвета (в цветовом пространстве)
|
силуэта
|
||||||
- **Delete Outer Areas (галочка)** — режим «только внешние пиксели»: удаляются только области, связанные с краями изображения (flood-fill/region-growing от границ). Выключена = удалять по всему холсту. Именно эта опция позволяет убрать фон, не задевая «пятна» того же цвета внутри объекта
|
- **extract-stamp-from-image (по цвету печати)** — хромакей/частотный фильтр по
|
||||||
- **Smooth Edge Line + Radius** — постобработка: полупрозрачная полоса толщиной N px по границе удаления (сгладить переход)
|
цвету чернил
|
||||||
- **Preview Deleted Background** — двухцветный предпросмотр маски (что удаляется/что остаётся)
|
- **remove-png-background (вариант без ИИ)** — проверенный цветовой подход,
|
||||||
- Реализация: выбор цвета → порог по цветовому расстоянию → (опц.) flood-fill от краёв → сглаживание краёв → маска поверх превью. Всё на `ImageData`, без ML.
|
знакомый по популярным онлайн-наборам PNG-утилит. Работает хорошо на
|
||||||
|
однотонном/градиентном фоне и в ряде случаев удобнее ИИ. Опции и алгоритм:
|
||||||
|
- **Background Color** — выбор удаляемого цвета: пипеткой по изображению или
|
||||||
|
hex/RGB
|
||||||
|
- **Percentage (похожесть цвета)** — slider; 0% = убрать только точный
|
||||||
|
цвет, >0% = убрать соседние оттенки в окрестности цвета (в цветовом
|
||||||
|
пространстве)
|
||||||
|
- **Delete Outer Areas (галочка)** — режим «только внешние пиксели»: удаляются
|
||||||
|
только области, связанные с краями изображения (flood-fill/region-growing от
|
||||||
|
границ). Выключена = удалять по всему холсту. Именно эта опция позволяет
|
||||||
|
убрать фон, не задевая «пятна» того же цвета внутри объекта
|
||||||
|
- **Smooth Edge Line + Radius** — постобработка: полупрозрачная полоса
|
||||||
|
толщиной N px по границе удаления (сгладить переход)
|
||||||
|
- **Preview Deleted Background** — двухцветный предпросмотр маски (что
|
||||||
|
удаляется/что остаётся)
|
||||||
|
- Реализация: выбор цвета → порог по цветовому расстоянию → (опц.) flood-fill
|
||||||
|
от краёв → сглаживание краёв → маска поверх превью. Всё на `ImageData`, без
|
||||||
|
ML.
|
||||||
|
|
||||||
### 3.5 Геометрические искажения и пиксель-сортировка
|
### 3.5 Геометрические искажения и пиксель-сортировка
|
||||||
|
|
||||||
- **add-whirl-to-png** — свирл/вихрь: обратное отображение координат
|
- **add-whirl-to-png** — свирл/вихрь: обратное отображение координат
|
||||||
- **sort-png-pixels** — pixel sorting (сортировка строк/столбцов по яркости/цвету)
|
- **sort-png-pixels** — pixel sorting (сортировка строк/столбцов по
|
||||||
- **upscale-png / downscale-png** — качественный ресемплинг (Lanczos, mipmap) поверх базового canvas-resize
|
яркости/цвету)
|
||||||
- **remove-signature-background, transparent-png-signature-maker, clean-up-signature-edges, improve-low-quality-signature, clean-logo-edges, clean-icon-edges, fix-low-quality-stamp, convert-rubber-stamp-to-digital-stamp** — комбинация «цвет→прозрачность + де-шум + морфология краёв»
|
- **upscale-png / downscale-png** — качественный ресемплинг (Lanczos, mipmap)
|
||||||
- **convert-image/png/jpg/icon/logo/signature-to-stamp** — стилизация в двухцветную «печать» (порог + зернистость + края)
|
поверх базового canvas-resize
|
||||||
|
- **remove-signature-background, transparent-png-signature-maker,
|
||||||
|
clean-up-signature-edges, improve-low-quality-signature, clean-logo-edges,
|
||||||
|
clean-icon-edges, fix-low-quality-stamp,
|
||||||
|
convert-rubber-stamp-to-digital-stamp** — комбинация «цвет→прозрачность +
|
||||||
|
де-шум + морфология краёв»
|
||||||
|
- **convert-image/png/jpg/icon/logo/signature-to-stamp** — стилизация в
|
||||||
|
двухцветную «печать» (порог + зернистость + края)
|
||||||
|
|
||||||
### 3.6 Анимации и GIF-энкодинг
|
### 3.6 Анимации и GIF-энкодинг
|
||||||
|
|
||||||
- **convert-png-to-gif, slowly-reveal-png, create-disappearing-png, fade-in-png, fade-out-png, create-scrolling-png, create-png-sequence-from-gif** — декомпозиция кадров GIF / генерация последовательности кадров + `gifenc`/`UPNG` (APNG)
|
- **convert-png-to-gif, slowly-reveal-png, create-disappearing-png, fade-in-png,
|
||||||
|
fade-out-png, create-scrolling-png, create-png-sequence-from-gif** —
|
||||||
|
декомпозиция кадров GIF / генерация последовательности кадров +
|
||||||
|
`gifenc`/`UPNG` (APNG)
|
||||||
|
|
||||||
> Примерный объём: **≈71 утилита уровня MEDIUM.**
|
> Примерный объём: **≈71 утилита уровня MEDIUM.**
|
||||||
|
|
||||||
@@ -140,35 +244,65 @@
|
|||||||
|
|
||||||
## 4. Уровень HARD — WASM, ML, длительная обработка, тонкая подгонка алгоритма
|
## 4. Уровень HARD — WASM, ML, длительная обработка, тонкая подгонка алгоритма
|
||||||
|
|
||||||
Реализация: WASM-модули, ML-модели в браузере, длительная обработка с прогресс-барами.
|
Реализация: WASM-модули, ML-модели в браузере, длительная обработка с
|
||||||
|
прогресс-барами.
|
||||||
|
|
||||||
- **compress-png / reduce-png-file-size / optimize-png** — сжатие на уровне формата: нужен качественный PNG-энкодер с контролем zlib-фильтров/битовой глубины и оптимизаторы. Решение: **pngquant / oxipng / zopfli (WASM)** + подгонка под «качество→размер» (это главный кандидат на HARD — именно здесь тюнинг бесконечен)
|
- **compress-png / reduce-png-file-size / optimize-png** — сжатие на уровне
|
||||||
- **remove-png-background (вариант с ИИ)** — опциональное улучшение к MEDIUM-версии: ML-сегментация (Rembg/DeepLab в WASM или ONNX) для произвольных фото, где цветовой матчинг бессилен. Начинать можно без него — не-ИИ версия уже даёт рабочий инструмент
|
формата: нужен качественный PNG-энкодер с контролем zlib-фильтров/битовой
|
||||||
- **extract-signature-from-image** — детект подписи на скане/фото с произвольным фоном: де-шум, контраст, ML-детекция
|
глубины и оптимизаторы. Решение: **pngquant / oxipng / zopfli (WASM)** +
|
||||||
- **convert-handwritten-signature-to-digital-signature** — очистка и «оцифровка» почерка (ML, денойзинг, выравнивание)
|
подгонка под «качество→размер» (это главный кандидат на HARD — именно здесь
|
||||||
- **extract-barcode-from-image** — декодирование штрих-кодов/QR: готовая библиотека **zxing-wasm**, сопряжение с камерой/фото
|
тюнинг бесконечен)
|
||||||
|
- **remove-png-background (вариант с ИИ)** — опциональное улучшение к
|
||||||
|
MEDIUM-версии: ML-сегментация (Rembg/DeepLab в WASM или ONNX) для произвольных
|
||||||
|
фото, где цветовой матчинг бессилен. Начинать можно без него — не-ИИ версия
|
||||||
|
уже даёт рабочий инструмент
|
||||||
|
- **extract-signature-from-image** — детект подписи на скане/фото с произвольным
|
||||||
|
фоном: де-шум, контраст, ML-детекция
|
||||||
|
- **convert-handwritten-signature-to-digital-signature** — очистка и «оцифровка»
|
||||||
|
почерка (ML, денойзинг, выравнивание)
|
||||||
|
- **extract-barcode-from-image** — декодирование штрих-кодов/QR: готовая
|
||||||
|
библиотека **zxing-wasm**, сопряжение с камерой/фото
|
||||||
|
|
||||||
> Итого **6 утилит уровня HARD** (все — опциональные улучшения/надстройки). Всё остальное (≈290) — EASY/MEDIUM.
|
> Итого **6 утилит уровня HARD** (все — опциональные улучшения/надстройки). Всё
|
||||||
|
> остальное (≈290) — EASY/MEDIUM.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 5. Итоговая сводка
|
## 5. Итоговая сводка
|
||||||
|
|
||||||
**EASY — ~185 утилит.** canvas + ImageData.
|
**EASY — ~185 утилит.** canvas + ImageData. Примеры: crop, resize, rotate,
|
||||||
Примеры: crop, resize, rotate, конвертации, альфа-канал, палитры, текст, рамки, анализ.
|
конвертации, альфа-канал, палитры, текст, рамки, анализ.
|
||||||
|
|
||||||
**MEDIUM — ~71 утилита.** Алгоритмы, Web Workers, gifenc.
|
**MEDIUM — ~71 утилита.** Алгоритмы, Web Workers, gifenc. Примеры: blur,
|
||||||
Примеры: blur, sharpen, морфология, квантование, дизеринг, pixel sorting, анимации GIF, remove-background (без ИИ).
|
sharpen, морфология, квантование, дизеринг, pixel sorting, анимации GIF,
|
||||||
|
remove-background (без ИИ).
|
||||||
|
|
||||||
**HARD — 6 утилит.** WASM (pngquant/oxipng/zxing), ML (ONNX/Rembg).
|
**HARD — 6 утилит.** WASM (pngquant/oxipng/zxing), ML (ONNX/Rembg). Примеры:
|
||||||
Примеры: compress/optimize, remove-background (ИИ), extract-signature, barcode.
|
compress/optimize, remove-background (ИИ), extract-signature, barcode.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 6. Рекомендации по реализации
|
## 6. Рекомендации по реализации
|
||||||
|
|
||||||
1. **Единое ядро обработки** — `src/lib/core/`: загрузка файла → `ImageData`, библиотека операций (`color.ts`, `alpha.ts`, `geometry.ts`, `filters.ts`, `morphology.ts`, `quantize.ts`, `channels.ts`), экспорт через `toBlob`. Каждая утилита — тонкая SvelteKit-страница + общие компоненты (DropZone, Preview, Download).
|
1. **Единое ядро обработки** — `src/lib/core/`: загрузка файла → `ImageData`,
|
||||||
2. **Порядок внедрения:** MVP = ~40 топовых EASY (crop, resize, rotate, convert, alpha, grayscale, brightness, палитры, watermark, add-text, add-background, pick-color, analyze). Затем остальные EASY → MEDIUM пакетами по темам (фильтры, морфология, квантование, анимации). `remove-png-background` (без ИИ) — отличная «витринная» MEDIUM-фича ранних этапов: не требует ML и переиспользует `remove-color` + flood-fill + сглаживание краёв. HARD — в последнюю очередь.
|
библиотека операций (`color.ts`, `alpha.ts`, `geometry.ts`, `filters.ts`,
|
||||||
3. **Производительность:** вся пиксельная работа и тяжёлые алгоритмы — в Web Worker; большие изображения декодировать сразу в downscale для превью; прогресс-бары для MEDIUM/HARD.
|
`morphology.ts`, `quantize.ts`, `channels.ts`), экспорт через `toBlob`.
|
||||||
4. **SEO/структура:** SvelteKit + static adapter — каждая утилита отдельный маршрут (`/crop-png`, `/rotate-png`), что даёт удобные адреса и повторяет распространённую структуру наборов PNG-инструментов.
|
Каждая утилита — тонкая SvelteKit-страница + общие компоненты (DropZone,
|
||||||
5. **Семейства logo/icon/stamp/signature** — переиспользуют общие операции; в коде это конфиг-опции той же страницы (например `/resize-png` и `/resize-icon` = один компонент с разными подписями/дефолтами). Не плодить дублирующий код.
|
Preview, Download).
|
||||||
|
2. **Порядок внедрения:** MVP = ~40 топовых EASY (crop, resize, rotate, convert,
|
||||||
|
alpha, grayscale, brightness, палитры, watermark, add-text, add-background,
|
||||||
|
pick-color, analyze). Затем остальные EASY → MEDIUM пакетами по темам
|
||||||
|
(фильтры, морфология, квантование, анимации). `remove-png-background` (без
|
||||||
|
ИИ) — отличная «витринная» MEDIUM-фича ранних этапов: не требует ML и
|
||||||
|
переиспользует `remove-color` + flood-fill + сглаживание краёв. HARD — в
|
||||||
|
последнюю очередь.
|
||||||
|
3. **Производительность:** вся пиксельная работа и тяжёлые алгоритмы — в Web
|
||||||
|
Worker; большие изображения декодировать сразу в downscale для превью;
|
||||||
|
прогресс-бары для MEDIUM/HARD.
|
||||||
|
4. **SEO/структура:** SvelteKit + static adapter — каждая утилита отдельный
|
||||||
|
маршрут (`/crop-png`, `/rotate-png`), что даёт удобные адреса и повторяет
|
||||||
|
распространённую структуру наборов PNG-инструментов.
|
||||||
|
5. **Семейства logo/icon/stamp/signature** — переиспользуют общие операции; в
|
||||||
|
коде это конфиг-опции той же страницы (например `/resize-png` и
|
||||||
|
`/resize-icon` = один компонент с разными подписями/дефолтами). Не плодить
|
||||||
|
дублирующий код.
|
||||||
|
|||||||
@@ -1,103 +1,118 @@
|
|||||||
# Demo fidelity: /preview/demo vs refs-html/demo.html
|
# Demo fidelity: /preview/demo vs refs-html/demo.html
|
||||||
|
|
||||||
> ⚠️ **УТРАТИЛ АКТУАЛЬНОСТЬ — архивная копия.** Актуальный план фикса верности и
|
> ⚠️ **УТРАТИЛ АКТУАЛЬНОСТЬ — архивная копия.** Актуальный план фикса верности и
|
||||||
> текущий статус пунктов B/F — в `docs/plan-design-fix.md` (Этап 1). Аудит верности
|
> текущий статус пунктов B/F — в `docs/plan-design-fix.md` (Этап 1). Аудит
|
||||||
> теперь автоматизирован (`pnpm audit` → `web/audit/audit-report.md`; пиксель-дифф не
|
> верности теперь автоматизирован (`pnpm audit` → `web/audit/audit-report.md`;
|
||||||
> используется); темы в `refs-html` переключаются (скрипт инжектится в
|
> пиксель-дифф не используется); темы в `refs-html` переключаются (скрипт
|
||||||
> `scripts/extract-static.mjs`). Ниже — исходный текст на момент написания.
|
> инжектится в `scripts/extract-static.mjs`). Ниже — исходный текст на момент
|
||||||
|
> написания.
|
||||||
|
|
||||||
Метод: Playwright, viewport 1440×900, скриншоты обеих страниц + замер вычисленных
|
Метод: Playwright, viewport 1440×900, скриншоты обеих страниц + замер
|
||||||
стилей ключевых элементов + пиксель-дифф (без чтения картинки моделью).
|
вычисленных стилей ключевых элементов + пиксель-дифф (без чтения картинки
|
||||||
Пиксель-дифф: **24.9%** (наш скролл 1731px, реф 1579px). Зоны различий размазаны
|
моделью). Пиксель-дифф: **24.9%** (наш скролл 1731px, реф 1579px). Зоны различий
|
||||||
по всей высоте → править надо глобально, не точечно.
|
размазаны по всей высоте → править надо глобально, не точечно.
|
||||||
|
|
||||||
Теперь процесс автоматизирован: `pnpm audit` (Playwright) пишет
|
Теперь процесс автоматизирован: `pnpm audit` (Playwright) пишет
|
||||||
`web/audit/audit-report.md` — light/dark токены + поэлементный дифф стилей по тексту.
|
`web/audit/audit-report.md` — light/dark токены + поэлементный дифф стилей по
|
||||||
Пиксель-дифф убран (неудобен для работы). Фиксы можно делать в любой момент и
|
тексту. Пиксель-дифф убран (неудобен для работы). Фиксы можно делать в любой
|
||||||
перепроверять аудитом.
|
момент и перепроверять аудитом.
|
||||||
|
|
||||||
## Баги (объективно, правим в любом случае)
|
## Баги (объективно, правим в любом случае)
|
||||||
|
|
||||||
- **B1. Кнопки/инпуты рендерятся шрифтом Arial, а не IBM Plex Sans.**
|
- **B1. Кнопки/инпуты рендерятся шрифтом Arial, а не IBM Plex Sans.**
|
||||||
Доказательство: пробы `Reset pipeline` → `fontFamily: Arial`. Причина: у `button/input`
|
Доказательство: пробы `Reset pipeline` → `fontFamily: Arial`. Причина: у
|
||||||
не задан `font-family: inherit`. Фикс: добавить в reset `design2.css`
|
`button/input` не задан `font-family: inherit`. Фикс: добавить в reset
|
||||||
`button, input, select, textarea { font-family: inherit; }`. Влияет на все страницы. (P1)
|
`design2.css` `button, input, select, textarea { font-family: inherit; }`.
|
||||||
- **B2. Двойной топбар.** Страница демо рендерит свой `.topbar` (brand / AUTO PIPELINE /
|
Влияет на все страницы. (P1)
|
||||||
help / theme / lang), который перекрывается глобальным `fixed` Header из
|
- **B2. Двойной топбар.** Страница демо рендерит свой `.topbar` (brand / AUTO
|
||||||
`preview/+layout.svelte` (в нём только theme-toggle). В рефе — один топбар.
|
PIPELINE / help / theme / lang), который перекрывается глобальным `fixed`
|
||||||
Контент демо-топбара (brand, статус, язык) визуально спрятан за фикс-хедером.
|
Header из `preview/+layout.svelte` (в нём только theme-toggle). В рефе — один
|
||||||
Фикс: убрать кастомный `.topbar` из демо и сделать хедер лейаута реф-стайл топбаром
|
топбар. Контент демо-топбара (brand, статус, язык) визуально спрятан за
|
||||||
(brand + version + status + help + theme + lang) — улучшит и C9/C10. (P1)
|
фикс-хедером. Фикс: убрать кастомный `.topbar` из демо и сделать хедер лейаута
|
||||||
|
реф-стайл топбаром (brand + version + status + help + theme + lang) — улучшит
|
||||||
|
и C9/C10. (P1)
|
||||||
|
|
||||||
## Расхождения дизайна (нужно решение)
|
## Расхождения дизайна (нужно решение)
|
||||||
|
|
||||||
- **F1. Фон холста.** Реф: плоский серый `#EEF1F4`. Наш: белый + blueprint-сетка.
|
- **F1. Фон холста.** Реф: плоский серый `#EEF1F4`. Наш: белый +
|
||||||
Решение: совпасть с рефом (убрать сетку на демо) или оставить сетку как сигнатуру нового дизайна.
|
blueprint-сетка. Решение: совпасть с рефом (убрать сетку на демо) или оставить
|
||||||
- **F2. Бейдж типа шага** (BACKGROUND/TRANSFORM/STYLE). Реф: приглушённый серый `#6D787F`.
|
сетку как сигнатуру нового дизайна.
|
||||||
Решено C16: `StepCard` рендерит `type` как приглушённый моно-текст (не синий бейдж);
|
- **F2. Бейдж типа шага** (BACKGROUND/TRANSFORM/STYLE). Реф: приглушённый серый
|
||||||
индекс шага (`step-index`) — синий `#1769D2`, как в рефе.
|
`#6D787F`. Решено C16: `StepCard` рендерит `type` как приглушённый моно-текст
|
||||||
- **F3. Подписи превью-тайлов** (SOURCE / STEP 01 / FINAL OUTPUT). Реф: синий `#1769D2`,
|
(не синий бейдж); индекс шага (`step-index`) — синий `#1769D2`, как в рефе.
|
||||||
weight 400. Решено C16: `PreviewTile` label — синий `#1769D2`.
|
- **F3. Подписи превью-тайлов** (SOURCE / STEP 01 / FINAL OUTPUT). Реф: синий
|
||||||
- **F4. Раскладка preview-stack.** Реф: одна колонка (5 строк, выше). Наш: 2 колонки (grid).
|
`#1769D2`, weight 400. Решено C16: `PreviewTile` label — синий `#1769D2`.
|
||||||
Решение: одна колонка (как реф) или оставить 2 колонки как выбор нового дизайна.
|
- **F4. Раскладка preview-stack.** Реф: одна колонка (5 строк, выше). Наш: 2
|
||||||
|
колонки (grid). Решение: одна колонка (как реф) или оставить 2 колонки как
|
||||||
|
выбор нового дизайна.
|
||||||
- **F5. Вес лейблов контролов.** Реф: 400. Наш (`MonoLabel`): 500. Мелко.
|
- **F5. Вес лейблов контролов.** Реф: 400. Наш (`MonoLabel`): 500. Мелко.
|
||||||
- **F6. Мета-подписи** (DIMENSIONS/FORMAT/SIZE): реф fs 9px, наш 10px. Мелко.
|
- **F6. Мета-подписи** (DIMENSIONS/FORMAT/SIZE): реф fs 9px, наш 10px. Мелко.
|
||||||
- **F7. Копирайт:** `Add tool` у нас `+ Add tool`; кнопка `Download result` — сверить стиль
|
- **F7. Копирайт:** `Add tool` у нас `+ Add tool`; кнопка `Download result` —
|
||||||
(в рефе синяя с белым текстом 478×42). Пробы `Download result`/`Add tool`/`DIRECTION`
|
сверить стиль (в рефе синяя с белым текстом 478×42). Пробы
|
||||||
вернули NOT FOUND — скорее артефакт замера (вложенный текст/чип размера), проверить визуально.
|
`Download result`/`Add tool`/`DIRECTION` вернули NOT FOUND — скорее артефакт
|
||||||
|
замера (вложенный текст/чип размера), проверить визуально.
|
||||||
- **F8. h1 line-height** чуть выше (наш 150px vs реф 115px) — мелкий зазор.
|
- **F8. h1 line-height** чуть выше (наш 150px vs реф 115px) — мелкий зазор.
|
||||||
- **F9. Вертикальный ритм:** контент на ~152px выше, зазоры между секциями отличаются
|
- **F9. Вертикальный ритм:** контент на ~152px выше, зазоры между секциями
|
||||||
(pipeline-head y 434 vs 400 и т.д.) — подровнять паддинги под реф.
|
отличаются (pipeline-head y 434 vs 400 и т.д.) — подровнять паддинги под реф.
|
||||||
- **F10. Внутренние отступы preview-панели** (Hide intermediate x 907 vs 1218) — паддинг панели.
|
- **F10. Внутренние отступы preview-панели** (Hide intermediate x 907 vs 1218) —
|
||||||
|
паддинг панели.
|
||||||
|
|
||||||
## Темы (отложено — F1-F4)
|
## Темы (отложено — F1-F4)
|
||||||
|
|
||||||
Серый фон `#EEF1F4` — это **светлая** тема. Уточнение по F1: наш токен `--background`
|
Серый фон `#EEF1F4` — это **светлая** тема. Уточнение по F1: наш токен
|
||||||
уже равен `#EEF1F4` (совпадает с рефом); видимое отличие — только blueprint-сетка
|
`--background` уже равен `#EEF1F4` (совпадает с рефом); видимое отличие — только
|
||||||
поверх фона, а не «белый vs серый». То есть F1 = «сетка против плоского», не цвет фона.
|
blueprint-сетка поверх фона, а не «белый vs серый». То есть F1 = «сетка против
|
||||||
|
плоского», не цвет фона.
|
||||||
|
|
||||||
Перед тем как сверять цвета бейджей/тайлов/раскладок (F2-F4), нужно сначала
|
Перед тем как сверять цвета бейджей/тайлов/раскладок (F2-F4), нужно сначала
|
||||||
зафиксировать палитры light/dark как единый источник токенов. Сейчас они есть в
|
зафиксировать палитры light/dark как единый источник токенов. Сейчас они есть в
|
||||||
`web/src/lib/styles/design2.css` (`:root` + `[data-theme="dark"]`), но не сверены с рефом.
|
`web/src/lib/styles/design2.css` (`:root` + `[data-theme="dark"]`), но не
|
||||||
|
сверены с рефом.
|
||||||
|
|
||||||
**Как проверить темы в refs-html:**
|
**Как проверить темы в refs-html:**
|
||||||
|
|
||||||
- Открыть `refs-html/demo.html` (и соседние `gradient.html`, `background-remover.html`)
|
- Открыть `refs-html/demo.html` (и соседние `gradient.html`,
|
||||||
через Playwright/браузер.
|
`background-remover.html`) через Playwright/браузер.
|
||||||
- Нажать theme-toggle в топбаре рефа (иконка луны/солнца) — переключает тему
|
- Нажать theme-toggle в топбаре рефа (иконка луны/солнца) — переключает тему
|
||||||
(скорее всего меняет атрибут/класс на `<html>` или `.page` и переопределяет CSS-переменные).
|
(скорее всего меняет атрибут/класс на `<html>` или `.page` и переопределяет
|
||||||
- Снять скриншоты светлой и тёмной темы, замерить computed-стиль `:root` переменных
|
CSS-переменные).
|
||||||
рефа (через `getComputedStyle(document.documentElement).getPropertyValue('--...')`)
|
- Снять скриншоты светлой и тёмной темы, замерить computed-стиль `:root`
|
||||||
и сопоставить с нашими токенами в `design2.css`.
|
переменных рефа (через
|
||||||
|
`getComputedStyle(document.documentElement).getPropertyValue('--...')`) и
|
||||||
|
сопоставить с нашими токенами в `design2.css`.
|
||||||
- Рефы, похоже, не доделаны по тёмной теме — зафиксировать, какие именно токены
|
- Рефы, похоже, не доделаны по тёмной теме — зафиксировать, какие именно токены
|
||||||
меняются и где расхождения. На основе этого выровнять наши токены, затем вернуться
|
меняются и где расхождения. На основе этого выровнять наши токены, затем
|
||||||
к F2 (бейдж типа), F3 (тайлы), F4 (раскладка preview-stack).
|
вернуться к F2 (бейдж типа), F3 (тайлы), F4 (раскладка preview-stack).
|
||||||
|
|
||||||
Решение по F1-F4 принимается только после сверки токенов светлой/тёмной темы с рефом.
|
Решение по F1-F4 принимается только после сверки токенов светлой/тёмной темы с
|
||||||
|
рефом.
|
||||||
|
|
||||||
**Состояние тем (уточнение):** наша dark-тема реализована в `design2.css`
|
**Состояние тем (уточнение):** наша dark-тема реализована в `design2.css`
|
||||||
(`[data-theme="dark"]`), но пока подключена только на preview-маршрутах (старые
|
(`[data-theme="dark"]`), но пока подключена только на preview-маршрутах (старые
|
||||||
страницы грузят `app.css`). Этого достаточно для сверки: токены, отличные от light,
|
страницы грузят `app.css`). Этого достаточно для сверки: токены, отличные от
|
||||||
соберём по всем `refs-html/*.html` (не только `demo.html`), остальные токены
|
light, соберём по всем `refs-html/*.html` (не только `demo.html`), остальные
|
||||||
не меняются. Light-токены уже совпадают с рефом (`--background`, `--panel`,
|
токены не меняются. Light-токены уже совпадают с рефом (`--background`,
|
||||||
`--foreground`, `--muted`, `--line`, `--blue` равны).
|
`--panel`, `--foreground`, `--muted`, `--line`, `--blue` равны).
|
||||||
|
|
||||||
## Состояние (C16)
|
## Состояние (C16)
|
||||||
|
|
||||||
Цветовые расхождения против `refs-html/demo.html` устранены: стек шрифтов сверен с
|
Цветовые расхождения против `refs-html/demo.html` устранены: стек шрифтов сверен
|
||||||
рефом (`"IBM Plex Sans", sans-serif` / `"IBM Plex Mono", monospace`), бренд топбара —
|
с рефом (`"IBM Plex Sans", sans-serif` / `"IBM Plex Mono", monospace`), бренд
|
||||||
`--foreground`, индекс шага — синий, тип шага и подпись плитки — как в рефе,
|
топбара — `--foreground`, индекс шага — синий, тип шага и подпись плитки — как в
|
||||||
`Segmented` — 10px/32px. Аудит: токены light 10 / dark 16, элементы 47. Остаток —
|
рефе, `Segmented` — 10px/32px. Аудит: токены light 10 / dark 16, элементы 47.
|
||||||
sub-pixel (10px→9px лейблы, letter-spacing) и структурные (`display`/`rect` от наших
|
Остаток — sub-pixel (10px→9px лейблы, letter-spacing) и структурные
|
||||||
flex/grid раскладок против статичного мока); визуально незначимы, правим точечно при
|
(`display`/`rect` от наших flex/grid раскладок против статичного мока);
|
||||||
желании. Старые тесты (583) — без регрессий.
|
визуально незначимы, правим точечно при желании. Старые тесты (583) — без
|
||||||
|
регрессий.
|
||||||
|
|
||||||
## План действий (предлагаю)
|
## План действий (предлагаю)
|
||||||
|
|
||||||
> Актуальный пошаговый план фикса вынесен в `docs/plan-design-fix.md`
|
> Актуальный пошаговый план фикса вынесен в `docs/plan-design-fix.md` (Этапы
|
||||||
> (Этапы 0–1: рефакторинг + верность). Ниже — исходные наброски.
|
> 0–1: рефакторинг + верность). Ниже — исходные наброски.
|
||||||
|
|
||||||
1. Сразу поправить B1 + B2 (чёткие баги, улучшают все страницы).
|
1. Сразу поправить B1 + B2 (чёткие баги, улучшают все страницы).
|
||||||
2. По F1–F4 — решение за тобой (совпадать с рефом дословно ИЛИ оставить как сигнатуру нового дизайна).
|
2. По F1–F4 — решение за тобой (совпадать с рефом дословно ИЛИ оставить как
|
||||||
3. F5–F10 — мелочи, подчистить в том же проходе, если решаем «совпадать с рефом».
|
сигнатуру нового дизайна).
|
||||||
|
3. F5–F10 — мелочи, подчистить в том же проходе, если решаем «совпадать с
|
||||||
|
рефом».
|
||||||
4. Переснять скриншоты и перепроверить пиксель-дифф.
|
4. Переснять скриншоты и перепроверить пиксель-дифф.
|
||||||
|
|||||||
@@ -4,20 +4,36 @@
|
|||||||
|
|
||||||
## 1. Цели и принципы
|
## 1. Цели и принципы
|
||||||
|
|
||||||
1. **Один источник правды** — все визуальные константы живут в токенах `web/src/app.css`; ни один компонент не пишет «магические» цвета/размеры напрямую.
|
1. **Один источник правды** — все визуальные константы живут в токенах
|
||||||
2. **Никакого дублирования** — повторяющийся визуальный рецепт оформляется один раз: либо токен, либо глобальный утилитарный класс, либо компонент в `ui/`.
|
`web/src/app.css`; ни один компонент не пишет «магические» цвета/размеры
|
||||||
3. **Простые компоненты важнее умных** — если проще сделать отдельные `SliderField` и `TextField`, делаем два маленьких компонента, а не один конфигурируемый монстр.
|
напрямую.
|
||||||
4. **Scoped-стили — только для раскладки** конкретного компонента (сетка, отступы между блоками). Внешний вид контролов и текстов — из дизайн-системы.
|
2. **Никакого дублирования** — повторяющийся визуальный рецепт оформляется один
|
||||||
|
раз: либо токен, либо глобальный утилитарный класс, либо компонент в `ui/`.
|
||||||
|
3. **Простые компоненты важнее умных** — если проще сделать отдельные
|
||||||
|
`SliderField` и `TextField`, делаем два маленьких компонента, а не один
|
||||||
|
конфигурируемый монстр.
|
||||||
|
4. **Scoped-стили — только для раскладки** конкретного компонента (сетка,
|
||||||
|
отступы между блоками). Внешний вид контролов и текстов — из дизайн-системы.
|
||||||
|
|
||||||
## 2. Аудит: что дублируется прямо сейчас
|
## 2. Аудит: что дублируется прямо сейчас
|
||||||
|
|
||||||
- **«Приглушённый текст»** (`--text-muted` + размер 0.85–0.95rem) — повторён в семи местах: `ToolPage` (.description, .hint), главная (.lead, .card-desc), лейаут (.nav-link, footer), `Preview` (.dims), `ParamForm` (label), `InfoPanel` (dt).
|
- **«Приглушённый текст»** (`--text-muted` + размер 0.85–0.95rem) — повторён в
|
||||||
- **«Заголовок секции капсом»** (uppercase, letter-spacing, muted) — точная копипаста в `ToolPage` и на главной.
|
семи местах: `ToolPage` (.description, .hint), главная (.lead, .card-desc),
|
||||||
- **Базовые инпуты** (padding, border, radius) — живут только внутри `ParamForm`; любые новые типы контролов (слайдеры, пипетка цвета) пришлось бы копировать вручную.
|
лейаут (.nav-link, footer), `Preview` (.dims), `ParamForm` (label),
|
||||||
- **Блок «label + отступы»** — дублировался бы в каждом `*Field`; решается обёрткой `Field.svelte`.
|
`InfoPanel` (dt).
|
||||||
- **«Панель»** (surface + border + radius) — три ручные реализации одного паттерна: `DropZone`, карточки главной, строки `InfoPanel`.
|
- **«Заголовок секции капсом»** (uppercase, letter-spacing, muted) — точная
|
||||||
- **Кнопки** — глобальные классы `.primary`/`.secondary` без компонента; состояния «занят/недоступен» каждый описывает заново.
|
копипаста в `ToolPage` и на главной.
|
||||||
- **Ошибка-баннер** — только в `ToolPage`, а понадобится минимум ещё раз в workspace.
|
- **Базовые инпуты** (padding, border, radius) — живут только внутри
|
||||||
|
`ParamForm`; любые новые типы контролов (слайдеры, пипетка цвета) пришлось бы
|
||||||
|
копировать вручную.
|
||||||
|
- **Блок «label + отступы»** — дублировался бы в каждом `*Field`; решается
|
||||||
|
обёрткой `Field.svelte`.
|
||||||
|
- **«Панель»** (surface + border + radius) — три ручные реализации одного
|
||||||
|
паттерна: `DropZone`, карточки главной, строки `InfoPanel`.
|
||||||
|
- **Кнопки** — глобальные классы `.primary`/`.secondary` без компонента;
|
||||||
|
состояния «занят/недоступен» каждый описывает заново.
|
||||||
|
- **Ошибка-баннер** — только в `ToolPage`, а понадобится минимум ещё раз в
|
||||||
|
workspace.
|
||||||
|
|
||||||
## 3. Токены (расширение `app.css`)
|
## 3. Токены (расширение `app.css`)
|
||||||
|
|
||||||
@@ -55,11 +71,13 @@
|
|||||||
- `.text-muted` — приглушённый текст (размер наследуется);
|
- `.text-muted` — приглушённый текст (размер наследуется);
|
||||||
- `.text-caption` — мелкий текст `--text-s`;
|
- `.text-caption` — мелкий текст `--text-s`;
|
||||||
- `.heading-section` — заголовок секции капсом (вместо двух копипаст);
|
- `.heading-section` — заголовок секции капсом (вместо двух копипаст);
|
||||||
- `.panel` — поверхность с бордером/радиусом (DropZone, карточки, InfoPanel-строки);
|
- `.panel` — поверхность с бордером/радиусом (DropZone, карточки,
|
||||||
|
InfoPanel-строки);
|
||||||
- `.error-banner` — баннер ошибки;
|
- `.error-banner` — баннер ошибки;
|
||||||
- `.visually-hidden` — скрытый, но читаемый скринридерами.
|
- `.visually-hidden` — скрытый, но читаемый скринридерами.
|
||||||
|
|
||||||
Элементные стили из текущего `app.css` (голые `input, select { ... }`) убрать — внешний вид контролов переезжает в компоненты `ui/`.
|
Элементные стили из текущего `app.css` (голые `input, select { ... }`) убрать —
|
||||||
|
внешний вид контролов переезжает в компоненты `ui/`.
|
||||||
|
|
||||||
## 5. Состав UI-компонентов (`web/src/lib/components/ui/`)
|
## 5. Состав UI-компонентов (`web/src/lib/components/ui/`)
|
||||||
|
|
||||||
@@ -76,15 +94,23 @@
|
|||||||
| `ColorField.svelte` | `id`, `label`, `value = $bindable()` | color-ветка `ParamForm`; точка будущего расширения пипеткой |
|
| `ColorField.svelte` | `id`, `label`, `value = $bindable()` | color-ветка `ParamForm`; точка будущего расширения пипеткой |
|
||||||
| `EmptyState.svelte` | `title`, `hint`, слот действия | «Обработка…», будущие пустые панели исходника/результата |
|
| `EmptyState.svelte` | `title`, `hint`, слот действия | «Обработка…», будущие пустые панели исходника/результата |
|
||||||
|
|
||||||
`Field` — чисто раскладочная обёртка (`<div class="field"><label for>…</label><slot /></div>`), ничего не знает о типе контрола. На ней построены `TextField`, `SliderField`, `SelectField`, `ColorField`. Исключение — `CheckboxField`: у чекбокса label оборачивает контрол (кликабельная строка), поэтому у него своя раскладка.
|
`Field` — чисто раскладочная обёртка
|
||||||
|
(`<div class="field"><label for>…</label><slot /></div>`), ничего не знает о
|
||||||
|
типе контрола. На ней построены `TextField`, `SliderField`, `SelectField`,
|
||||||
|
`ColorField`. Исключение — `CheckboxField`: у чекбокса label оборачивает контрол
|
||||||
|
(кликабельная строка), поэтому у него своя раскладка.
|
||||||
|
|
||||||
## 6. Миграция существующего
|
## 6. Миграция существующего
|
||||||
|
|
||||||
1. `ParamForm` — переписывается на `Field` + `*Field`-компоненты; собственная стилизация контролов и label удаляется.
|
1. `ParamForm` — переписывается на `Field` + `*Field`-компоненты; собственная
|
||||||
|
стилизация контролов и label удаляется.
|
||||||
2. `DownloadButton` и кнопки `ToolPage` — на `Button`.
|
2. `DownloadButton` и кнопки `ToolPage` — на `Button`.
|
||||||
3. `DropZone`, карточки главной, строки `InfoPanel` — на `.panel` + свои раскладочные стили.
|
3. `DropZone`, карточки главной, строки `InfoPanel` — на `.panel` + свои
|
||||||
4. `ToolPage`, главная, лейаут — приглушённые тексты на `.text-*`, заголовки секций на `.heading-section`, баннер на `.error-banner`.
|
раскладочные стили.
|
||||||
5. Из `app.css` удалить глобальные классы кнопок и голые стили инпутов после перехода.
|
4. `ToolPage`, главная, лейаут — приглушённые тексты на `.text-*`, заголовки
|
||||||
|
секций на `.heading-section`, баннер на `.error-banner`.
|
||||||
|
5. Из `app.css` удалить глобальные классы кнопок и голые стили инпутов после
|
||||||
|
перехода.
|
||||||
|
|
||||||
## 7. Порядок работ
|
## 7. Порядок работ
|
||||||
|
|
||||||
@@ -97,13 +123,18 @@
|
|||||||
|
|
||||||
## 8. Критерии готовности
|
## 8. Критерии готовности
|
||||||
|
|
||||||
- Ни один компонент не задаёт цвет/радиус/шрифт мимо токенов или классов системы.
|
- Ни один компонент не задаёт цвет/радиус/шрифт мимо токенов или классов
|
||||||
|
системы.
|
||||||
- Все интерактивные контролы на страницах — экземпляры `ui/*`.
|
- Все интерактивные контролы на страницах — экземпляры `ui/*`.
|
||||||
- `grep -E '#[0-9a-f]{3,6}' web/src/lib/components` не находит ничего, кроме токенов в `app.css`.
|
- `grep -E '#[0-9a-f]{3,6}' web/src/lib/components` не находит ничего, кроме
|
||||||
- Визуальной регрессии нет (ручная сверка всех страниц до/после), тесты и билд зелёные.
|
токенов в `app.css`.
|
||||||
|
- Визуальной регрессии нет (ручная сверка всех страниц до/после), тесты и билд
|
||||||
|
зелёные.
|
||||||
|
|
||||||
## 9. Что сознательно не делаем
|
## 9. Что сознательно не делаем
|
||||||
|
|
||||||
- Никаких CSS-фреймворков и препроцессоров — обычный CSS, как решено на старте.
|
- Никаких CSS-фреймворков и препроцессоров — обычный CSS, как решено на старте.
|
||||||
- Никаких тем/тёмного режима — но токены сразу семантические, чтобы тема добавилась заменой значений `:root`.
|
- Никаких тем/тёмного режима — но токены сразу семантические, чтобы тема
|
||||||
- Никаких сложных составных компонентов (модалки, таблицы) — они появятся, когда потребуется реальная задача.
|
добавилась заменой значений `:root`.
|
||||||
|
- Никаких сложных составных компонентов (модалки, таблицы) — они появятся, когда
|
||||||
|
потребуется реальная задача.
|
||||||
|
|||||||
+116
-45
@@ -4,38 +4,59 @@
|
|||||||
>
|
>
|
||||||
> **Что сделано по плану:**
|
> **Что сделано по плану:**
|
||||||
>
|
>
|
||||||
> - Шаг 0 — каркас: SvelteKit + adapter-static, design-токены на CSS, лейаут с навигацией по категориям ✔
|
> - Шаг 0 — каркас: SvelteKit + adapter-static, design-токены на CSS, лейаут с
|
||||||
> - Шаг 1 — ядро: `PixelImage`, декодирование файлов, кодирование PNG/JPEG/WebP, скачивание ✔
|
> навигацией по категориям ✔
|
||||||
> - Шаг 2 — операции ядра: color / geometry / alpha / analyze, чистые функции без DOM ✔
|
> - Шаг 1 — ядро: `PixelImage`, декодирование файлов, кодирование PNG/JPEG/WebP,
|
||||||
|
> скачивание ✔
|
||||||
|
> - Шаг 2 — операции ядра: color / geometry / alpha / analyze, чистые функции
|
||||||
|
> без DOM ✔
|
||||||
> - Шаг 3 — реестр из 11 инструментов + санитизация параметров ✔
|
> - Шаг 3 — реестр из 11 инструментов + санитизация параметров ✔
|
||||||
> - Шаг 4 — универсальные UI-компоненты (DropZone, Preview, ParamForm, DownloadButton, InfoPanel, ToolPage) ✔
|
> - Шаг 4 — универсальные UI-компоненты (DropZone, Preview, ParamForm,
|
||||||
> - Шаг 5 — маршруты: `/tools/[id]` с prerender всех страниц, главная-каталог по категориям ✔
|
> DownloadButton, InfoPanel, ToolPage) ✔
|
||||||
|
> - Шаг 5 — маршруты: `/tools/[id]` с prerender всех страниц, главная-каталог по
|
||||||
|
> категориям ✔
|
||||||
> - Шаги 6–7 — обработка ошибок, состояния, полировка; верификация ✔
|
> - Шаги 6–7 — обработка ошибок, состояния, полировка; верификация ✔
|
||||||
>
|
>
|
||||||
> **Checkpoint пройден:** все 11 инструментов вручную проверены в браузере (загрузка → применение → скачивание), включая найденный и исправленный баг resize с сохранением пропорций. Тесты 75/75, svelte-check 0 ошибок, статический экспорт собирается.
|
> **Checkpoint пройден:** все 11 инструментов вручную проверены в браузере
|
||||||
|
> (загрузка → применение → скачивание), включая найденный и исправленный баг
|
||||||
|
> resize с сохранением пропорций. Тесты 75/75, svelte-check 0 ошибок,
|
||||||
|
> статический экспорт собирается.
|
||||||
>
|
>
|
||||||
> **Попутные улучшения сверх плана:** корневой скрипт `pnpm dev`; `sanitizeParams` против пустых/невалидных значений полей; семантика «вписать в размеры» для resize.
|
> **Попутные улучшения сверх плана:** корневой скрипт `pnpm dev`;
|
||||||
|
> `sanitizeParams` против пустых/невалидных значений полей; семантика «вписать в
|
||||||
|
> размеры» для resize.
|
||||||
|
|
||||||
## 0. Зафиксированные решения
|
## 0. Зафиксированные решения
|
||||||
|
|
||||||
- **Объём MVP:** только одиночные инструменты «загрузил → настроил → скачал». Пайплайн-workspace (`/workspace`) вне скоупа этого плана.
|
- **Объём MVP:** только одиночные инструменты «загрузил → настроил → скачал».
|
||||||
- **Набор инструментов первого среза:** ~11 базовых EASY (см. §3). Достаточно, чтобы проверить насквозь ядро → реестр → универсальную страницу.
|
Пайплайн-workspace (`/workspace`) вне скоупа этого плана.
|
||||||
- **Стили:** обычный CSS без фреймворка — design-токены на CSS custom properties, ноль зависимостей.
|
- **Набор инструментов первого среза:** ~11 базовых EASY (см. §3). Достаточно,
|
||||||
- **Стек:** SvelteKit + `@sveltejs/adapter-static` + TypeScript, пакетный менеджер pnpm (уже зафиксирован в корневом `package.json`). Весь код сайта — в `web/`.
|
чтобы проверить насквозь ядро → реестр → универсальную страницу.
|
||||||
- **Ядро без DOM:** все пиксельные операции — чистые функции над `PixelImage`; DOM/canvas допускается только в модуле IO и компонентах превью.
|
- **Стили:** обычный CSS без фреймворка — design-токены на CSS custom
|
||||||
|
properties, ноль зависимостей.
|
||||||
|
- **Стек:** SvelteKit + `@sveltejs/adapter-static` + TypeScript, пакетный
|
||||||
|
менеджер pnpm (уже зафиксирован в корневом `package.json`). Весь код сайта — в
|
||||||
|
`web/`.
|
||||||
|
- **Ядро без DOM:** все пиксельные операции — чистые функции над `PixelImage`;
|
||||||
|
DOM/canvas допускается только в модуле IO и компонентах превью.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1. Шаг 0 — Каркас проекта
|
## 1. Шаг 0 — Каркас проекта
|
||||||
|
|
||||||
1. Создать SvelteKit-приложение в `web/`: `npx sv create web` (шаблон minimal, TypeScript, без лишних аддонов).
|
1. Создать SvelteKit-приложение в `web/`: `npx sv create web` (шаблон minimal,
|
||||||
|
TypeScript, без лишних аддонов).
|
||||||
2. `pnpm install` внутри `web/`.
|
2. `pnpm install` внутри `web/`.
|
||||||
3. `svelte.config.js`: заменить адаптер на `@sveltejs/adapter-static`.
|
3. `svelte.config.js`: заменить адаптер на `@sveltejs/adapter-static`.
|
||||||
4. `src/routes/+layout.ts`: `export const prerender = true;` — весь сайт prerenderится в статику.
|
4. `src/routes/+layout.ts`: `export const prerender = true;` — весь сайт
|
||||||
5. `src/app.css`: дизайн-токены (палитра, типографика, отступы, радиусы через CSS custom properties) + минимальный сброс стилей.
|
prerenderится в статику.
|
||||||
6. `src/routes/+layout.svelte`: шапка с названием сайта и навигацией по категориям инструментов (категории — константа из реестра), футер.
|
5. `src/app.css`: дизайн-токены (палитра, типографика, отступы, радиусы через
|
||||||
|
CSS custom properties) + минимальный сброс стилей.
|
||||||
|
6. `src/routes/+layout.svelte`: шапка с названием сайта и навигацией по
|
||||||
|
категориям инструментов (категории — константа из реестра), футер.
|
||||||
|
|
||||||
**Проверка:** `pnpm dev` открывает пустой каркас; `pnpm build && pnpm preview` отдаёт статический экспорт.
|
**Проверка:** `pnpm dev` открывает пустой каркас; `pnpm build && pnpm preview`
|
||||||
|
отдаёт статический экспорт.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -46,28 +67,42 @@
|
|||||||
- `types.ts`
|
- `types.ts`
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
export type PixelImage = { width: number; height: number; data: Uint8ClampedArray }; // RGBA, как ImageData
|
export type PixelImage = {
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
data: Uint8ClampedArray;
|
||||||
|
}; // RGBA, как ImageData
|
||||||
```
|
```
|
||||||
|
|
||||||
- `io.ts` — единственное место ядра, где разрешён canvas/DOM:
|
- `io.ts` — единственное место ядра, где разрешён canvas/DOM:
|
||||||
- `decodeFile(file: File): Promise<PixelImage>` — `createImageBitmap(file)` → отрисовка на canvas → `getImageData`;
|
- `decodeFile(file: File): Promise<PixelImage>` — `createImageBitmap(file)` →
|
||||||
- `encode(img: PixelImage, mime: 'image/png' | 'image/jpeg' | 'image/webp', quality?: number): Promise<Blob>` — `putImageData` → `canvas.toBlob`;
|
отрисовка на canvas → `getImageData`;
|
||||||
|
- `encode(img: PixelImage, mime: 'image/png' | 'image/jpeg' | 'image/webp', quality?: number): Promise<Blob>`
|
||||||
|
— `putImageData` → `canvas.toBlob`;
|
||||||
- `downloadBlob(blob: Blob, filename: string): void`.
|
- `downloadBlob(blob: Blob, filename: string): void`.
|
||||||
|
|
||||||
**Проверка:** юнит-смоук — декодировать фикстурный PNG, закодировать обратно, размеры совпадают.
|
**Проверка:** юнит-смоук — декодировать фикстурный PNG, закодировать обратно,
|
||||||
|
размеры совпадают.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3. Шаг 2 — Операции ядра (чистые функции)
|
## 3. Шаг 2 — Операции ядра (чистые функции)
|
||||||
|
|
||||||
Файлы в `web/src/lib/core/`, каждая функция: `(img, params) => new PixelImage`, без мутации входа:
|
Файлы в `web/src/lib/core/`, каждая функция: `(img, params) => new PixelImage`,
|
||||||
|
без мутации входа:
|
||||||
|
|
||||||
- `color.ts`: `grayscale(img)` (luma), `invert(img)`, `brightnessContrast(img, brightness −100..100, contrast −100..100)`.
|
- `color.ts`: `grayscale(img)` (luma), `invert(img)`,
|
||||||
- `geometry.ts`: `flip(img, axis)`, `rotate90(img, turns)`, `crop(img, x, y, w, h)` — индексная математика; `resize(img, w, h)` — билинейная интерполяция на TS (не `drawImage`, чтобы ядро осталось DOM-free и воспроизводимым бит-в-бит).
|
`brightnessContrast(img, brightness −100..100, contrast −100..100)`.
|
||||||
- `alpha.ts`: `removeColorToAlpha(img, hex, tolerancePercent)` — цветовое расстояние → альфа 0.
|
- `geometry.ts`: `flip(img, axis)`, `rotate90(img, turns)`,
|
||||||
|
`crop(img, x, y, w, h)` — индексная математика; `resize(img, w, h)` —
|
||||||
|
билинейная интерполяция на TS (не `drawImage`, чтобы ядро осталось DOM-free и
|
||||||
|
воспроизводимым бит-в-бит).
|
||||||
|
- `alpha.ts`: `removeColorToAlpha(img, hex, tolerancePercent)` — цветовое
|
||||||
|
расстояние → альфа 0.
|
||||||
- `analyze.ts`: `imageInfo(img)` → `{ width, height, hasAlpha, colorCount }`.
|
- `analyze.ts`: `imageInfo(img)` → `{ width, height, hasAlpha, colorCount }`.
|
||||||
|
|
||||||
**Проверка:** vitest на маленьких матрицах пикселей (2×2, 3×3): flip, rotate, crop, grayscale — ожидаемые значения руками.
|
**Проверка:** vitest на маленьких матрицах пикселей (2×2, 3×3): flip, rotate,
|
||||||
|
crop, grayscale — ожидаемые значения руками.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -77,8 +112,22 @@
|
|||||||
|
|
||||||
```ts
|
```ts
|
||||||
export type ParamDef =
|
export type ParamDef =
|
||||||
| { id: string; label: string; type: "number"; min?: number; max?: number; step?: number; default: number }
|
| {
|
||||||
| { id: string; label: string; type: "select"; options: { value: string; label: string }[]; default: string }
|
id: string;
|
||||||
|
label: string;
|
||||||
|
type: "number";
|
||||||
|
min?: number;
|
||||||
|
max?: number;
|
||||||
|
step?: number;
|
||||||
|
default: number;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
type: "select";
|
||||||
|
options: { value: string; label: string }[];
|
||||||
|
default: string;
|
||||||
|
}
|
||||||
| { id: string; label: string; type: "checkbox"; default: boolean }
|
| { id: string; label: string; type: "checkbox"; default: boolean }
|
||||||
| { id: string; label: string; type: "color"; default: string };
|
| { id: string; label: string; type: "color"; default: string };
|
||||||
|
|
||||||
@@ -88,11 +137,15 @@ export type ToolEntry = {
|
|||||||
description: string;
|
description: string;
|
||||||
category: CategoryId;
|
category: CategoryId;
|
||||||
params: ParamDef[];
|
params: ParamDef[];
|
||||||
run: (img: PixelImage, params: Record<string, unknown>) => Promise<PixelImage> | PixelImage;
|
run: (
|
||||||
|
img: PixelImage,
|
||||||
|
params: Record<string, unknown>,
|
||||||
|
) => Promise<PixelImage> | PixelImage;
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
Категории (константа там же): `convert`, `alpha`, `color`, `geometry`, `analyze`.
|
Категории (константа там же): `convert`, `alpha`, `color`, `geometry`,
|
||||||
|
`analyze`.
|
||||||
|
|
||||||
### Состав первого среза — 11 инструментов
|
### Состав первого среза — 11 инструментов
|
||||||
|
|
||||||
@@ -110,9 +163,11 @@ export type ToolEntry = {
|
|||||||
| 10 | `remove-color-from-png` | alpha | hex color, tolerance % |
|
| 10 | `remove-color-from-png` | alpha | hex color, tolerance % |
|
||||||
| 11 | `png-info` | analyze | — особый случай: не картинка, а текстовая сводка |
|
| 11 | `png-info` | analyze | — особый случай: не картинка, а текстовая сводка |
|
||||||
|
|
||||||
Для `png-info` в записи реестра добавляется флаг `resultType: 'info'`; остальные — `'image'`.
|
Для `png-info` в записи реестра добавляется флаг `resultType: 'info'`; остальные
|
||||||
|
— `'image'`.
|
||||||
|
|
||||||
**Проверка:** импорт реестра в тесте — все id уникальны, у каждого `params` имеют дефолты, `run` определён.
|
**Проверка:** импорт реестра в тесте — все id уникальны, у каждого `params`
|
||||||
|
имеют дефолты, `run` определён.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -120,33 +175,45 @@ export type ToolEntry = {
|
|||||||
|
|
||||||
`web/src/lib/components/`:
|
`web/src/lib/components/`:
|
||||||
|
|
||||||
- `DropZone.svelte` — drag&drop + клик; эмитит `File`; проверка MIME (PNG/JPEG/WebP/GIF/BMP).
|
- `DropZone.svelte` — drag&drop + клик; эмитит `File`; проверка MIME
|
||||||
- `Preview.svelte` — рендер `PixelImage` на canvas с fit-масштабированием; шахматный фон под прозрачность.
|
(PNG/JPEG/WebP/GIF/BMP).
|
||||||
- `ParamForm.svelte` — генерирует контролы из `ParamDef[]`, двусторонний `bind:values`.
|
- `Preview.svelte` — рендер `PixelImage` на canvas с fit-масштабированием;
|
||||||
- `DownloadButton.svelte` — encode выбранного формата + `downloadBlob`, имя `<tool-id>.<ext>`.
|
шахматный фон под прозрачность.
|
||||||
|
- `ParamForm.svelte` — генерирует контролы из `ParamDef[]`, двусторонний
|
||||||
|
`bind:values`.
|
||||||
|
- `DownloadButton.svelte` — encode выбранного формата + `downloadBlob`, имя
|
||||||
|
`<tool-id>.<ext>`.
|
||||||
- `InfoPanel.svelte` — текстовый вывод для `resultType: 'info'`.
|
- `InfoPanel.svelte` — текстовый вывод для `resultType: 'info'`.
|
||||||
- `ToolPage.svelte` — оркестратор страницы инструмента; состояния `idle → loaded → processing → done | error`; раскладка: слева DropZone, справа превью + форма параметров + кнопки «Применить» / «Скачать».
|
- `ToolPage.svelte` — оркестратор страницы инструмента; состояния
|
||||||
|
`idle → loaded → processing → done | error`; раскладка: слева DropZone, справа
|
||||||
|
превью + форма параметров + кнопки «Применить» / «Скачать».
|
||||||
|
|
||||||
**Правило:** новый инструмент = новая запись в реестре (+ функция операции). Компоненты и маршрут не трогаем.
|
**Правило:** новый инструмент = новая запись в реестре (+ функция операции).
|
||||||
|
Компоненты и маршрут не трогаем.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 6. Шаг 5 — Маршруты
|
## 6. Шаг 5 — Маршруты
|
||||||
|
|
||||||
- `/` (`src/routes/+page.svelte`) — сетка карточек инструментов, сгруппированных по категориям; данные из реестра.
|
- `/` (`src/routes/+page.svelte`) — сетка карточек инструментов, сгруппированных
|
||||||
|
по категориям; данные из реестра.
|
||||||
- `/tools/[id]` — универсальная страница:
|
- `/tools/[id]` — универсальная страница:
|
||||||
- `+page.ts`: `entries()` возвращает все `id` из реестра (обязательное условие prerender при adapter-static);
|
- `+page.ts`: `entries()` возвращает все `id` из реестра (обязательное условие
|
||||||
|
prerender при adapter-static);
|
||||||
- `load()` возвращает запись реестра по `id`, неизвестный id → 404;
|
- `load()` возвращает запись реестра по `id`, неизвестный id → 404;
|
||||||
- `+page.svelte` — тонкая обёртка над `ToolPage`.
|
- `+page.svelte` — тонкая обёртка над `ToolPage`.
|
||||||
|
|
||||||
**Проверка:** `pnpm build` — в `build/tools/` лежат все 11 страниц; главная содержит ссылки на каждую.
|
**Проверка:** `pnpm build` — в `build/tools/` лежат все 11 страниц; главная
|
||||||
|
содержит ссылки на каждую.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 7. Шаг 6 — Ошибки и минимальная полировка
|
## 7. Шаг 6 — Ошибки и минимальная полировка
|
||||||
|
|
||||||
- Обработка ошибок: неподдерживаемый формат, ошибка декода, некорректные параметры crop (выход за границы — клампы или понятная ошибка).
|
- Обработка ошибок: неподдерживаемый формат, ошибка декода, некорректные
|
||||||
- Состояния: «перетащите файл», спиннер во время обработки, disabled-кнопки до загрузки.
|
параметры crop (выход за границы — клампы или понятная ошибка).
|
||||||
|
- Состояния: «перетащите файл», спиннер во время обработки, disabled-кнопки до
|
||||||
|
загрузки.
|
||||||
- Тексты описаний для всех 11 инструментов (title/description уже в реестре).
|
- Тексты описаний для всех 11 инструментов (title/description уже в реестре).
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -161,6 +228,10 @@ cd web && pnpm build # статический экспорт должен
|
|||||||
cd web && pnpm test # vitest: ядро (геометрия/цвет) + целостность реестра
|
cd web && pnpm test # vitest: ядро (геометрия/цвет) + целостность реестра
|
||||||
```
|
```
|
||||||
|
|
||||||
Ручной smoke-тест каждого из 11 инструментов: загрузка PNG → применить → скачать → открыть результат.
|
Ручной smoke-тест каждого из 11 инструментов: загрузка PNG → применить → скачать
|
||||||
|
→ открыть результат.
|
||||||
|
|
||||||
**Checkpoint MVP:** любой из 11 инструментов доступен по прямому URL с главной страницы; PNG загружается drag&drop; параметры применяются; результат скачивается; всё работает офлайн со статического хостинга; ни одной серверной зависимости.
|
**Checkpoint MVP:** любой из 11 инструментов доступен по прямому URL с главной
|
||||||
|
страницы; PNG загружается drag&drop; параметры применяются; результат
|
||||||
|
скачивается; всё работает офлайн со статического хостинга; ни одной серверной
|
||||||
|
зависимости.
|
||||||
|
|||||||
+42
-43
@@ -2,15 +2,14 @@
|
|||||||
|
|
||||||
> **СТАТУС: РЕАЛИЗОВАН (2026-09-07) — устарел.** Скрипты
|
> **СТАТУС: РЕАЛИЗОВАН (2026-09-07) — устарел.** Скрипты
|
||||||
> `web/scripts/audit-dom.mjs`, `audit-css.mjs`, `audit-cdp.mjs`,
|
> `web/scripts/audit-dom.mjs`, `audit-css.mjs`, `audit-cdp.mjs`,
|
||||||
> `audit-cdp-responsive.mjs` и npm-скрипты `refs-dom-audit` /
|
> `audit-cdp-responsive.mjs` и npm-скрипты `refs-dom-audit` / `refs-css-audit` /
|
||||||
> `refs-css-audit` / `refs-cdp-audit` на месте; старый текстовый `audit.mjs`
|
> `refs-cdp-audit` на месте; старый текстовый `audit.mjs` удалён. Актуальная
|
||||||
> удалён. Актуальная доводка верности и статус пунктов F — в
|
> доводка верности и статус пунктов F — в `plan-design-fix.md`.
|
||||||
> `plan-design-fix.md`.
|
|
||||||
|
|
||||||
> Статус: к выполнению. Ветка `feat/redesign`.
|
> Статус: к выполнению. Ветка `feat/redesign`. Источник правды по пикселям:
|
||||||
> Источник правды по пикселям: `refs-html/*.html` (статический экспорт рефа).
|
> `refs-html/*.html` (статический экспорт рефа). Актуальный рабочий аудит сейчас
|
||||||
> Актуальный рабочий аудит сейчас — `pnpm refs-audit` (`scripts/audit.mjs`), но он
|
> — `pnpm refs-audit` (`scripts/audit.mjs`), но он **сломан** (см. «Почему
|
||||||
> **сломан** (см. «Почему текущий аудит не работает») и подлежит замене.
|
> текущий аудит не работает») и подлежит замене.
|
||||||
|
|
||||||
## Почему текущий аудит не работает
|
## Почему текущий аудит не работает
|
||||||
|
|
||||||
@@ -37,13 +36,13 @@
|
|||||||
Заводим их через `t()` — совпадёт с рефом (EN).
|
Заводим их через `t()` — совпадёт с рефом (EN).
|
||||||
2. **Имена классов НЕ сводим к рефу.** Матчинг делаем по **структурному пути**,
|
2. **Имена классов НЕ сводим к рефу.** Матчинг делаем по **структурному пути**,
|
||||||
а не по классу. Компоненты `kit/` остаются идиоматичными, Stage 0 не
|
а не по классу. Компоненты `kit/` остаются идиоматичными, Stage 0 не
|
||||||
откатываем. Требуется только эквивалентность *структуры* (теги, вложенность,
|
откатываем. Требуется только эквивалентность _структуры_ (теги, вложенность,
|
||||||
порядок сиблингов, наличие узлов, текст).
|
порядок сиблингов, наличие узлов, текст).
|
||||||
3. **Пиксельный дифф (Playwright + pixelmatch) пока НЕ делаем.** DOM+CSS-аудита
|
3. **Пиксельный дифф (Playwright + pixelmatch) пока НЕ делаем.** DOM+CSS-аудита
|
||||||
достаточно; добавим как сеть позже, если стили что-то упустят.
|
достаточно; добавим как сеть позже, если стили что-то упустят.
|
||||||
4. **Порядок прогона маршрутов:** `demo` → `list-tools` → затем 2 инструмента
|
4. **Порядок прогона маршрутов:** `demo` → `list-tools` → затем 2 инструмента
|
||||||
(`tools/linear-gradient-png` ↔ `gradient.html`,
|
(`tools/linear-gradient-png` ↔ `gradient.html`, `tools/remove-background-png`
|
||||||
`tools/remove-background-png` ↔ `background-remover.html`).
|
↔ `background-remover.html`).
|
||||||
|
|
||||||
## Принципы
|
## Принципы
|
||||||
|
|
||||||
@@ -106,15 +105,15 @@ Svelte-атрибутов и комментов гидрации), чтобы п
|
|||||||
- `moved` — тот же текст/тег, другой путь (перепутанный порядок/родитель);
|
- `moved` — тот же текст/тег, другой путь (перепутанный порядок/родитель);
|
||||||
- `textMismatch` — нормализованный текст различается (в т.ч. RU vs EN).
|
- `textMismatch` — нормализованный текст различается (в т.ч. RU vs EN).
|
||||||
|
|
||||||
Класс-мисматч — **только информационно**, не блокирует (классы свои по
|
Класс-мисматч — **только информационно**, не блокирует (классы свои по решению
|
||||||
решению №2). Счётчики: `added / removed / moved / textMismatch`.
|
№2). Счётчики: `added / removed / moved / textMismatch`.
|
||||||
|
|
||||||
### Рендеринг
|
### Рендеринг
|
||||||
|
|
||||||
- **наше:** dev-сервер на `5179` (как сейчас в `audit.mjs`).
|
- **наше:** dev-сервер на `5179` (как сейчас в `audit.mjs`).
|
||||||
- **реф:** `pathToFileURL` к `refs-html/<ref>.html` (`file://`). Реф —
|
- **реф:** `pathToFileURL` к `refs-html/<ref>.html` (`file://`). Реф —
|
||||||
самодостаточный (инлайн `<style>`), рендерится полностью. Шрифты для фазы A
|
самодостаточный (инлайн `<style>`), рендерится полностью. Шрифты для фазы A не
|
||||||
не важны (сравниваем структуру/текст, не растеризацию).
|
важны (сравниваем структуру/текст, не растеризацию).
|
||||||
- Переиспользуем из текущего `audit.mjs`: `discoverRoutes()`, `EXCLUDE`
|
- Переиспользуем из текущего `audit.mjs`: `discoverRoutes()`, `EXCLUDE`
|
||||||
(`/preview`), `DYNAMIC` (`tools/[id]` → `gradient.html` /
|
(`/preview`), `DYNAMIC` (`tools/[id]` → `gradient.html` /
|
||||||
`background-remover.html`), `refNameFor()`.
|
`background-remover.html`), `refNameFor()`.
|
||||||
@@ -122,8 +121,8 @@ Svelte-атрибутов и комментов гидрации), чтобы п
|
|||||||
### Шаги (на каждом маршруте)
|
### Шаги (на каждом маршруте)
|
||||||
|
|
||||||
1. Прогнать `pnpm refs-dom-audit`, получить дельты.
|
1. Прогнать `pnpm refs-dom-audit`, получить дельты.
|
||||||
2. Поправить структуру Svelte под реф (добавить/убрать/переставить узлы,
|
2. Поправить структуру Svelte под реф (добавить/убрать/переставить узлы, вынести
|
||||||
вынести тексты в `t()` с EN-базой).
|
тексты в `t()` с EN-базой).
|
||||||
3. Перезапустить, пока `added/removed/moved/textMismatch` ≃ 0 (кроме `IGNORE`).
|
3. Перезапустить, пока `added/removed/moved/textMismatch` ≃ 0 (кроме `IGNORE`).
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -135,17 +134,17 @@ Svelte-атрибутов и комментов гидрации), чтобы п
|
|||||||
|
|
||||||
### Сравниваемые свойства (computed)
|
### Сравниваемые свойства (computed)
|
||||||
|
|
||||||
`borderRadius`, позиция/размер (`getBoundingClientRect`: x/y/w/h),
|
`borderRadius`, позиция/размер (`getBoundingClientRect`: x/y/w/h), `padding`
|
||||||
`padding` (все стороны), `margin`, `color`, `backgroundColor`,
|
(все стороны), `margin`, `color`, `backgroundColor`,
|
||||||
`borderTop/Right/Bottom/Left Width|Color`, `fontFamily`, `fontSize`,
|
`borderTop/Right/Bottom/Left Width|Color`, `fontFamily`, `fontSize`,
|
||||||
`fontWeight`, `letterSpacing`, `lineHeight`, `gap`, `display`, `boxShadow`.
|
`fontWeight`, `letterSpacing`, `lineHeight`, `gap`, `display`, `boxShadow`.
|
||||||
|
|
||||||
### Дифф
|
### Дифф
|
||||||
|
|
||||||
Для каждого состыкованного по пути элемента — список свойств с дельтой
|
Для каждого состыкованного по пути элемента — список свойств с дельтой (наше
|
||||||
(наше значение → реф). Элементы, не совпавшие по пути (остаток из фазы A),
|
значение → реф). Элементы, не совпавшие по пути (остаток из фазы A), идут в
|
||||||
идут в `unmatched`. Отчёт: элемент → свойство → наше/реф. Правится одной
|
`unmatched`. Отчёт: элемент → свойство → наше/реф. Правится одной строкой CSS,
|
||||||
строкой CSS, без брутфорса.
|
без брутфорса.
|
||||||
|
|
||||||
### Шаги
|
### Шаги
|
||||||
|
|
||||||
@@ -167,17 +166,17 @@ Svelte-атрибутов и комментов гидрации), чтобы п
|
|||||||
(шаги 1–5 плана list-tools). То есть фаза A/B для list-tools стартует после
|
(шаги 1–5 плана list-tools). То есть фаза A/B для list-tools стартует после
|
||||||
landing этих шагов.
|
landing этих шагов.
|
||||||
3. **`tools/linear-gradient-png`** (↔ `gradient.html`) и
|
3. **`tools/linear-gradient-png`** (↔ `gradient.html`) и
|
||||||
**`tools/remove-background-png`** (↔ `background-remover.html`) — **последние**.
|
**`tools/remove-background-png`** (↔ `background-remover.html`) —
|
||||||
Известные расхождения: наши инструменты могут иметь **другие наборы
|
**последние**. Известные расхождения: наши инструменты могут иметь **другие
|
||||||
параметров / UI**, чем конкретные рефовые тулзы. Ожидаются структурные и
|
наборы параметров / UI**, чем конкретные рефовые тулзы. Ожидаются структурные
|
||||||
стилевые дельты, которые не сводятся к «подровнять паддинг». Для них —
|
и стилевые дельты, которые не сводятся к «подровнять паддинг». Для них —
|
||||||
триаж после прогона:
|
триаж после прогона:
|
||||||
- реальный разрыв верности → фиксим;
|
- реальный разрыв верности → фиксим;
|
||||||
- принципиальное отличие (параметр, которого нет в реф-тулзе; иной контрол)
|
- принципиальное отличие (параметр, которого нет в реф-тулзе; иной контрол) →
|
||||||
→ либо аккуратно приводим наш UI параметра к рефу, либо **документируем
|
либо аккуратно приводим наш UI параметра к рефу, либо **документируем как
|
||||||
как допустимое расхождение / заносим в allowlist** конфига аудита
|
допустимое расхождение / заносим в allowlist** конфига аудита (селектор +
|
||||||
(селектор + причина). «Скипы/фиксы» для инструментов оформляем явно, чтобы
|
причина). «Скипы/фиксы» для инструментов оформляем явно, чтобы аудит не
|
||||||
аудит не шумел на осознанных отличиях.
|
шумел на осознанных отличиях.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -185,25 +184,25 @@ Svelte-атрибутов и комментов гидрации), чтобы п
|
|||||||
|
|
||||||
- `scripts/audit-dom.mjs` — фаза A (нормализация + структурный дифф).
|
- `scripts/audit-dom.mjs` — фаза A (нормализация + структурный дифф).
|
||||||
- `scripts/audit-css.mjs` — фаза B (матчинг по пути + дифф стилей).
|
- `scripts/audit-css.mjs` — фаза B (матчинг по пути + дифф стилей).
|
||||||
- `package.json`: `refs-dom-audit`, `refs-css-audit` (опционально
|
- `package.json`: `refs-dom-audit`, `refs-css-audit` (опционально `refs-audit`,
|
||||||
`refs-audit`, прогоняющий обе фазы).
|
прогоняющий обе фазы).
|
||||||
- Переиспользуем из старого `audit.mjs`: `discoverRoutes`, `EXCLUDE`,
|
- Переиспользуем из старого `audit.mjs`: `discoverRoutes`, `EXCLUDE`, `DYNAMIC`,
|
||||||
`DYNAMIC`, `refNameFor`, `waitFor` (fast-fail на 4xx/5xx).
|
`refNameFor`, `waitFor` (fast-fail на 4xx/5xx).
|
||||||
- Отчёты: `web/audit/dom-report.{md,json}` и `web/audit/css-report.{md,json}`
|
- Отчёты: `web/audit/dom-report.{md,json}` и `web/audit/css-report.{md,json}`
|
||||||
(старый `audit-report.*` заменяем).
|
(старый `audit-report.*` заменяем).
|
||||||
|
|
||||||
## Верификация (после каждого коммита)
|
## Верификация (после каждого коммита)
|
||||||
|
|
||||||
- `pnpm lint`, `pnpm exec svelte-check --tsconfig ./tsconfig.json`,
|
- `pnpm lint`, `pnpm exec svelte-check --tsconfig ./tsconfig.json`, `pnpm build`
|
||||||
`pnpm build` — без ошибок.
|
— без ошибок.
|
||||||
- `pnpm refs-dom-audit` (фаза A) / `pnpm refs-css-audit` (фаза B) по
|
- `pnpm refs-dom-audit` (фаза A) / `pnpm refs-css-audit` (фаза B) по затронутому
|
||||||
затронутому маршруту: счётчики расхождений не растут без причины.
|
маршруту: счётчики расхождений не растут без причины.
|
||||||
|
|
||||||
## Риски / заметки
|
## Риски / заметки
|
||||||
|
|
||||||
- **Стабильность матчинга:** нужна аккуратная нормализация (сортировка
|
- **Стабильность матчинга:** нужна аккуратная нормализация (сортировка классов,
|
||||||
классов, игнор svelte-атрибутов/`data-*`, игнор whitespace-текстовых узлов,
|
игнор svelte-атрибутов/`data-*`, игнор whitespace-текстовых узлов, игнор
|
||||||
игнор внутренностей SVG-иконок). Подстраивается по первым прогонам demo.
|
внутренностей SVG-иконок). Подстраивается по первым прогонам demo.
|
||||||
- **Оболочка** (TopBar/footer/язык) сейчас отличается по дизайну — сводим к рефу
|
- **Оболочка** (TopBar/footer/язык) сейчас отличается по дизайну — сводим к рефу
|
||||||
в рамках полного соответствия (без IGNORE); финальная унификация компонентов —
|
в рамках полного соответствия (без IGNORE); финальная унификация компонентов —
|
||||||
отдельным рефакторингом позже.
|
отдельным рефакторингом позже.
|
||||||
|
|||||||
@@ -5,56 +5,54 @@
|
|||||||
> `schema.layout`, изоляция веток и вся миграция инструментов (121/125,
|
> `schema.layout`, изоляция веток и вся миграция инструментов (121/125,
|
||||||
> остальные 4 — отложены решениями) выполнены. Ключевые архитектурные решения
|
> остальные 4 — отложены решениями) выполнены. Ключевые архитектурные решения
|
||||||
> (параллельная сборка, правило копий, изоляция old↔preview) закреплены в
|
> (параллельная сборка, правило копий, изоляция old↔preview) закреплены в
|
||||||
> `AGENTS.md`.
|
> `AGENTS.md`. **Незакрытое из этого плана:** шаг 37 — физический переезд старых
|
||||||
> **Незакрытое из этого плана:** шаг 37 — физический переезд старых файлов
|
> файлов в папки `old/` (перенесён в `backlog.md`), и удаление старого кода — по
|
||||||
> в папки `old/` (перенесён в `backlog.md`), и удаление старого кода —
|
> `plan-redesign.md` (C17–C21). Отложенные инструменты — `watermark-image-png`
|
||||||
> по `plan-redesign.md` (C17–C21). Отложенные инструменты —
|
> (overlay), `png-info`, `reduce-to-size` — в `backlog.md`. Ниже — полное
|
||||||
> `watermark-image-png` (overlay), `png-info`, `reduce-to-size` — в `backlog.md`.
|
> состояние на момент закрытия.
|
||||||
> Ниже — полное состояние на момент закрытия.
|
|
||||||
|
|
||||||
> Статус: **в реализации.** Фаза 0 (фундамент) ✔, Фаза 1 (рабочий инструмент
|
> Статус: **в реализации.** Фаза 0 (фундамент) ✔, Фаза 1 (рабочий инструмент в
|
||||||
> в preview) ✔, Фаза 2 (простые инструменты без составных типов) — переведены
|
> preview) ✔, Фаза 2 (простые инструменты без составных типов) — переведены все
|
||||||
> все одиночные инструменты (18 шт). Фаза 3 — **составной тип `dimension`
|
> одиночные инструменты (18 шт). Фаза 3 — **составной тип `dimension` полностью
|
||||||
> полностью переведён** (12 инструментов: create-empty, single-color,
|
> переведён** (12 инструментов: create-empty, single-color, random-noise,
|
||||||
> random-noise, linear-gradient, color-spectrum, random-colors, draw-grid,
|
> linear-gradient, color-spectrum, random-colors, draw-grid, placeholder,
|
||||||
> placeholder, fit-on-background, change-canvas-size, resize, crop); **составной
|
> fit-on-background, change-canvas-size, resize, crop); **составной тип
|
||||||
> тип `color-pair` полностью переведён** (4 инструмента: blend-two,
|
> `color-pair` полностью переведён** (4 инструмента: blend-two, step-colors,
|
||||||
> step-colors, linear-gradient, two-colors); **составной тип `offset`
|
> linear-gradient, two-colors); **составной тип `offset` полностью переведён**
|
||||||
> полностью переведён** (4 инструмента: circle-mask, square-mask,
|
> (4 инструмента: circle-mask, square-mask, star-mask, wavy-mask); **составной
|
||||||
> star-mask, wavy-mask); **составной тип `position9` — переведены
|
> тип `position9` — переведены add-text-png и date-stamp-png** (водяной
|
||||||
> add-text-png и date-stamp-png** (водяной знак-картинка — отдельный шаг:
|
> знак-картинка — отдельный шаг: требует overlay-механику в новом превью);
|
||||||
> требует overlay-механику в новом превью); **составной тип `font-style`
|
> **составной тип `font-style` полностью переведён** (text-to-png, add-text,
|
||||||
> полностью переведён** (text-to-png, add-text, date-stamp); **составной тип
|
> date-stamp); **составной тип `plate` полностью переведён** (add-text,
|
||||||
> `plate` полностью переведён** (add-text, date-stamp); **составной тип
|
> date-stamp); **составной тип `gradient` полностью переведён**
|
||||||
> `gradient` полностью переведён** (linear-gradient): вместо горизонтального/
|
> (linear-gradient): вместо горизонтального/ вертикального select — **угол
|
||||||
> вертикального select — **угол 0..360°** (новый виджет `AngleControl`:
|
> 0..360°** (новый виджет `AngleControl`: slider + кнопки-пресеты
|
||||||
> slider + кнопки-пресеты 0°/90°/180°/270°, переиспользуемый компонент);
|
> 0°/90°/180°/270°, переиспользуемый компонент); preview научен применять
|
||||||
> preview
|
> **генераторы** (`executeGenerate`, кнопка Generate) и рендерить все kinds
|
||||||
> научен применять **генераторы** (`executeGenerate`, кнопка Generate) и
|
> схемы (slider/number/color/select/checkbox/dimension/
|
||||||
> рендерить все kinds схемы (slider/number/color/select/checkbox/dimension/
|
|
||||||
> color-pair/offset/position9/font-style/plate/gradient). **Пер-инструмент
|
> color-pair/offset/position9/font-style/plate/gradient). **Пер-инструмент
|
||||||
> раскладка `schema.layout` реализована** (шаг 32): тип `ToolSchemaLayout`
|
> раскладка `schema.layout` реализована** (шаг 32): тип `ToolSchemaLayout`
|
||||||
> {groups: [{title?, cols?, fields}]}, рендер групп в `SchemaFields.svelte`
|
> {groups: [{title?, cols?, fields}]}, рендер групп в `SchemaFields.svelte`
|
||||||
> (заголовок группы + сетка колонок, неупомянутые поля — в общей группе),
|
> (заголовок группы + сетка колонок, неупомянутые поля — в общей группе), пилот
|
||||||
> пилот — `add-text` (группы Text/Placement/Plate). **UI-макеты расставлены
|
> — `add-text` (группы Text/Placement/Plate). **UI-макеты расставлены по
|
||||||
> по переведённым инструментам** (шаг 33): geometry (fit-on-background,
|
> переведённым инструментам** (шаг 33): geometry (fit-on-background,
|
||||||
> change-canvas-size, resize, crop), маски alpha (Shape/Position), generate
|
> change-canvas-size, resize, crop), маски alpha (Shape/Position), generate
|
||||||
> (create-empty, linear-gradient, color-spectrum, random-colors, draw-grid,
|
> (create-empty, linear-gradient, color-spectrum, random-colors, draw-grid,
|
||||||
> step-colors, placeholder, text-to-png), text (add-text, date-stamp),
|
> step-colors, placeholder, text-to-png), text (add-text, date-stamp), filters
|
||||||
> filters (randomize-pixels, add-noise) — везде, где канвас отделён от
|
> (randomize-pixels, add-noise) — везде, где канвас отделён от параметров
|
||||||
> параметров эффекта либо фигура от позиции.
|
> эффекта либо фигура от позиции. Следующее закрыто в
|
||||||
> Следующее закрыто в `archive/plan-migrate-remaining.md` (2026-09-07):
|
> `archive/plan-migrate-remaining.md` (2026-09-07): перевод оставшихся
|
||||||
> перевод оставшихся инструментов выполнен (всего 121/125 в preview; 4 хвоста —
|
> инструментов выполнен (всего 121/125 в preview; 4 хвоста —
|
||||||
> `watermark-image-png`, `png-info`, `reduce-to-size-png`, `compress-png` —
|
> `watermark-image-png`, `png-info`, `reduce-to-size-png`, `compress-png` —
|
||||||
> отложены решениями в `backlog.md`). Незакрытые продолжения: переезд старых
|
> отложены решениями в `backlog.md`). Незакрытые продолжения: переезд старых
|
||||||
> компонентов в папки `old/` (шаг 37 ниже), закрытие долгов и сбор фидбека —
|
> компонентов в папки `old/` (шаг 37 ниже), закрытие долгов и сбор фидбека — см.
|
||||||
> см. также `plan-design-fix.md`.
|
> также `plan-design-fix.md`.
|
||||||
>
|
>
|
||||||
> Ключевые файлы нового registry: `web/src/lib/registry-new/{types,index,*}.ts`
|
> Ключевые файлы нового registry: `web/src/lib/registry-new/{types,index,*}.ts`
|
||||||
> (по файлу на категорию: geometry/alpha/convert/analyze/filters/color/generate)
|
> (по файлу на категорию: geometry/alpha/convert/analyze/filters/color/generate)
|
||||||
>
|
>
|
||||||
> - `web/src/lib/preview/categories.ts`,
|
> - `web/src/lib/preview/categories.ts`, `web/src/lib/registry-schema.ts` (kind
|
||||||
> `web/src/lib/registry-schema.ts` (kind `dimension`), `kit/fields/DimensionField.svelte`,
|
> `dimension`), `kit/fields/DimensionField.svelte`,
|
||||||
> `preview/executor/index.ts` (`executeGenerate`), `SchemaFields.svelte`
|
> `preview/executor/index.ts` (`executeGenerate`), `SchemaFields.svelte`
|
||||||
> (полный рендер kinds). Старый `web/src/lib/registry.ts` разбит по категориям
|
> (полный рендер kinds). Старый `web/src/lib/registry.ts` разбит по категориям
|
||||||
> в `web/src/lib/registry/` (см. `registry.ts` — тонкий баррель).
|
> в `web/src/lib/registry/` (см. `registry.ts` — тонкий баррель).
|
||||||
@@ -62,23 +60,23 @@
|
|||||||
## Ключевая стратегия: параллельная сборка, старый UI не трогаем
|
## Ключевая стратегия: параллельная сборка, старый UI не трогаем
|
||||||
|
|
||||||
Старый UI (группа `(old)/`, `ParamForm.svelte` + старый pipeline) **продолжает
|
Старый UI (группа `(old)/`, `ParamForm.svelte` + старый pipeline) **продолжает
|
||||||
работать как сейчас, без рефакторинга**. Новый `ToolSchema<P>` строится
|
работать как сейчас, без рефакторинга**. Новый `ToolSchema<P>` строится рядом и
|
||||||
рядом и служит источником для **нового UI**. Инструменты переписываются под
|
служит источником для **нового UI**. Инструменты переписываются под новый
|
||||||
новый registry по мере нужды — для нового UI.
|
registry по мере нужды — для нового UI.
|
||||||
|
|
||||||
Это та же логика, что в `plan-redesign.md` применена к дизайну: параллельная
|
Это та же логика, что в `plan-redesign.md` применена к дизайну: параллельная
|
||||||
сборка, изоляция от старого, затем новый становится основным и старый
|
сборка, изоляция от старого, затем новый становится основным и старый удаляется
|
||||||
удаляется вместе со старым дизайном.
|
вместе со старым дизайном.
|
||||||
|
|
||||||
**Правило копий (важно!):** если для нового UI/registry нужно внести изменения
|
**Правило копий (важно!):** если для нового UI/registry нужно внести изменения в
|
||||||
в файл, который **прямо или косвенно** уже используется `(old)` веткой, — этот
|
файл, который **прямо или косвенно** уже используется `(old)` веткой, — этот
|
||||||
файл **НЕ трогаем**. Вместо этого делаем **копию** в новом месте (например,
|
файл **НЕ трогаем**. Вместо этого делаем **копию** в новом месте (например, в
|
||||||
в `lib/preview/`) и правим копию. Это приводит к дублированию, но
|
`lib/preview/`) и правим копию. Это приводит к дублированию, но **гарантированно
|
||||||
**гарантированно не задевает старую ветку сайта**. Пример: категории — новый
|
не задевает старую ветку сайта**. Пример: категории — новый
|
||||||
`lib/preview/categories.ts` (object as const) копирует и заменяет собой
|
`lib/preview/categories.ts` (object as const) копирует и заменяет собой
|
||||||
`../categories` для нового кода, старый `categories.ts` обслуживает `(old)` и
|
`../categories` для нового кода, старый `categories.ts` обслуживает `(old)` и
|
||||||
остаётся без изменений. После перехода (Фаза 5) копия становится основной,
|
остаётся без изменений. После перехода (Фаза 5) копия становится основной, а
|
||||||
а исходник продолжает обслуживать `(old)` UI (он не удаляется — см. шаг 35).
|
исходник продолжает обслуживать `(old)` UI (он не удаляется — см. шаг 35).
|
||||||
|
|
||||||
**Разделение схем: две независимые схемы.**
|
**Разделение схем: две независимые схемы.**
|
||||||
|
|
||||||
@@ -98,14 +96,14 @@
|
|||||||
Помимо двух схем, registry тоже разделён по UI (по факту миграции):
|
Помимо двух схем, registry тоже разделён по UI (по факту миграции):
|
||||||
|
|
||||||
- **Старый registry** (`web/src/lib/registry.ts` + `registry/` +
|
- **Старый registry** (`web/src/lib/registry.ts` + `registry/` +
|
||||||
`registry-helpers.ts`) — работает на старом UI, использует `ParamDef[]`.
|
`registry-helpers.ts`) — работает на старом UI, использует `ParamDef[]`. **Не
|
||||||
**Не трогаем**; остаётся обслуживать `(old)/` маршруты (см. шаг 35).
|
трогаем**; остаётся обслуживать `(old)/` маршруты (см. шаг 35).
|
||||||
- **Новый registry** (`web/src/lib/registry-new/`) — строится **с нуля «как надо»**:
|
- **Новый registry** (`web/src/lib/registry-new/`) — строится **с нуля «как
|
||||||
`ToolEntry<P>` с обязательным `schema`, типизированный `run`, **без** `ParamDef[]`
|
надо»**: `ToolEntry<P>` с обязательным `schema`, типизированный `run`, **без**
|
||||||
и **без** связи со старым. Импортирует core-функции (`expandCanvas`,
|
`ParamDef[]` и **без** связи со старым. Импортирует core-функции
|
||||||
`strokeImage`, …) напрямую. Наполняется **по-инструментно** по мере миграции
|
(`expandCanvas`, `strokeImage`, …) напрямую. Наполняется **по-инструментно**
|
||||||
(не разовым переводом всех 130). Preview показывает только переведённые
|
по мере миграции (не разовым переводом всех 130). Preview показывает только
|
||||||
инструменты.
|
переведённые инструменты.
|
||||||
|
|
||||||
Preview (`catalog.ts`, маршруты `preview/**`, `SchemaToolView`, `SchemaFields`)
|
Preview (`catalog.ts`, маршруты `preview/**`, `SchemaToolView`, `SchemaFields`)
|
||||||
импортируют из `$lib/registry-new`. После удаления старого UI `registry-new/`
|
импортируют из `$lib/registry-new`. После удаления старого UI `registry-new/`
|
||||||
@@ -122,12 +120,12 @@ Preview (`catalog.ts`, маршруты `preview/**`, `SchemaToolView`, `SchemaF
|
|||||||
|
|
||||||
## Проблема (текущая)
|
## Проблема (текущая)
|
||||||
|
|
||||||
- `ParamDef` — плоский union из 6 примитивов (`number | slider | select |
|
- `ParamDef` — плоский union из 6 примитивов
|
||||||
checkbox | color | text`). Параметры инструмента — отдельные записи в плоском
|
(`number | slider | select | checkbox | color | text`). Параметры инструмента
|
||||||
массиве.
|
— отдельные записи в плоском массиве.
|
||||||
- `run()`/`generate()` получают `Record<string, unknown>`; значения достаются
|
- `run()`/`generate()` получают `Record<string, unknown>`; значения достаются
|
||||||
через `num(p, "fromColor")` — runtime string lookup **без type safety**,
|
через `num(p, "fromColor")` — runtime string lookup **без type safety**, легко
|
||||||
легко напутать id.
|
напутать id.
|
||||||
- Поля инструмента **слабо связаны** (например «ширина» и «цвет» рамки — два
|
- Поля инструмента **слабо связаны** (например «ширина» и «цвет» рамки — два
|
||||||
независимых поля без общего контракта) → общий генерализованный рендер, нет
|
независимых поля без общего контракта) → общий генерализованный рендер, нет
|
||||||
возможности сделать идеальный пер-инструмент UI.
|
возможности сделать идеальный пер-инструмент UI.
|
||||||
@@ -139,16 +137,16 @@ checkbox | color | text`). Параметры инструмента — отд
|
|||||||
1. **Полная типизация.** `interface Params` (runtime-тип) объявляется явно в
|
1. **Полная типизация.** `interface Params` (runtime-тип) объявляется явно в
|
||||||
каждом инструменте. `run`/`generate` типизируются прямо на него, а не на
|
каждом инструменте. `run`/`generate` типизируются прямо на него, а не на
|
||||||
`Record<string, unknown>`.
|
`Record<string, unknown>`.
|
||||||
2. **Отдельная типизированная схема для UI/дефолтов/валидации.** Два понятия
|
2. **Отдельная типизированная схема для UI/дефолтов/валидации.** Два понятия на
|
||||||
на инструмент: runtime-тип + схема. Схема проверяется компилятором на
|
инструмент: runtime-тип + схема. Схема проверяется компилятором на
|
||||||
соответствие `Params` (чтобы не рассинхронизировались).
|
соответствие `Params` (чтобы не рассинхронизировались).
|
||||||
3. **Общий рендер,** но с пер-инструмент layout: общие компоненты рисуют поля
|
3. **Общий рендер,** но с пер-инструмент layout: общие компоненты рисуют поля по
|
||||||
по схеме; схема инструмента задаёт layout и группировку. Без отдельного
|
схеме; схема инструмента задаёт layout и группировку. Без отдельного
|
||||||
Svelte-компонента на каждый инструмент.
|
Svelte-компонента на каждый инструмент.
|
||||||
4. **Составные типы** (`gradient`, `color-pair`, `dimension`, `offset`,
|
4. **Составные типы** (`gradient`, `color-pair`, `dimension`, `offset`,
|
||||||
`position9`, `font-style`, `plate`) остаются в плане — это способ выразить
|
`position9`, `font-style`, `plate`) остаются в плане — это способ выразить в
|
||||||
в схеме связанную группу полей и переиспользовать её на нескольких
|
схеме связанную группу полей и переиспользовать её на нескольких инструментах
|
||||||
инструментах и в UI.
|
и в UI.
|
||||||
5. Рано или поздно `tool-views.ts` поглощается registry (preview/lede/layout
|
5. Рано или поздно `tool-views.ts` поглощается registry (preview/lede/layout
|
||||||
переезжают в meta инструмента) — но это отдельный шаг, см. ниже.
|
переезжают в meta инструмента) — но это отдельный шаг, см. ниже.
|
||||||
6. **Старый UI не рефакторим.** Две независимые схемы: старый `ParamDef[]`
|
6. **Старый UI не рефакторим.** Две независимые схемы: старый `ParamDef[]`
|
||||||
@@ -157,8 +155,8 @@ checkbox | color | text`). Параметры инструмента — отд
|
|||||||
|
|
||||||
## Целевая архитектура (на примере «добавить рамку»)
|
## Целевая архитектура (на примере «добавить рамку»)
|
||||||
|
|
||||||
Цель: «добавить рамку: ширина и цвет» — **один интерфейс**, а не два
|
Цель: «добавить рамку: ширина и цвет» — **один интерфейс**, а не два слабо
|
||||||
слабо связанных поля.
|
связанных поля.
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// 1. Runtime-тип — что получает run/generate
|
// 1. Runtime-тип — что получает run/generate
|
||||||
@@ -232,8 +230,8 @@ function toolSchema<P>(fields: FieldsOf<P>): ToolSchema<P> { ... }
|
|||||||
```
|
```
|
||||||
|
|
||||||
Ключевая идея связки: **builder `field.x<T>()` связывает runtime-тип поля с его
|
Ключевая идея связки: **builder `field.x<T>()` связывает runtime-тип поля с его
|
||||||
схемой на этапе компиляции**, а `toolSchema<P>` заставляет ключи схемы
|
схемой на этапе компиляции**, а `toolSchema<P>` заставляет ключи схемы совпадать
|
||||||
совпадать с ключами `P`. Это и есть защита от рассинхрона.
|
с ключами `P`. Это и есть защита от рассинхрона.
|
||||||
|
|
||||||
## Составные типы (для переиспользования и группировки)
|
## Составные типы (для переиспользования и группировки)
|
||||||
|
|
||||||
@@ -247,9 +245,9 @@ function toolSchema<P>(fields: FieldsOf<P>): ToolSchema<P> { ... }
|
|||||||
| `font-style` | font + size + bold + color | 3-4 |
|
| `font-style` | font + size + bold + color | 3-4 |
|
||||||
| `plate` | enabled + color + opacity | 2-3 |
|
| `plate` | enabled + color + opacity | 2-3 |
|
||||||
|
|
||||||
Составной тип = группы полей, которые в интерфейсе `Params` могут разворачиваться
|
Составной тип = группы полей, которые в интерфейсе `Params` могут
|
||||||
во вложенный объект или плоские ключи — решается на этапе реализации.
|
разворачиваться во вложенный объект или плоские ключи — решается на этапе
|
||||||
Пример для `gradient`:
|
реализации. Пример для `gradient`:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
interface GradientParams {
|
interface GradientParams {
|
||||||
@@ -293,27 +291,27 @@ add-text-png, date-stamp-png
|
|||||||
|
|
||||||
## Пошаговый план перевода инструментов
|
## Пошаговый план перевода инструментов
|
||||||
|
|
||||||
> Каждый шаг — **атомарное, маленькое изменение** (в идеале 1 инструмент =
|
> Каждый шаг — **атомарное, маленькое изменение** (в идеале 1 инструмент = 1
|
||||||
> 1 diff, укладывается в правило «коммиты < ~500 строк»). После каждого шага —
|
> diff, укладывается в правило «коммиты < ~500 строк»). После каждого шага —
|
||||||
> ревью; параллельно можно делать UI нового инструмента.
|
> ревью; параллельно можно делать UI нового инструмента.
|
||||||
>
|
>
|
||||||
> **Принцип малых шагов:** перевод инструмента не меняет поведение старой
|
> **Принцип малых шагов:** перевод инструмента не меняет поведение старой схемы
|
||||||
> схемы (`params` остаётся), не ломает старый UI. Инструмент получает ДОПОЛНИТЕЛЬНО
|
> (`params` остаётся), не ломает старый UI. Инструмент получает ДОПОЛНИТЕЛЬНО
|
||||||
> новое поле `schema` + `interface Params`, старое `run`/`params` не трогаем.
|
> новое поле `schema` + `interface Params`, старое `run`/`params` не трогаем.
|
||||||
> Это гарантирует, что старый UI продолжает работать без регресса.
|
> Это гарантирует, что старый UI продолжает работать без регресса.
|
||||||
>
|
>
|
||||||
> **Новый подход (решение пользователя):** сначала делаем **один полноценный
|
> **Новый подход (решение пользователя):** сначала делаем **один полноценный
|
||||||
> рабочий инструмент в preview** — не эталон «на бумаге», а реально юзабельный,
|
> рабочий инструмент в preview** — не эталон «на бумаге», а реально юзабельный,
|
||||||
> чтобы руками оценить, насколько это рабочее решение и какие возникнут сложности.
|
> чтобы руками оценить, насколько это рабочее решение и какие возникнут
|
||||||
> Оцениваем, при необходимости правим инфраструктуру, и только потом переводим
|
> сложности. Оцениваем, при необходимости правим инфраструктуру, и только потом
|
||||||
> остальные инструменты тем же проверенным паттерном.
|
> переводим остальные инструменты тем же проверенным паттерном.
|
||||||
|
|
||||||
### Фаза 0 — фундамент ✔ (сделано)
|
### Фаза 0 — фундамент ✔ (сделано)
|
||||||
|
|
||||||
Typed field builders + `Field<T>` + `toolSchema<P>()` + `ToolSchema<P>` —
|
Typed field builders + `Field<T>` + `toolSchema<P>()` + `ToolSchema<P>` — в
|
||||||
в `web/src/lib/registry-schema.ts`. `ToolEntry<P>` generic + опциональное
|
`web/src/lib/registry-schema.ts`. `ToolEntry<P>` generic + опциональное поле
|
||||||
поле `schema`. Новые `defaultSchemaParams`/`sanitizeSchemaParams`.
|
`schema`. Новые `defaultSchemaParams`/`sanitizeSchemaParams`. Старый
|
||||||
Старый `ParamDef[]`/pipeline не тронуты. Проверено: compile-time-защита
|
`ParamDef[]`/pipeline не тронуты. Проверено: compile-time-защита
|
||||||
(лишний/неверный/отсутствующий ключ ловит TS), тесты, svelte-check без новых
|
(лишний/неверный/отсутствующий ключ ловит TS), тесты, svelte-check без новых
|
||||||
ошибок.
|
ошибок.
|
||||||
|
|
||||||
@@ -326,18 +324,18 @@ Typed field builders + `Field<T>` + `toolSchema<P>()` + `ToolSchema<P>` —
|
|||||||
5. **Инструмент «добавить рамку» (`add-border-png`)** — сделать полностью
|
5. **Инструмент «добавить рамку» (`add-border-png`)** — сделать полностью
|
||||||
рабочим в preview:
|
рабочим в preview:
|
||||||
- `interface AddBorderParams { thickness: number; color: string }`
|
- `interface AddBorderParams { thickness: number; color: string }`
|
||||||
- `addBorderSchema = toolSchema<AddBorderParams>(...)` + `schema: ...` в entry
|
- `addBorderSchema = toolSchema<AddBorderParams>(...)` + `schema: ...` в
|
||||||
(старый `params`/`run` остаются — старый UI не регрессит).
|
entry (старый `params`/`run` остаются — старый UI не регрессит).
|
||||||
- **Новый рендер в preview** (`kit/ToolView` → `ParamControl` или новый
|
- **Новый рендер в preview** (`kit/ToolView` → `ParamControl` или новый
|
||||||
компонент схемы): читает `ToolSchema<P>`, рисует поля рамки, применение
|
компонент схемы): читает `ToolSchema<P>`, рисует поля рамки, применение
|
||||||
реально работает (end-to-end до картинки).
|
реально работает (end-to-end до картинки).
|
||||||
- Дефолты/валидация/значения — из схемы (`defaultSchemaParams`,
|
- Дефолты/валидация/значения — из схемы (`defaultSchemaParams`,
|
||||||
`sanitizeSchemaParams`).
|
`sanitizeSchemaParams`).
|
||||||
- **Ревью-оценка:** удобно ли это, что сломалось/усложнилось, что поправить
|
- **Ревью-оценка:** удобно ли это, что сломалось/усложнилось, что поправить в
|
||||||
в инфраструктуре до масштабирования. Принимается макет/решение.
|
инфраструктуре до масштабирования. Принимается макет/решение.
|
||||||
|
|
||||||
Результат: один реально рабочий инструмент на новой системе = доказываем паттерн,
|
Результат: один реально рабочий инструмент на новой системе = доказываем
|
||||||
прежде чем переводить остальных.
|
паттерн, прежде чем переводить остальных.
|
||||||
|
|
||||||
### Фаза 2 — простые инструменты без составных типов (1 инструмент = 1 шаг)
|
### Фаза 2 — простые инструменты без составных типов (1 инструмент = 1 шаг)
|
||||||
|
|
||||||
@@ -345,8 +343,8 @@ Typed field builders + `Field<T>` + `toolSchema<P>()` + `ToolSchema<P>` —
|
|||||||
составных типов. Каждый — отдельный маленький diff (~15-30 строк), тем самым
|
составных типов. Каждый — отдельный маленький diff (~15-30 строк), тем самым
|
||||||
проверенным в Фазе 1 паттерном. **Переезжают в `registry-new`.**
|
проверенным в Фазе 1 паттерном. **Переезжают в `registry-new`.**
|
||||||
|
|
||||||
Переведено: `add-border-png` (Фаза 1), `add-stroke-png`, `find-contour-png`, плюс
|
Переведено: `add-border-png` (Фаза 1), `add-stroke-png`, `find-contour-png`,
|
||||||
весь блок простых инструментов ниже (пункты 8-22). Следующее — Фаза 3.
|
плюс весь блок простых инструментов ниже (пункты 8-22). Следующее — Фаза 3.
|
||||||
|
|
||||||
6. ~~add-stroke-png (color + slider)~~ → переведён в `registry-new` ✔
|
6. ~~add-stroke-png (color + slider)~~ → переведён в `registry-new` ✔
|
||||||
7. ~~find-contour-png (color + slider)~~ → переведён в `registry-new` ✔
|
7. ~~find-contour-png (color + slider)~~ → переведён в `registry-new` ✔
|
||||||
@@ -369,8 +367,8 @@ Typed field builders + `Field<T>` + `toolSchema<P>()` + `ToolSchema<P>` —
|
|||||||
> **Замечание (конвертеры):** старые `convert-png-to-jpg`/`convert-png-to-webp`
|
> **Замечание (конвертеры):** старые `convert-png-to-jpg`/`convert-png-to-webp`
|
||||||
> несли `output`-метаданные (`mime`/`ext`/`qualityParamId`) для выбора формата
|
> несли `output`-метаданные (`mime`/`ext`/`qualityParamId`) для выбора формата
|
||||||
> вывода. В новом `ToolEntry<P>` поля `output` пока нет, поэтому при переводе
|
> вывода. В новом `ToolEntry<P>` поля `output` пока нет, поэтому при переводе
|
||||||
> эти метаданные не перенесены — preview пока отдаёт результат как PNG.
|
> эти метаданные не перенесены — preview пока отдаёт результат как PNG. Механика
|
||||||
> Механика выбора формата/качества в новом UI — отдельный шаг (не блокирует Фазу 2).
|
> выбора формата/качества в новом UI — отдельный шаг (не блокирует Фазу 2).
|
||||||
|
|
||||||
### Фаза 3 — инструменты с составными типами (по типу, затем по инструментам)
|
### Фаза 3 — инструменты с составными типами (по типу, затем по инструментам)
|
||||||
|
|
||||||
@@ -378,19 +376,19 @@ Typed field builders + `Field<T>` + `toolSchema<P>()` + `ToolSchema<P>` —
|
|||||||
`single-color-png`, `random-noise-png`, `linear-gradient-png`,
|
`single-color-png`, `random-noise-png`, `linear-gradient-png`,
|
||||||
`color-spectrum-png`, `random-colors-png`, `draw-grid-png`,
|
`color-spectrum-png`, `random-colors-png`, `draw-grid-png`,
|
||||||
`placeholder-png` — генераторы в `registry-new/generate.ts`;
|
`placeholder-png` — генераторы в `registry-new/generate.ts`;
|
||||||
`fit-on-background-png`, `change-canvas-size-png`, `resize-png`,
|
`fit-on-background-png`, `change-canvas-size-png`, `resize-png`, `crop-png`
|
||||||
`crop-png` — в `registry-new/geometry.ts`). Составной тип во всех видах:
|
— в `registry-new/geometry.ts`). Составной тип во всех видах: вложенный
|
||||||
вложенный объект `size: { width, height }` в Params + `field.dimension`,
|
объект `size: { width, height }` в Params + `field.dimension`, виджет
|
||||||
виджет `kit/fields/DimensionField.svelte`.
|
`kit/fields/DimensionField.svelte`. Preview: генераторы применяются через
|
||||||
Preview: генераторы применяются через `executeGenerate` (кнопка Generate),
|
`executeGenerate` (кнопка Generate), `SchemaFields` рендерит все kinds схемы
|
||||||
`SchemaFields` рендерит все kinds схемы (slider/number/color/select/
|
(slider/number/color/select/ checkbox/dimension). Тесты: 592 passed.
|
||||||
checkbox/dimension). Тесты: 592 passed.
|
|
||||||
25. `color-pair` — **все 4 инструмента переведены** ✔ (`blend-two-png`,
|
25. `color-pair` — **все 4 инструмента переведены** ✔ (`blend-two-png`,
|
||||||
`step-colors-png`, `linear-gradient-png` — генераторы в
|
`step-colors-png`, `linear-gradient-png` — генераторы в
|
||||||
`registry-new/generate.ts`, `two-colors-png` — run в `registry-new/color.ts`).
|
`registry-new/generate.ts`, `two-colors-png` — run в
|
||||||
Составной тип во всех видах: вложенный объект `pair: { from, to }` +
|
`registry-new/color.ts`). Составной тип во всех видах: вложенный объект
|
||||||
`field.colorPair`, виджет `kit/fields/schema/ColorPairControl.svelte`,
|
`pair: { from, to }` + `field.colorPair`, виджет
|
||||||
kind `color-pair` в схеме (default/sanitize). Тесты: +4 (596 passed).
|
`kit/fields/schema/ColorPairControl.svelte`, kind `color-pair` в схеме
|
||||||
|
(default/sanitize). Тесты: +4 (596 passed).
|
||||||
26. `offset` — **все 4 инструмента переведены** ✔ (`circle-mask-png`,
|
26. `offset` — **все 4 инструмента переведены** ✔ (`circle-mask-png`,
|
||||||
`square-mask-png`, `star-mask-png`, `wavy-mask-png` — run в
|
`square-mask-png`, `star-mask-png`, `wavy-mask-png` — run в
|
||||||
`registry-new/alpha.ts`). Составной тип во всех видах: вложенный объект
|
`registry-new/alpha.ts`). Составной тип во всех видах: вложенный объект
|
||||||
@@ -399,40 +397,39 @@ Typed field builders + `Field<T>` + `toolSchema<P>()` + `ToolSchema<P>` —
|
|||||||
(default/sanitize). Тесты: +4 (600 passed).
|
(default/sanitize). Тесты: +4 (600 passed).
|
||||||
27. `position9` — **переведены `add-text-png` и `date-stamp-png`** ✔ (run в
|
27. `position9` — **переведены `add-text-png` и `date-stamp-png`** ✔ (run в
|
||||||
`registry-new/text.ts`, оба `domOnly`). Составной тип во всех видах:
|
`registry-new/text.ts`, оба `domOnly`). Составной тип во всех видах:
|
||||||
значение — строка `Position9`, kind `position9` в схеме (default/sanitize
|
значение — строка `Position9`, kind `position9` в схеме (default/sanitize по
|
||||||
по `POSITION9_VALUES`), виджет 3×3 `kit/fields/schema/PositionControl.svelte`.
|
`POSITION9_VALUES`), виджет 3×3 `kit/fields/schema/PositionControl.svelte`.
|
||||||
В `ToolEntry` добавлен флаг `domOnly` (превью-executor держит такие
|
В `ToolEntry` добавлен флаг `domOnly` (превью-executor держит такие
|
||||||
инструменты вне worker). `watermark-image-png` — отдельный под-шаг:
|
инструменты вне worker). `watermark-image-png` — отдельный под-шаг: ему
|
||||||
ему нужен overlay-source (`getOverlay`/store), которого в новом превью пока
|
нужен overlay-source (`getOverlay`/store), которого в новом превью пока нет.
|
||||||
нет. Тесты: +2 (602 passed). `font-style` и `plate` на этих инструментах
|
Тесты: +2 (602 passed). `font-style` и `plate` на этих инструментах сводятся
|
||||||
сводятся в шаги 28-29.
|
в шаги 28-29.
|
||||||
28. `font-style` — **все 3 инструмента переведены** ✔ (`text-to-png` —
|
28. `font-style` — **все 3 инструмента переведены** ✔ (`text-to-png` — генератор
|
||||||
генератор в `registry-new/generate.ts` (domOnly), `add-text-png`/
|
в `registry-new/generate.ts` (domOnly), `add-text-png`/ `date-stamp-png` —
|
||||||
`date-stamp-png` — рефакторинг в `registry-new/text.ts`). Составной тип
|
рефакторинг в `registry-new/text.ts`). Составной тип во всех видах:
|
||||||
во всех видах: вложенный объект `style: { font, size, bold, color }` +
|
вложенный объект `style: { font, size, bold, color }` + `field.fontStyle`,
|
||||||
`field.fontStyle`, виджет `kit/fields/schema/FontStyleControl.svelte`,
|
виджет `kit/fields/schema/FontStyleControl.svelte`, kind `font-style` в
|
||||||
kind `font-style` в схеме (default/sanitize, clamp размера к min/max).
|
схеме (default/sanitize, clamp размера к min/max). Тесты: +1 (603 passed).
|
||||||
Тесты: +1 (603 passed).
|
|
||||||
29. `plate` — **все 2 инструмента переведены** ✔ (`add-text-png`,
|
29. `plate` — **все 2 инструмента переведены** ✔ (`add-text-png`,
|
||||||
`date-stamp-png` — рефакторинг в `registry-new/text.ts`). Составной тип
|
`date-stamp-png` — рефакторинг в `registry-new/text.ts`). Составной тип во
|
||||||
во всех видах: вложенный объект `plate: { enabled, color, opacity }` +
|
всех видах: вложенный объект `plate: { enabled, color, opacity }` +
|
||||||
`field.plate`, виджет `kit/fields/schema/PlateControl.svelte` (чекбокс +
|
`field.plate`, виджет `kit/fields/schema/PlateControl.svelte` (чекбокс +
|
||||||
цвет + слайдер непрозрачности, деактивируется при выключенной плашке),
|
цвет + слайдер непрозрачности, деактивируется при выключенной плашке), kind
|
||||||
kind `plate` в схеме (default/sanitize, clamp opacity к 0..100).
|
`plate` в схеме (default/sanitize, clamp opacity к 0..100). Покрытие тестов
|
||||||
Покрытие тестов расширено (дефолты/sanitize plate в существующих тестах).
|
расширено (дефолты/sanitize plate в существующих тестах).
|
||||||
30. `gradient` — **переведён** ✔ (linear-gradient-png). Направление градиента
|
30. `gradient` — **переведён** ✔ (linear-gradient-png). Направление градиента —
|
||||||
— не горизонталь/вертикаль, а **произвольный угол**: новый переиспользуемый
|
не горизонталь/вертикаль, а **произвольный угол**: новый переиспользуемый
|
||||||
виджет `kit/fields/schema/AngleControl.svelte` (слайдер 0..360° + кнопки
|
виджет `kit/fields/schema/AngleControl.svelte` (слайдер 0..360° + кнопки
|
||||||
стандартных углов 0°/90°/180°/270° с активным состоянием). Составной тип
|
стандартных углов 0°/90°/180°/270° с активным состоянием). Составной тип
|
||||||
`gradient: { from, to, angle }` во всех видах (`field.gradient`, kind
|
`gradient: { from, to, angle }` во всех видах (`field.gradient`, kind
|
||||||
`gradient` в схеме — default/sanitize, clamp угла к 0..360, валидация
|
`gradient` в схеме — default/sanitize, clamp угла к 0..360, валидация
|
||||||
цветов; виджет `kit/fields/schema/GradientControl.svelte` — пара цветов +
|
цветов; виджет `kit/fields/schema/GradientControl.svelte` — пара цветов +
|
||||||
AngleControl). Рендер по углу — локальный `angleGradient` в
|
AngleControl). Рендер по углу — локальный `angleGradient` в
|
||||||
`registry-new/generate.ts` (core-`gradientImage` не трогали: он умеет
|
`registry-new/generate.ts` (core-`gradientImage` не трогали: он умеет только
|
||||||
только horizontal/vertical и используется старым UI). Семантика: 0° слева
|
horizontal/vertical и используется старым UI). Семантика: 0° слева направо,
|
||||||
направо, 90° сверху вниз, рост угла по часовой (ось Y вниз); нормализация
|
90° сверху вниз, рост угла по часовой (ось Y вниз); нормализация по полному
|
||||||
по полному диапазону проекции на ось (180°/270° разворачивают градиент).
|
диапазону проекции на ось (180°/270° разворачивают градиент). Тесты:
|
||||||
Тесты: дефолты, направление по углу (0°/90°/180°), sanitize/кламп.
|
дефолты, направление по углу (0°/90°/180°), sanitize/кламп.
|
||||||
|
|
||||||
### Фаза 4 — масштаб UI на остальные инструменты
|
### Фаза 4 — масштаб UI на остальные инструменты
|
||||||
|
|
||||||
@@ -443,40 +440,41 @@ Typed field builders + `Field<T>` + `toolSchema<P>()` + `ToolSchema<P>` —
|
|||||||
`ToolSchemaLayout.groups: { title?, cols?, fields[] }` в registry-schema;
|
`ToolSchemaLayout.groups: { title?, cols?, fields[] }` в registry-schema;
|
||||||
`SchemaFields.svelte` рендерит группы (подпись + сетка `1..n` колонок,
|
`SchemaFields.svelte` рендерит группы (подпись + сетка `1..n` колонок,
|
||||||
неупомянутые поля — в общей группе в конце, без заголовка). Пилот —
|
неупомянутые поля — в общей группе в конце, без заголовка). Пилот —
|
||||||
`add-text-png` (Text: text+style; Placement: position+margin; Plate).
|
`add-text-png` (Text: text+style; Placement: position+margin; Plate). Тесты:
|
||||||
Тесты: schema-layout в registry-schema.test.ts; полные запуски чисты.
|
schema-layout в registry-schema.test.ts; полные запуски чисты.
|
||||||
33. Для каждого переведённого инструмента — UI-макет **расставлен** ✔.
|
33. Для каждого переведённого инструмента — UI-макет **расставлен** ✔.
|
||||||
Layout-группы заданы там, где группировка содержательна (канвас отдельно
|
Layout-группы заданы там, где группировка содержательна (канвас отдельно от
|
||||||
от параметров эффекта, фигура отдельно от позиции; составные виджеты —
|
параметров эффекта, фигура отдельно от позиции; составные виджеты — внутри
|
||||||
внутри групп целиком):
|
групп целиком):
|
||||||
- geometry: fit-on-background (Canvas/Background), change-canvas-size
|
- geometry: fit-on-background (Canvas/Background), change-canvas-size
|
||||||
(Canvas/Anchor), resize (Canvas/Scaling), crop (Offset/Crop area);
|
(Canvas/Anchor), resize (Canvas/Scaling), crop (Offset/Crop area);
|
||||||
- alpha-маски: circle/square/star/wavy (Shape — с 2 колонками где плотно,
|
- alpha-маски: circle/square/star/wavy (Shape — с 2 колонками где плотно,
|
||||||
Position — offset);
|
Position — offset);
|
||||||
- generate: create-empty (Canvas/Fill), linear-gradient (Canvas/Colors),
|
- generate: create-empty (Canvas/Fill), linear-gradient (Canvas/Colors),
|
||||||
color-spectrum (Canvas/Spectrum), random-colors (Canvas/Random),
|
color-spectrum (Canvas/Spectrum), random-colors (Canvas/Random), draw-grid
|
||||||
draw-grid (Canvas/Grid), step-colors (Colors/Output), placeholder
|
(Canvas/Grid), step-colors (Colors/Output), placeholder
|
||||||
(Canvas/Colors/Text), text-to-png (Text/Background/Padding);
|
(Canvas/Colors/Text), text-to-png (Text/Background/Padding);
|
||||||
- text: add-text, date-stamp (Text/Placement/Plate);
|
- text: add-text, date-stamp (Text/Placement/Plate);
|
||||||
- filters: randomize-pixels (Blocks), add-noise (Noise/Seed).
|
- filters: randomize-pixels (Blocks), add-noise (Noise/Seed). Инструменты с
|
||||||
Инструменты с 1–3 простыми полями остались без layout (одна общая группа).
|
1–3 простыми полями остались без layout (одна общая группа).
|
||||||
|
|
||||||
### Фаза 5 — изоляция старого UI
|
### Фаза 5 — изоляция старого UI
|
||||||
|
|
||||||
34. Поглощение `tool-views.ts` (preview/lede/layout → meta инструмента) ✔.
|
34. Поглощение `tool-views.ts` (preview/lede/layout → meta инструмента) ✔.
|
||||||
Отдельного `tool-views.ts` в репо нет: preview сразу строился на
|
Отдельного `tool-views.ts` в репо нет: preview сразу строился на
|
||||||
`registry-new`. Meta инструмента живёт в `ToolEntry` (`title`, `description`,
|
`registry-new`. Meta инструмента живёт в `ToolEntry` (`title`,
|
||||||
`category`, `run`/`generate`), layout — в `schema.layout` (шаг 32), рендер —
|
`description`, `category`, `run`/`generate`), layout — в `schema.layout`
|
||||||
`SchemaToolView`/`SchemaFields`/`SchemaPreview`. Дублирующей системы нет.
|
(шаг 32), рендер — `SchemaToolView`/`SchemaFields`/`SchemaPreview`.
|
||||||
|
Дублирующей системы нет.
|
||||||
35. Старый UI **не удаляется**, а выносится в `(old)/`-маршруты и остаётся там
|
35. Старый UI **не удаляется**, а выносится в `(old)/`-маршруты и остаётся там
|
||||||
временно (посмотреть, как работает, сравнить с новым; старые тесты
|
временно (посмотреть, как работает, сравнить с новым; старые тесты
|
||||||
продолжают проходить):
|
продолжают проходить):
|
||||||
- маршруты `(old)/{+page,demo,list-tools,tools/[id]}` — старый дизайн,
|
- маршруты `(old)/{+page,demo,list-tools,tools/[id]}` — старый дизайн, тянет
|
||||||
тянет `old.css` (не `design2.css`), старый header/footer;
|
`old.css` (не `design2.css`), старый header/footer;
|
||||||
- старый `registry.ts`/`registry/` + `ParamDef[]` обслуживают только
|
- старый `registry.ts`/`registry/` + `ParamDef[]` обслуживают только
|
||||||
`(old)/`-инструменты — не удаляются, не рефакторятся;
|
`(old)/`-инструменты — не удаляются, не рефакторятся;
|
||||||
- удаление происходит позже, отдельным решением (когда новый UI покроет
|
- удаление происходит позже, отдельным решением (когда новый UI покроет все
|
||||||
все инструменты и ревью завершено).
|
инструменты и ревью завершено).
|
||||||
36. **Линтер-изоляция веток** (гарантия, что old и preview не смешиваются) ✔.
|
36. **Линтер-изоляция веток** (гарантия, что old и preview не смешиваются) ✔.
|
||||||
Кастомный ESLint-плагин `web/eslint-plugins/isolation/no-mixed-imports`
|
Кастомный ESLint-плагин `web/eslint-plugins/isolation/no-mixed-imports`
|
||||||
Резолвит каждый импорт (и `$lib/...`, и относительные `./`/`../`) до
|
Резолвит каждый импорт (и `$lib/...`, и относительные `./`/`../`) до
|
||||||
@@ -490,12 +488,12 @@ Typed field builders + `Field<T>` + `toolSchema<P>()` + `ToolSchema<P>` —
|
|||||||
`lib/registry-schema.ts`, `lib/registry-schema.test.ts`,
|
`lib/registry-schema.ts`, `lib/registry-schema.test.ts`,
|
||||||
`lib/components/kit/**`;
|
`lib/components/kit/**`;
|
||||||
- общее (разрешено обоим): всё прочее — `core/`, `i18n/`, `theme`,
|
- общее (разрешено обоим): всё прочее — `core/`, `i18n/`, `theme`,
|
||||||
`assets/`, корневой `lib` (`index.ts`, тесты).
|
`assets/`, корневой `lib` (`index.ts`, тесты). Достигнутая полная изоляция
|
||||||
Достигнутая полная изоляция (одиночные пересечения устранены):
|
(одиночные пересечения устранены):
|
||||||
- старые пилоты `registry/geometry.ts` (`add-border`) и `registry/alpha.ts`
|
- старые пилоты `registry/geometry.ts` (`add-border`) и `registry/alpha.ts`
|
||||||
(`add-stroke`) получали `schema` из нового `registry-schema` — убрано;
|
(`add-stroke`) получали `schema` из нового `registry-schema` — убрано; оба
|
||||||
оба инструмента работают в старом UI через `params: ParamDef[]`,
|
инструмента работают в старом UI через `params: ParamDef[]`, в preview —
|
||||||
в preview — через свои schema-версии в `registry-new/`;
|
через свои schema-версии в `registry-new/`;
|
||||||
- `registry.ts` больше не импортирует `ToolSchema` из `registry-schema`;
|
- `registry.ts` больше не импортирует `ToolSchema` из `registry-schema`;
|
||||||
- preview `list-tools` тянул `TOOL_ICONS` из старого `lib/tools/tool-icons`
|
- preview `list-tools` тянул `TOOL_ICONS` из старого `lib/tools/tool-icons`
|
||||||
→ создана копия `lib/preview/tool-icons.ts` (правило копий).
|
→ создана копия `lib/preview/tool-icons.ts` (правило копий).
|
||||||
@@ -507,10 +505,10 @@ Typed field builders + `Field<T>` + `toolSchema<P>()` + `ToolSchema<P>` —
|
|||||||
### Как ревьюить каждый шаг
|
### Как ревьюить каждый шаг
|
||||||
|
|
||||||
- Diff **только один инструмент** (или фундамент без инструментов).
|
- Diff **только один инструмент** (или фундамент без инструментов).
|
||||||
- Старый `params`/`run`/`defaultParams`/`sanitizeParams` — **не изменены** (кроме
|
- Старый `params`/`run`/`defaultParams`/`sanitizeParams` — **не изменены**
|
||||||
добаление `schema`, если это пилот).
|
(кроме добаление `schema`, если это пилот).
|
||||||
- Старый UI и старые тесты продолжают работать — запустить
|
- Старый UI и старые тесты продолжают работать — запустить `pnpm --dir web test`
|
||||||
`pnpm --dir web test` и `pnpm --dir web exec svelte-check`.
|
и `pnpm --dir web exec svelte-check`.
|
||||||
- Порядок полей в `interface Params` и в `schema` совпадает; дефолты в сeme
|
- Порядок полей в `interface Params` и в `schema` совпадает; дефолты в сeme
|
||||||
равны старым `default` из `params`.
|
равны старым `default` из `params`.
|
||||||
- Пилот из Фазы 1 проверяется **руками в preview**: применяется рамка,
|
- Пилот из Фазы 1 проверяется **руками в preview**: применяется рамка,
|
||||||
@@ -539,8 +537,8 @@ Typed field builders + `Field<T>` + `toolSchema<P>()` + `ToolSchema<P>` —
|
|||||||
на `ParamDef[]` как раньше.
|
на `ParamDef[]` как раньше.
|
||||||
- **Новый UI** (kit/`SchemaToolView` + `SchemaFields` + `SchemaPreview`, читает
|
- **Новый UI** (kit/`SchemaToolView` + `SchemaFields` + `SchemaPreview`, читает
|
||||||
`ToolSchema<P>`) — сейчас рендерит поля по схеме (number/slider/color).
|
`ToolSchema<P>`) — сейчас рендерит поля по схеме (number/slider/color).
|
||||||
- `tool-views.ts` отсутствует — preview/lede/layout уже живут в meta
|
- `tool-views.ts` отсутствует — preview/lede/layout уже живут в meta инструмента
|
||||||
инструмента (`ToolEntry` + `schema.layout`), дублирования нет.
|
(`ToolEntry` + `schema.layout`), дублирования нет.
|
||||||
|
|
||||||
## Оценка трудозатрат
|
## Оценка трудозатрат
|
||||||
|
|
||||||
@@ -557,12 +555,12 @@ Typed field builders + `Field<T>` + `toolSchema<P>()` + `ToolSchema<P>` —
|
|||||||
| **Итого** | | **~22-32ч** (поэтапно) |
|
| **Итого** | | **~22-32ч** (поэтапно) |
|
||||||
|
|
||||||
> Оценка выросла по сравнению с ранней версией плана, потому что принят путь
|
> Оценка выросла по сравнению с ранней версией плана, потому что принят путь
|
||||||
> «явный interface Params + схема + общий рендер с пер-инструмент layout» —
|
> «явный interface Params + схема + общий рендер с пер-инструмент layout» — это
|
||||||
> это полный рефакторинг pipeline, а не только добавление составных типов.
|
> полный рефакторинг pipeline, а не только добавление составных типов.
|
||||||
>
|
>
|
||||||
> Старый UI/`ParamDef[]`/старый pipeline в смету **не входят** — они не
|
> Старый UI/`ParamDef[]`/старый pipeline в смету **не входят** — они не
|
||||||
> рефакторятся, а продолжают работать на `(old)/`-маршрутах до перехода
|
> рефакторятся, а продолжают работать на `(old)/`-маршрутах до перехода (затем
|
||||||
> (затем убираются отдельным решением — см. шаг 35).
|
> убираются отдельным решением — см. шаг 35).
|
||||||
|
|
||||||
## Порядок реализации (кратко)
|
## Порядок реализации (кратко)
|
||||||
|
|
||||||
@@ -571,20 +569,19 @@ Typed field builders + `Field<T>` + `toolSchema<P>()` + `ToolSchema<P>` —
|
|||||||
|
|
||||||
1. **Фаза 0** — фундамент: builders + `toolSchema<P>` + `ToolEntry<P>.schema` +
|
1. **Фаза 0** — фундамент: builders + `toolSchema<P>` + `ToolEntry<P>.schema` +
|
||||||
новые default/sanitize. Ни одного инструмента не изменено. ✔
|
новые default/sanitize. Ни одного инструмента не изменено. ✔
|
||||||
2. **Фаза 1** — **один полноценный рабочий инструмент в preview**
|
2. **Фаза 1** — **один полноценный рабочий инструмент в preview** («добавить
|
||||||
(«добавить рамку»): schema + рендер + end-to-end применение. Ручная оценка
|
рамку»): schema + рендер + end-to-end применение. Ручная оценка
|
||||||
работоспособности решения/сложностей.
|
работоспособности решения/сложностей.
|
||||||
3. **Фаза 2** — простые/одиночные инструменты (проверенный паттерн Фазы 1),
|
3. **Фаза 2** — простые/одиночные инструменты (проверенный паттерн Фазы 1), 1
|
||||||
1 инструмент = 1 диф.
|
инструмент = 1 диф.
|
||||||
4. **Фаза 3** — инструменты с составными типами (dimension → color-pair →
|
4. **Фаза 3** — инструменты с составными типами (dimension → color-pair → offset
|
||||||
offset → position9 → font-style → plate → gradient), пилот каждого типа
|
→ position9 → font-style → plate → gradient), пилот каждого типа отдельно.
|
||||||
отдельно.
|
|
||||||
5. **Фаза 4** — масштаб UI на остальные: составные виджеты, пер-инструмент
|
5. **Фаза 4** — масштаб UI на остальные: составные виджеты, пер-инструмент
|
||||||
layout (для каждого инструмента — UI-макет).
|
layout (для каждого инструмента — UI-макет).
|
||||||
6. **Фаза 5** — изоляция: старый UI на `(old)/`-маршруты (не удаляется,
|
6. **Фаза 5** — изоляция: старый UI на `(old)/`-маршруты (не удаляется, остаётся
|
||||||
остаётся для ревью); preview на `registry-new` полностью;
|
для ревью); preview на `registry-new` полностью; линтер-изоляция веток
|
||||||
линтер-изоляция веток (плагин `isolation`), затем перенос старых
|
(плагин `isolation`), затем перенос старых компонентов/библиотек в папки
|
||||||
компонентов/библиотек в папки `old/`.
|
`old/`.
|
||||||
|
|
||||||
## Зависимости
|
## Зависимости
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
# План: доводка дизайна до верности (фикс по refs)
|
# План: доводка дизайна до верности (фикс по refs)
|
||||||
|
|
||||||
> **СТАТУС: ЗАКРЫТ (2026-09-07).** Актуальный дизайн (`preview/*`) ушёл вперёд
|
> **СТАТУС: ЗАКРЫТ (2026-09-07).** Актуальный дизайн (`preview/*`) ушёл вперёд
|
||||||
> от референсов `refs-html/` — сверка с ними больше не информативна
|
> от референсов `refs-html/` — сверка с ними больше не информативна (новые
|
||||||
> (новые компоненты `SchemaToolView`/`SchemaPreview`/каталог не имеют
|
> компоненты `SchemaToolView`/`SchemaPreview`/каталог не имеют рефовой копии).
|
||||||
> рефовой копии). Аудит против рефа прекращается; качество дизайна дальше
|
> Аудит против рефа прекращается; качество дизайна дальше гоняется ревью руками
|
||||||
> гоняется ревью руками и специализированными проверками
|
> и специализированными проверками (`lint:all` —
|
||||||
> (`lint:all` — stylelint/design-tokens/токен-аудит). Порядок удаления старого
|
> stylelint/design-tokens/токен-аудит). Порядок удаления старого и перехода — по
|
||||||
> и перехода — по `plan-redesign.md` (C17–C21).
|
> `plan-redesign.md` (C17–C21).
|
||||||
|
|
||||||
> Статус: к выполнению. Ветка `feat/redesign`.
|
> Статус: к выполнению. Ветка `feat/redesign`. Источники:
|
||||||
> Источники: `docs/archive/demo-fidelity.md` (архивная копия старого отчёта по
|
> `docs/archive/demo-fidelity.md` (архивная копия старого отчёта по верности,
|
||||||
> верности, неактуальна), `docs/plan-redesign.md` (общая стратегия, фазы 1–4
|
> неактуальна), `docs/plan-redesign.md` (общая стратегия, фазы 1–4 выполнены до
|
||||||
> выполнены до C16). Актуальный аудит верности — `pnpm refs-cdp-audit` /
|
> C16). Актуальный аудит верности — `pnpm refs-cdp-audit` /
|
||||||
> `pnpm refs-css-audit` / `pnpm refs-dom-audit` → `web/audit/audit-report.md`
|
> `pnpm refs-css-audit` / `pnpm refs-dom-audit` → `web/audit/audit-report.md`
|
||||||
> (текущий список скриптов — в `web/package.json`, см. `refs-*audit`).
|
> (текущий список скриптов — в `web/package.json`, см. `refs-*audit`).
|
||||||
|
|
||||||
@@ -22,11 +22,10 @@
|
|||||||
- `refs/` и `refs-html/` приведены к консистентному виду — текущая «правда по
|
- `refs/` и `refs-html/` приведены к консистентному виду — текущая «правда по
|
||||||
пикселям». Механизм синхронизации ref→код не создаётся (см. §9 plan-redesign).
|
пикселям». Механизм синхронизации ref→код не создаётся (см. §9 plan-redesign).
|
||||||
- Аудит верности автоматизирован: `pnpm refs-cdp-audit` (1440×900,
|
- Аудит верности автоматизирован: `pnpm refs-cdp-audit` (1440×900,
|
||||||
`scripts/audit-cdp.mjs`) пишет
|
`scripts/audit-cdp.mjs`) пишет `web/audit/audit-report.md` — light/dark
|
||||||
`web/audit/audit-report.md` — light/dark токены + поэлементный дифф стилей;
|
токены + поэлементный дифф стилей; пиксель-дифф не используется. Палитры
|
||||||
пиксель-дифф не используется. Палитры токенов уже сверены с рефом (light
|
токенов уже сверены с рефом (light совпадает, dark собран). Темы в `refs-html`
|
||||||
совпадает, dark собран). Темы в `refs-html` переключаются — скрипт инжектится
|
переключаются — скрипт инжектится в `scripts/extract-static.mjs`.
|
||||||
в `scripts/extract-static.mjs`.
|
|
||||||
- Исходный отчёт `demo-fidelity.md` перенесён в `docs/archive/` как утративший
|
- Исходный отчёт `demo-fidelity.md` перенесён в `docs/archive/` как утративший
|
||||||
актуальность; его данные актуализированы ниже (Этап 1).
|
актуальность; его данные актуализированы ниже (Этап 1).
|
||||||
- Зафиксированные решения (подтверждены автором):
|
- Зафиксированные решения (подтверждены автором):
|
||||||
@@ -38,13 +37,14 @@
|
|||||||
- Никаких правок старого дизайна `(old)/` и старого `ui/`.
|
- Никаких правок старого дизайна `(old)/` и старого `ui/`.
|
||||||
- Компоненты пишем в `kit/` (переименование в `ui/` — на C19 по plan-redesign).
|
- Компоненты пишем в `kit/` (переименование в `ui/` — на C19 по plan-redesign).
|
||||||
- Коммиты атомарные (< ~500 строк), по 1–2 компонента; после каждого —
|
- Коммиты атомарные (< ~500 строк), по 1–2 компонента; после каждого —
|
||||||
`svelte-check` + `lint` + `build` + `refs-cdp-audit` (diff% не должен расти без причины).
|
`svelte-check` + `lint` + `build` + `refs-cdp-audit` (diff% не должен расти
|
||||||
|
без причины).
|
||||||
- Коммиты делает автор (агент не коммитит).
|
- Коммиты делает автор (агент не коммитит).
|
||||||
|
|
||||||
## Этап 0 — Рефакторинг (SRP / дизайн-обёртки), без изменения визуала
|
## Этап 0 — Рефакторинг (SRP / дизайн-обёртки), без изменения визуала
|
||||||
|
|
||||||
Гигантские страницы (`demo` ~513, `tools/[id]` ~451 строк) дробим на
|
Гигантские страницы (`demo` ~513, `tools/[id]` ~451 строк) дробим на компоненты
|
||||||
компоненты единой ответственности; убираем дублирующиеся inline-стили.
|
единой ответственности; убираем дублирующиеся inline-стили.
|
||||||
|
|
||||||
Новые обёртки в `src/lib/components/kit/`:
|
Новые обёртки в `src/lib/components/kit/`:
|
||||||
|
|
||||||
@@ -62,9 +62,8 @@
|
|||||||
Декомпозиция страниц:
|
Декомпозиция страниц:
|
||||||
|
|
||||||
- `tools/[id]`: вынести `ParamControl` (диспетчер полей по `ParamDef`),
|
- `tools/[id]`: вынести `ParamControl` (диспетчер полей по `ParamDef`),
|
||||||
`SettingsPanel` (левая панель), `PreviewPanel` (правая панель) —
|
`SettingsPanel` (левая панель), `PreviewPanel` (правая панель) — колоцированно
|
||||||
колоцированно в `routes/preview/tools/[id]/`. Страница сжимается до
|
в `routes/preview/tools/[id]/`. Страница сжимается до состояния + глёв.
|
||||||
состояния + глёв.
|
|
||||||
- `demo`: переписать на те же обёртки + `WorkspaceHeader` (eyebrow + h1 + lede +
|
- `demo`: переписать на те же обёртки + `WorkspaceHeader` (eyebrow + h1 + lede +
|
||||||
file-chip).
|
file-chip).
|
||||||
|
|
||||||
@@ -81,10 +80,9 @@
|
|||||||
|
|
||||||
Аудит: `pnpm refs-cdp-audit` (1440×900) пишет `web/audit/audit-report.md` —
|
Аудит: `pnpm refs-cdp-audit` (1440×900) пишет `web/audit/audit-report.md` —
|
||||||
light/dark токены + поэлементный дифф стилей. Пиксель-дифф не используется.
|
light/dark токены + поэлементный дифф стилей. Пиксель-дифф не используется.
|
||||||
Палитры токенов уже сверены с рефом: light совпадает (`--background`,
|
Палитры токенов уже сверены с рефом: light совпадает (`--background`, `--panel`,
|
||||||
`--panel`, `--foreground`, `--muted`, `--line`, `--blue`), dark собран (16
|
`--foreground`, `--muted`, `--line`, `--blue`), dark собран (16 токенов). Темы в
|
||||||
токенов). Темы в `refs-html` переключаются (скрипт инжектится в
|
`refs-html` переключаются (скрипт инжектится в `extract-static.mjs`).
|
||||||
`extract-static.mjs`).
|
|
||||||
|
|
||||||
Открытые / решённые пункты (исходник — `docs/archive/demo-fidelity.md`):
|
Открытые / решённые пункты (исходник — `docs/archive/demo-fidelity.md`):
|
||||||
|
|
||||||
@@ -94,26 +92,26 @@ light/dark токены + поэлементный дифф стилей. Пик
|
|||||||
рендерит ровно один `TopBar`; `demo` свой `.topbar` не рендерит. Подтвердить
|
рендерит ровно один `TopBar`; `demo` свой `.topbar` не рендерит. Подтвердить
|
||||||
аудитом/визуально при Этапе 1.
|
аудитом/визуально при Этапе 1.
|
||||||
- **F1** (решено — плоский фон): наш `--background` уже `#EEF1F4` (как реф);
|
- **F1** (решено — плоский фон): наш `--background` уже `#EEF1F4` (как реф);
|
||||||
видимое отличие было в blueprint-сетке поверх фона. Фикс: убрать `BlueprintGrid`
|
видимое отличие было в blueprint-сетке поверх фона. Фикс: убрать
|
||||||
из `AppShell`.
|
`BlueprintGrid` из `AppShell`.
|
||||||
- **F2** (сделано C16): тип шага (`StepCard`) — приглушённый mono-текст, не
|
- **F2** (сделано C16): тип шага (`StepCard`) — приглушённый mono-текст, не
|
||||||
синий бейдж; индекс шага — синий `#1769D2`.
|
синий бейдж; индекс шага — синий `#1769D2`.
|
||||||
- **F3** (сделано C16): подпись превью-тайла (`PreviewTile`) — синяя `#1769D2`.
|
- **F3** (сделано C16): подпись превью-тайла (`PreviewTile`) — синяя `#1769D2`.
|
||||||
- **F4** (решено — одна колонка): реализуется в `PreviewStack` (Этап 0),
|
- **F4** (решено — одна колонка): реализуется в `PreviewStack` (Этап 0), колонка
|
||||||
колонка = `1fr`.
|
= `1fr`.
|
||||||
- **F5** (открыт): вес `MonoLabel` 500 → 400 (как в рефе).
|
- **F5** (открыт): вес `MonoLabel` 500 → 400 (как в рефе).
|
||||||
- **F6** (открыт): мета-подписи (MetaRow/MetaList, DIMENSIONS/FORMAT/SIZE)
|
- **F6** (открыт): мета-подписи (MetaRow/MetaList, DIMENSIONS/FORMAT/SIZE) fs
|
||||||
fs 10px → 9px.
|
10px → 9px.
|
||||||
- **F7** (низкий): копирайт/стиль — у нас `+ Add tool` (реф `Add tool`);
|
- **F7** (низкий): копирайт/стиль — у нас `+ Add tool` (реф `Add tool`); кнопка
|
||||||
кнопка `Download result` — сверить с рефом (синяя, ~478×42). Проверить
|
`Download result` — сверить с рефом (синяя, ~478×42). Проверить визуально
|
||||||
визуально (замеры NOT FOUND — артефакт вложенного чипа размера).
|
(замеры NOT FOUND — артефакт вложенного чипа размера).
|
||||||
- **F8** (низкий): `h1` line-height чуть выше рефа (150px vs 115px).
|
- **F8** (низкий): `h1` line-height чуть выше рефа (150px vs 115px).
|
||||||
- **F9** (открыт): вертикальный ритм — подровнять паддинги секций под реф
|
- **F9** (открыт): вертикальный ритм — подровнять паддинги секций под реф
|
||||||
(pipeline-head и пр.).
|
(pipeline-head и пр.).
|
||||||
- **F10** (открыт): внутренние отступы preview-панели (паддинг панели).
|
- **F10** (открыт): внутренние отступы preview-панели (паддинг панели).
|
||||||
|
|
||||||
После Этапа 1 — повторный `refs-cdp-audit`, зафиксировать остаточный diff (sub-pixel /
|
После Этапа 1 — повторный `refs-cdp-audit`, зафиксировать остаточный diff
|
||||||
структурный) и обновить статус пунктов выше прямо в этом файле.
|
(sub-pixel / структурный) и обновить статус пунктов выше прямо в этом файле.
|
||||||
|
|
||||||
## Этап 2 — plan-redesign.md (C17–C21), ОТЛОЖЕНО
|
## Этап 2 — plan-redesign.md (C17–C21), ОТЛОЖЕНО
|
||||||
|
|
||||||
|
|||||||
@@ -4,65 +4,96 @@
|
|||||||
|
|
||||||
## 1. Принципы отбора
|
## 1. Принципы отбора
|
||||||
|
|
||||||
- Максимум переиспользования: готовые контракты записей (`run`, `preview`, формат вывода, info-режим) и существующие операции ядра.
|
- Максимум переиспользования: готовые контракты записей (`run`, `preview`,
|
||||||
- Новые механизмы ядра — минимальные, чистые, покрытые тестами до подключения инструментов.
|
формат вывода, info-режим) и существующие операции ядра.
|
||||||
- Canvas/DOM-зависимые утилиты (SVG-вход, надписи и водяные знаки, эффекты через фильтры контекста) — вне этого пакета, отдельная волна.
|
- Новые механизмы ядра — минимальные, чистые, покрытые тестами до подключения
|
||||||
- Целевой объём волны: около 37 новых инструментов, каталог вырастает с 11 до ~48.
|
инструментов.
|
||||||
|
- Canvas/DOM-зависимые утилиты (SVG-вход, надписи и водяные знаки, эффекты через
|
||||||
|
фильтры контекста) — вне этого пакета, отдельная волна.
|
||||||
|
- Целевой объём волны: около 37 новых инструментов, каталог вырастает с 11 до
|
||||||
|
~48.
|
||||||
|
|
||||||
## 2. Новые механизмы
|
## 2. Новые механизмы
|
||||||
|
|
||||||
- **Генераторы без входного файла**: у записи появляется признак «источник не нужен»; страница такого инструмента не показывает левую панель и зону перетаскивания — только параметры и результат.
|
- **Генераторы без входного файла**: у записи появляется признак «источник не
|
||||||
- **Текстовый ввод и вывод**: часть утилит принимает вставленный текст вместо файла и выдаёт текстовый результат (панель с полем только для чтения, кнопки «копировать» и «скачать как текст»). Распространяется на base64, data-uri, hex.
|
нужен»; страница такого инструмента не показывает левую панель и зону
|
||||||
- **BMP-энкодер в ядре**: 24-битный, чистая функция из пикселей в байты, с тестами на маленьких матрицах (заголовок, порядок каналов BGR, паддинг строки).
|
перетаскивания — только параметры и результат.
|
||||||
|
- **Текстовый ввод и вывод**: часть утилит принимает вставленный текст вместо
|
||||||
|
файла и выдаёт текстовый результат (панель с полем только для чтения, кнопки
|
||||||
|
«копировать» и «скачать как текст»). Распространяется на base64, data-uri,
|
||||||
|
hex.
|
||||||
|
- **BMP-энкодер в ядре**: 24-битный, чистая функция из пикселей в байты, с
|
||||||
|
тестами на маленьких матрицах (заголовок, порядок каналов BGR, паддинг
|
||||||
|
строки).
|
||||||
|
|
||||||
## 3. Состав пакета
|
## 3. Состав пакета
|
||||||
|
|
||||||
**Конвертация — 6**
|
**Конвертация — 6** `jpg-to-png`, `webp-to-png`, `gif-to-png` (первый кадр),
|
||||||
`jpg-to-png`, `webp-to-png`, `gif-to-png` (первый кадр), `bmp-to-png`, `ico-to-png` — всё через уже умеющий декодировать ввод, сохранение в PNG; плюс `png-to-bmp` на новом энкодере.
|
`bmp-to-png`, `ico-to-png` — всё через уже умеющий декодировать ввод, сохранение
|
||||||
|
в PNG; плюс `png-to-bmp` на новом энкодере.
|
||||||
|
|
||||||
**Текстовые представления — 6**
|
**Текстовые представления — 6** `png-to-base64`, `base64-to-png`,
|
||||||
`png-to-base64`, `base64-to-png`, `png-to-data-uri`, `data-uri-to-png`, `png-to-hex`, `hex-to-png`.
|
`png-to-data-uri`, `data-uri-to-png`, `png-to-hex`, `hex-to-png`.
|
||||||
|
|
||||||
**Цвет — 9**
|
**Цвет — 9** `change-opacity` (множитель прозрачности), `sepia`, `change-hue`
|
||||||
`change-opacity` (множитель прозрачности), `sepia`, `change-hue` (сдвиг тона), `extract-channel` (выбор R/G/B в оттенках серого), `swap-channels` (перестановка парой), `threshold-black-white` (порог яркости), `posterize` (число уровней), `two-colors` (два цвета + порог), `invert-alpha`.
|
(сдвиг тона), `extract-channel` (выбор R/G/B в оттенках серого), `swap-channels`
|
||||||
|
(перестановка парой), `threshold-black-white` (порог яркости), `posterize`
|
||||||
|
(число уровней), `two-colors` (два цвета + порог), `invert-alpha`.
|
||||||
|
|
||||||
**Прозрачность и холст — 7**
|
**Прозрачность и холст — 7** `remove-alpha-channel` (подложка белым),
|
||||||
`remove-alpha-channel` (подложка белым), `set-alpha-channel` (константная прозрачность), `extract-alpha-mask` (альфа в чёрно-белую маску), `add-padding` (отступ + цвет/прозрачность), `add-border` (толщина + цвет), `round-corners` (радиус), `fit-on-background` (вписать на полотно цвета с полями).
|
`set-alpha-channel` (константная прозрачность), `extract-alpha-mask` (альфа в
|
||||||
|
чёрно-белую маску), `add-padding` (отступ + цвет/прозрачность), `add-border`
|
||||||
|
(толщина + цвет), `round-corners` (радиус), `fit-on-background` (вписать на
|
||||||
|
полотно цвета с полями).
|
||||||
|
|
||||||
**Композиция — 2**
|
**Композиция — 2** `tile` (повтор по сетке), `center-by-alpha` (центрирование по
|
||||||
`tile` (повтор по сетке), `center-by-alpha` (центрирование по непрозрачному содержимому).
|
непрозрачному содержимому).
|
||||||
|
|
||||||
**Генераторы — 4**
|
**Генераторы — 4** `create-empty` (размер + цвет или прозрачность),
|
||||||
`create-empty` (размер + цвет или прозрачность), `single-color`, `random-noise` (с фиксированным зерном для воспроизводимости), `linear-gradient` (два цвета, направление).
|
`single-color`, `random-noise` (с фиксированным зерном для воспроизводимости),
|
||||||
|
`linear-gradient` (два цвета, направление).
|
||||||
|
|
||||||
**Анализ — 3**
|
**Анализ — 3** `is-grayscale`, `is-transparent`, `orientation`
|
||||||
`is-grayscale`, `is-transparent`, `orientation` (портрет/ландшафт/квадрат) — текстовые сводки в info-режиме.
|
(портрет/ландшафт/квадрат) — текстовые сводки в info-режиме.
|
||||||
|
|
||||||
## 4. Новые операции ядра (все — чистые функции с тестами)
|
## 4. Новые операции ядра (все — чистые функции с тестами)
|
||||||
|
|
||||||
- Холст: расширение полотна (общая база для padding/border/fit), тайлинг, центр по bounding-box альфы, скругление углов маской радиуса.
|
- Холст: расширение полотна (общая база для padding/border/fit), тайлинг, центр
|
||||||
- Цвет: сепия, сдвиг тона через HSL, множитель альфы, извлечение/перестановка каналов, пороги (яркость и двухцветный), постеризация уровней, инверсия альфы.
|
по bounding-box альфы, скругление углов маской радиуса.
|
||||||
|
- Цвет: сепия, сдвиг тона через HSL, множитель альфы, извлечение/перестановка
|
||||||
|
каналов, пороги (яркость и двухцветный), постеризация уровней, инверсия альфы.
|
||||||
- Генерация: заливка цветом, шум с зерном, линейный градиент.
|
- Генерация: заливка цветом, шум с зерном, линейный градиент.
|
||||||
- Форматы: BMP-энкодер; кодеки текстовых представлений (base64/data-uri/hex в обе стороны).
|
- Форматы: BMP-энкодер; кодеки текстовых представлений (base64/data-uri/hex в
|
||||||
|
обе стороны).
|
||||||
- Анализ: проверка монохромности, полной непрозрачности, ориентации.
|
- Анализ: проверка монохромности, полной непрозрачности, ориентации.
|
||||||
|
|
||||||
## 5. Этапы и проверки
|
## 5. Этапы и проверки
|
||||||
|
|
||||||
- **A. Механизмы** — генераторный режим страницы, текстовый ввод/вывод, BMP-энкодер. Проверка: `test/check/build`.
|
- **A. Механизмы** — генераторный режим страницы, текстовый ввод/вывод,
|
||||||
- **B. Конвертация и текстовые представления** — 12 инструментов. Проверка: ручной прогон каждой конвертации туда-обратно.
|
BMP-энкодер. Проверка: `test/check/build`.
|
||||||
|
- **B. Конвертация и текстовые представления** — 12 инструментов. Проверка:
|
||||||
|
ручной прогон каждой конвертации туда-обратно.
|
||||||
- **C. Цвет** — 9 инструментов. Проверка: юнит-тесты операций + визуально.
|
- **C. Цвет** — 9 инструментов. Проверка: юнит-тесты операций + визуально.
|
||||||
- **D. Прозрачность, холст, композиция** — 9 инструментов. Проверка: юнит-тесты + визуально.
|
- **D. Прозрачность, холст, композиция** — 9 инструментов. Проверка:
|
||||||
- **E. Генераторы и анализ** — 7 инструментов. Проверка: детерминизм шума (одно зерно — одно изображение), своды анализа.
|
юнит-тесты + визуально.
|
||||||
- **F. Итог** — полный прогон, смоук всех новых инструментов «настроить → скачать», регресс старых одиннадцати.
|
- **E. Генераторы и анализ** — 7 инструментов. Проверка: детерминизм шума (одно
|
||||||
|
зерно — одно изображение), своды анализа.
|
||||||
|
- **F. Итог** — полный прогон, смоук всех новых инструментов «настроить →
|
||||||
|
скачать», регресс старых одиннадцати.
|
||||||
|
|
||||||
## 6. Критерии готовности
|
## 6. Критерии готовности
|
||||||
|
|
||||||
- Каталог содержит ~48 рабочих инструментов, каждый доступен страницей с формы из реестра.
|
- Каталог содержит ~48 рабочих инструментов, каждый доступен страницей с формы
|
||||||
- Все новые операции ядра покрыты тестами; обратимые конвертации проверены круговым прогоном (например PNG → base64 → PNG даёт те же пиксели).
|
из реестра.
|
||||||
- Генераторы работают без загрузки файла; текстовые представления копируются и скачиваются.
|
- Все новые операции ядра покрыты тестами; обратимые конвертации проверены
|
||||||
|
круговым прогоном (например PNG → base64 → PNG даёт те же пиксели).
|
||||||
|
- Генераторы работают без загрузки файла; текстовые представления копируются и
|
||||||
|
скачиваются.
|
||||||
- `test/check/build` зелёные.
|
- `test/check/build` зелёные.
|
||||||
|
|
||||||
## 7. Что сознательно не делаем
|
## 7. Что сознательно не делаем
|
||||||
|
|
||||||
- SVG-вход, рисование текста и водяных знаков, эффекты через фильтры контекста, GIF/APNG-энкодинг, пакетная обработка — последующие волны.
|
- SVG-вход, рисование текста и водяных знаков, эффекты через фильтры контекста,
|
||||||
- Семейства logo/icon/signature/stamp как отдельные страницы — они будут конфигурациями тех же операций после этой волны.
|
GIF/APNG-энкодинг, пакетная обработка — последующие волны.
|
||||||
|
- Семейства logo/icon/signature/stamp как отдельные страницы — они будут
|
||||||
|
конфигурациями тех же операций после этой волны.
|
||||||
|
|||||||
@@ -4,41 +4,69 @@
|
|||||||
|
|
||||||
## 1. Что получается
|
## 1. Что получается
|
||||||
|
|
||||||
Одиннадцать новых инструментов: свободные трансформации формы (наклон, поворот на произвольный угол, зум, сдвиг), светокоррекция (гамма, автоконтраст, температура, тонирование), виньетка, эффект «артефактов сжатия» и вход SVG. Каталог вырастает с 58 до ~69.
|
Одиннадцать новых инструментов: свободные трансформации формы (наклон, поворот
|
||||||
|
на произвольный угол, зум, сдвиг), светокоррекция (гамма, автоконтраст,
|
||||||
|
температура, тонирование), виньетка, эффект «артефактов сжатия» и вход SVG.
|
||||||
|
Каталог вырастает с 58 до ~69.
|
||||||
|
|
||||||
Текстовые инструменты и водяные знаки в волну не входят — им нужен рендер текста на канвасе, это отдельная четвёртая волна.
|
Текстовые инструменты и водяные знаки в волну не входят — им нужен рендер текста
|
||||||
|
на канвасе, это отдельная четвёртая волна.
|
||||||
|
|
||||||
## 2. Новое ядро
|
## 2. Новое ядро
|
||||||
|
|
||||||
- **Аффинные трансформации** (`core/affine.ts`): единая функция обратного отображения — для каждого пикселя результата вычисляется исходная координата матрицей и семплируется билинейно (общий семплер, вынесенный из `resize`). Поверх неё: наклон по осям, поворот на произвольный угол (с расширением холста под новые габариты), зум к центру, сдвиг.
|
- **Аффинные трансформации** (`core/affine.ts`): единая функция обратного
|
||||||
- **Свет и цвет** (`core/color.ts` дополнения): гамма-коррекция через LUT, автоконтраст (растяжка по гистограмме яркости с отсечкой хвостов), температура (множители тёплых/холодных каналов), тонирование (умножение RGB на цвет с сохранением яркостного баланса).
|
отображения — для каждого пикселя результата вычисляется исходная координата
|
||||||
- **Виньетка** (`core/effects.ts`): радиальное затемнение от центра, сила слайдером; чистая математика без canvas.
|
матрицей и семплируется билинейно (общий семплер, вынесенный из `resize`).
|
||||||
- **IO-расширения** (`core/io.ts`): декодирование SVG из текста/файла через blob-URL и `Image`; раундтрип «закодировать JPEG с качеством Q → декодировать обратно» для эффекта артефактов сжатия.
|
Поверх неё: наклон по осям, поворот на произвольный угол (с расширением холста
|
||||||
|
под новые габариты), зум к центру, сдвиг.
|
||||||
|
- **Свет и цвет** (`core/color.ts` дополнения): гамма-коррекция через LUT,
|
||||||
|
автоконтраст (растяжка по гистограмме яркости с отсечкой хвостов), температура
|
||||||
|
(множители тёплых/холодных каналов), тонирование (умножение RGB на цвет с
|
||||||
|
сохранением яркостного баланса).
|
||||||
|
- **Виньетка** (`core/effects.ts`): радиальное затемнение от центра, сила
|
||||||
|
слайдером; чистая математика без canvas.
|
||||||
|
- **IO-расширения** (`core/io.ts`): декодирование SVG из текста/файла через
|
||||||
|
blob-URL и `Image`; раундтрип «закодировать JPEG с качеством Q → декодировать
|
||||||
|
обратно» для эффекта артефактов сжатия.
|
||||||
|
|
||||||
## 3. Инструменты — 11
|
## 3. Инструменты — 11
|
||||||
|
|
||||||
- **Геометрия:** `skew-png` (углы X/Y), `rotate-free-png` (любой угол, холст расширяется), `zoom-png` (масштаб ≥100 % к центру), `shift-png` (сдвиг со смещением содержимого).
|
- **Геометрия:** `skew-png` (углы X/Y), `rotate-free-png` (любой угол, холст
|
||||||
- **Фильтры:** `vignette-png` (сила затемнения), `jpeg-artifacts-png` (имитация пережатия).
|
расширяется), `zoom-png` (масштаб ≥100 % к центру), `shift-png` (сдвиг со
|
||||||
- **Цвет:** `gamma-png` (0.1–3.0), `auto-contrast-png` (без параметров), `temperature-png` (−100 тёплый…+100 холодный или наоборот — подпись честная), `tint-png` (цвет + сила).
|
смещением содержимого).
|
||||||
|
- **Фильтры:** `vignette-png` (сила затемнения), `jpeg-artifacts-png` (имитация
|
||||||
|
пережатия).
|
||||||
|
- **Цвет:** `gamma-png` (0.1–3.0), `auto-contrast-png` (без параметров),
|
||||||
|
`temperature-png` (−100 тёплый…+100 холодный или наоборот — подпись честная),
|
||||||
|
`tint-png` (цвет + сила).
|
||||||
- **Конвертация:** `svg-to-png` (текст SVG слева, как base64-режим).
|
- **Конвертация:** `svg-to-png` (текст SVG слева, как base64-режим).
|
||||||
|
|
||||||
## 4. Этапы и проверки
|
## 4. Этапы и проверки
|
||||||
|
|
||||||
- **A. Ядро** — семплер, аффинный модуль, свет/цвет, эффекты + точные тесты: поворот квадрата на 90° совпадает с существующим `rotate90`, зум ×2 удваивает размеры и центр остаётся центром, гамма 1 — идентичность, автоконтраст растягивает известный диапазон, виньетка симметрична и не трогает центр при нулевой силе. Проверка: `pnpm test/check`.
|
- **A. Ядро** — семплер, аффинный модуль, свет/цвет, эффекты + точные тесты:
|
||||||
- **B. IO** — SVG-декод и JPEG-раундтрип. Проверка: сборка; живой прогон на этапе D.
|
поворот квадрата на 90° совпадает с существующим `rotate90`, зум ×2 удваивает
|
||||||
- **C. Реестр** — одиннадцать записей, иконки, тесты целостности. Проверка: `test/build`, страницы в экспорте.
|
размеры и центр остаётся центром, гамма 1 — идентичность, автоконтраст
|
||||||
|
растягивает известный диапазон, виньетка симметрична и не трогает центр при
|
||||||
|
нулевой силе. Проверка: `pnpm test/check`.
|
||||||
|
- **B. IO** — SVG-декод и JPEG-раундтрип. Проверка: сборка; живой прогон на
|
||||||
|
этапе D.
|
||||||
|
- **C. Реестр** — одиннадцать записей, иконки, тесты целостности. Проверка:
|
||||||
|
`test/build`, страницы в экспорте.
|
||||||
- **D. Итог** — полный прогон, смоук §7.
|
- **D. Итог** — полный прогон, смоук §7.
|
||||||
|
|
||||||
## 5. Смоук-чеклист
|
## 5. Смоук-чеклист
|
||||||
|
|
||||||
1. Наклон X/Y по отдельности и вместе — форма искажается предсказуемо, без чёрных полос при малых углах.
|
1. Наклон X/Y по отдельности и вместе — форма искажается предсказуемо, без
|
||||||
|
чёрных полос при малых углах.
|
||||||
2. Поворот на 30° — холст вырос, углы прозрачны, содержимое повернуто.
|
2. Поворот на 30° — холст вырос, углы прозрачны, содержимое повернуто.
|
||||||
3. Зум 200 % — содержимое увеличено к центру, холст прежнего размера, края обрезаны.
|
3. Зум 200 % — содержимое увеличено к центру, холст прежнего размера, края
|
||||||
|
обрезаны.
|
||||||
4. Сдвиг уводит содержимое за край, противоположный край заполняется фоном.
|
4. Сдвиг уводит содержимое за край, противоположный край заполняется фоном.
|
||||||
5. Гамма 0.5 темнее, 2.0 светлее, 1.0 — без изменений.
|
5. Гамма 0.5 темнее, 2.0 светлее, 1.0 — без изменений.
|
||||||
6. Автоконтраст на тусклом фото вытягивает диапазон.
|
6. Автоконтраст на тусклом фото вытягивает диапазон.
|
||||||
7. Температура ±50 заметно теплеет/холодеет.
|
7. Температура ±50 заметно теплеет/холодеет.
|
||||||
8. Тонирование красным даёт равномерный оттенок, белые области окрашиваются сильнее всего.
|
8. Тонирование красным даёт равномерный оттенок, белые области окрашиваются
|
||||||
|
сильнее всего.
|
||||||
9. Виньетка затемняет только края, центр не трогает.
|
9. Виньетка затемняет только края, центр не трогает.
|
||||||
10. Артефакты: качество 10 даёт видимые квадраты JPEG.
|
10. Артефакты: качество 10 даёт видимые квадраты JPEG.
|
||||||
11. SVG вставленный слева декодируется в результат справа; битый SVG — баннер.
|
11. SVG вставленный слева декодируется в результат справа; битый SVG — баннер.
|
||||||
@@ -46,8 +74,10 @@
|
|||||||
|
|
||||||
## 6. Критерии готовности
|
## 6. Критерии готовности
|
||||||
|
|
||||||
- Одиннадцать инструментов работают и покрыты юнит-тестами ядра (кроме IO-дом-части).
|
- Одиннадцать инструментов работают и покрыты юнит-тестами ядра (кроме
|
||||||
- Аффинные операции опираются на общий билинейный семплер; дублирования сэмплинга нет.
|
IO-дом-части).
|
||||||
|
- Аффинные операции опираются на общий билинейный семплер; дублирования
|
||||||
|
сэмплинга нет.
|
||||||
- Каталог ~69 инструментов; `test/check/build` зелёные, смоук §5 пройден.
|
- Каталог ~69 инструментов; `test/check/build` зелёные, смоук §5 пройден.
|
||||||
|
|
||||||
## 7. Что сознательно не делаем
|
## 7. Что сознательно не делаем
|
||||||
|
|||||||
@@ -1,35 +1,42 @@
|
|||||||
# План: закрытие EASY/MEDIUM-пробелов из сравнения с onlinepngtools
|
# План: закрытие EASY/MEDIUM-пробелов из сравнения с onlinepngtools
|
||||||
|
|
||||||
> **СТАТУС: ВЫПОЛНЕН (2026-09-07) — устарел.** Все волны W1–W9 закрыты.
|
> **СТАТУС: ВЫПОЛНЕН (2026-09-07) — устарел.** Все волны W1–W9 закрыты. Идеи из
|
||||||
> Идеи из раздела «Вне очереди» (region-инструменты, мультифайловый вывод,
|
> раздела «Вне очереди» (region-инструменты, мультифайловый вывод, анимационные,
|
||||||
> анимационные, HARD-серия) перенесены в `backlog.md`.
|
> HARD-серия) перенесены в `backlog.md`.
|
||||||
|
|
||||||
> Статус: черновик на ревью
|
> Статус: черновик на ревью
|
||||||
|
|
||||||
## Принципы
|
## Принципы
|
||||||
|
|
||||||
- Волны собираются вокруг общего математического ядра: одна волна = один движок + N инструментов на нём.
|
- Волны собираются вокруг общего математического ядра: одна волна = один
|
||||||
- Каждая волна оформляется отдельным планом при старте; здесь — очередь и состав.
|
движок + N инструментов на нём.
|
||||||
- Порядок: сначала кластеры, дающие много инструментов малой кровью, затем одиночные эффекты, MEDIUM-пакеты в конце.
|
- Каждая волна оформляется отдельным планом при старте; здесь — очередь и
|
||||||
- Полный список параметров каждого инструмента живёт в карте инструментов (tools-map.md); здесь только состав и ядро.
|
состав.
|
||||||
|
- Порядок: сначала кластеры, дающие много инструментов малой кровью, затем
|
||||||
|
одиночные эффекты, MEDIUM-пакеты в конце.
|
||||||
|
- Полный список параметров каждого инструмента живёт в карте инструментов
|
||||||
|
(tools-map.md); здесь только состав и ядро.
|
||||||
|
|
||||||
## Очередь волн
|
## Очередь волн
|
||||||
|
|
||||||
### W1. Палитры — ВЫПОЛНЕНА (11 инструментов)
|
### W1. Палитры — ВЫПОЛНЕНА (11 инструментов)
|
||||||
|
|
||||||
Ядро: RGB↔HSL + гармонии цветового круга. Вывод: свотч-полотно (генератор).
|
Ядро: RGB↔HSL + гармонии цветового круга. Вывод: свотч-полотно (генератор).
|
||||||
Состав: color-wheel, complementary, monochromatic, analogous, triadic, tetradic, similar-shades, sort-colors.
|
Состав: color-wheel, complementary, monochromatic, analogous, triadic, tetradic,
|
||||||
Плюс утилиты смешения тем же ядром: mix-colors, average-color, blend-two, step-between (+4, итого 12).
|
similar-shades, sort-colors. Плюс утилиты смешения тем же ядром: mix-colors,
|
||||||
|
average-color, blend-two, step-between (+4, итого 12).
|
||||||
|
|
||||||
### W2. Каналы и пространства — ВЫПОЛНЕНА (6 инструментов)
|
### W2. Каналы и пространства — ВЫПОЛНЕНА (6 инструментов)
|
||||||
|
|
||||||
Ядро: матрицы преобразования RGB→(HSL/HSV/HSI/CMYK/YCbCr/LAB) + визуализация выбранного компонента серым или окрашенно.
|
Ядро: матрицы преобразования RGB→(HSL/HSV/HSI/CMYK/YCbCr/LAB) + визуализация
|
||||||
Общий select «компонент» + select «режим отображения».
|
выбранного компонента серым или окрашенно. Общий select «компонент» + select
|
||||||
|
«режим отображения».
|
||||||
|
|
||||||
### W3. Маски по свойствам пикселей — ВЫПОЛНЕНА (7 инструментов)
|
### W3. Маски по свойствам пикселей — ВЫПОЛНЕНА (7 инструментов)
|
||||||
|
|
||||||
Ядро: предикат над пикселем → бинарная маска (с инверсией и подсветкой цветом).
|
Ядро: предикат над пикселем → бинарная маска (с инверсией и подсветкой цветом).
|
||||||
Состав: show-transparent, show-grayscale, show-color, light-mask, dark-mask, unique-color-mask, extract-by-color.
|
Состав: show-transparent, show-grayscale, show-color, light-mask, dark-mask,
|
||||||
|
unique-color-mask, extract-by-color.
|
||||||
|
|
||||||
### W4. Фигурные маски — ВЫПОЛНЕНА (4 инструмента)
|
### W4. Фигурные маски — ВЫПОЛНЕНА (4 инструмента)
|
||||||
|
|
||||||
@@ -39,38 +46,52 @@
|
|||||||
### W5. Геометрия-добивки — ВЫПОЛНЕНА (5 инструментов)
|
### W5. Геометрия-добивки — ВЫПОЛНЕНА (5 инструментов)
|
||||||
|
|
||||||
Ядро: bbox по альфе (trim) переиспользуется тремя инструментами источника.
|
Ядро: bbox по альфе (trim) переиспользуется тремя инструментами источника.
|
||||||
Состав: trim-empty-space (закрывает remove border/space), change-canvas-size, change-aspect-ratio, landscape↔portrait, symmetric-copy.
|
Состав: trim-empty-space (закрывает remove border/space), change-canvas-size,
|
||||||
|
change-aspect-ratio, landscape↔portrait, symmetric-copy.
|
||||||
|
|
||||||
### W6. Эффекты лёгкие — ВЫПОЛНЕНА (6 инструментов; color-blocks покрыт pixelate)
|
### W6. Эффекты лёгкие — ВЫПОЛНЕНА (6 инструментов; color-blocks покрыт pixelate)
|
||||||
|
|
||||||
Состав: pixelate, color-blocks, randomize-pixels (seed), add-noise, feather-edges, clean-edges, silhouette.
|
Состав: pixelate, color-blocks, randomize-pixels (seed), add-noise,
|
||||||
Shadow/glow — сюда же, если потянет этап: оба = размытая альфа + смещение + цвет (ядро blur уже есть).
|
feather-edges, clean-edges, silhouette. Shadow/glow — сюда же, если потянет
|
||||||
|
этап: оба = размытая альфа + смещение + цвет (ядро blur уже есть).
|
||||||
|
|
||||||
### W7. Конвертеры и генераторы — ВЫПОЛНЕНА (11 инструментов)
|
### W7. Конвертеры и генераторы — ВЫПОЛНЕНА (11 инструментов)
|
||||||
|
|
||||||
PNG↔bytes, PNG↔rgb-values (текстовые результаты), verify-is-png (анализ сигнатуры),
|
PNG↔bytes, PNG↔rgb-values (текстовые результаты), verify-is-png (анализ
|
||||||
text-to-png (domText без входной картинки), emoji-to-png, placeholder-png,
|
сигнатуры), text-to-png (domText без входной картинки), emoji-to-png,
|
||||||
color-spectrum, colorful-random (seed), draw-grid.
|
placeholder-png, color-spectrum, colorful-random (seed), draw-grid.
|
||||||
multi-color-gradient — если успеем новый тип параметра «список цветов», иначе перенос.
|
multi-color-gradient — если успеем новый тип параметра «список цветов», иначе
|
||||||
|
перенос.
|
||||||
|
|
||||||
### W8. Цветовые MEDIUM — ВЫПОЛНЕНА (4 инструмента; median-cut + Floyd–Steinberg/Bayer, custom-palette через text-параметр)
|
### W8. Цветовые MEDIUM — ВЫПОЛНЕНА (4 инструмента; median-cut + Floyd–Steinberg/Bayer, custom-palette через text-параметр)
|
||||||
|
|
||||||
Ядро: квантование (median-cut или k-means).
|
Ядро: квантование (median-cut или k-means). Состав: quantize (k),
|
||||||
Состав: quantize (k), decrease-color-count (=quantize с пресетами), custom-palette (маппинг на список цветов — нужен тип параметра «список»), dithering (Floyd–Steinberg/Bayer поверх квантования).
|
decrease-color-count (=quantize с пресетами), custom-palette (маппинг на список
|
||||||
|
цветов — нужен тип параметра «список»), dithering (Floyd–Steinberg/Bayer поверх
|
||||||
|
квантования).
|
||||||
|
|
||||||
### W9. Сжатие — ВЫПОЛНЕНА (3 инструмента; optimize/change-quality осознанно не выделены — покрыты пресетами compress и W8)
|
### W9. Сжатие — ВЫПОЛНЕНА (3 инструмента; optimize/change-quality осознанно не выделены — покрыты пресетами compress и W8)
|
||||||
|
|
||||||
Без wasm честное управление размером PNG ограничено: реальный рычаг — квантование (W8) + обрезка метаданных + итеративный подбор под целевой KB.
|
Без wasm честное управление размером PNG ограничено: реальный рычаг —
|
||||||
Состав: compress (пресеты усилий), reduce-to-size (целевой KB, бинарный поиск по k), optimize (re-encode), change-quality/low-quality — переосмыслить как пресеты W8; jpeg-artifacts уже покрывает «испортить».
|
квантование (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 выделения области на превью; отдельное решение.
|
- Region-инструменты
|
||||||
- Мультифайловый вывод (split-parts, gif-frames, separate-colors) — ждут механизм «результат = набор файлов».
|
(censor/erase/pixelate-area/blur-area/sharpen-area/reverse-area) — ждут UI
|
||||||
- Анимационные (slow-reveal/fade/scrolling) — выход не PNG; отдельное решение о формате.
|
выделения области на превью; отдельное решение.
|
||||||
|
- Мультифайловый вывод (split-parts, gif-frames, separate-colors) — ждут
|
||||||
|
механизм «результат = набор файлов».
|
||||||
|
- Анимационные (slow-reveal/fade/scrolling) — выход не PNG; отдельное решение о
|
||||||
|
формате.
|
||||||
- HARD: glitch, barcode, signature-extract, handwritten-digital.
|
- HARD: glitch, barcode, signature-extract, handwritten-digital.
|
||||||
- Нишевые серии logo/icon/stamp/signature — не копируем.
|
- Нишевые серии logo/icon/stamp/signature — не копируем.
|
||||||
|
|
||||||
## Оценка масштаба
|
## Оценка масштаба
|
||||||
|
|
||||||
W1–W7 дают **~47 новых инструментов**, почти все EASY. W8 добавляет ядро квантования (открывает custom-palette/dithering и улучшает W9). Суммарно каталог достигает ~120+ при текущих 71.
|
W1–W7 дают **~47 новых инструментов**, почти все EASY. W8 добавляет ядро
|
||||||
|
квантования (открывает custom-palette/dithering и улучшает W9). Суммарно каталог
|
||||||
|
достигает ~120+ при текущих 71.
|
||||||
|
|||||||
@@ -4,43 +4,65 @@
|
|||||||
|
|
||||||
## 1. Что получается
|
## 1. Что получается
|
||||||
|
|
||||||
Выдача поиска на главной превращается из списка строк в сетку карточек: слева иконка инструмента, справа название и описание. Инструменты получают два новых поля реестра — `popularity` (приоритет в выдаче) и `icon` (идентификатор иконки). Иконки переиспользуются в каталоге `/list-tools` и в легендах шагов цепочки. Поиск работает по-прежнему: нечёткий скоринг, клавиатура ↑↓ Enter Esc.
|
Выдача поиска на главной превращается из списка строк в сетку карточек: слева
|
||||||
|
иконка инструмента, справа название и описание. Инструменты получают два новых
|
||||||
|
поля реестра — `popularity` (приоритет в выдаче) и `icon` (идентификатор
|
||||||
|
иконки). Иконки переиспользуются в каталоге `/list-tools` и в легендах шагов
|
||||||
|
цепочки. Поиск работает по-прежнему: нечёткий скоринг, клавиатура ↑↓ Enter Esc.
|
||||||
|
|
||||||
## 2. Данные реестра
|
## 2. Данные реестра
|
||||||
|
|
||||||
- **`popularity?: number`** (0–100, по умолчанию 50) — приоритет внутри равного скоринга запроса. Значения проставляются вручную осознанно: базовые операции (обрезка, размер, удаление фона, конвертации JPG/PNG) — 80–95; цветовые и геометрия — 40–70; узкие и генераторы — 10–30.
|
- **`popularity?: number`** (0–100, по умолчанию 50) — приоритет внутри равного
|
||||||
- **`icon?: string`** — имя иконки из набора Lucide. Явный словарь-маппинг для всех инструментов в одном месте; отсутствие записи даёт нейтральную заглушку (Wrench).
|
скоринга запроса. Значения проставляются вручную осознанно: базовые операции
|
||||||
- Тест целостности расширяется: популярность в диапазоне, иконка — непустая строка из разрешённого набора.
|
(обрезка, размер, удаление фона, конвертации JPG/PNG) — 80–95; цветовые и
|
||||||
|
геометрия — 40–70; узкие и генераторы — 10–30.
|
||||||
|
- **`icon?: string`** — имя иконки из набора Lucide. Явный словарь-маппинг для
|
||||||
|
всех инструментов в одном месте; отсутствие записи даёт нейтральную заглушку
|
||||||
|
(Wrench).
|
||||||
|
- Тест целостности расширяется: популярность в диапазоне, иконка — непустая
|
||||||
|
строка из разрешённого набора.
|
||||||
|
|
||||||
## 3. Иконки
|
## 3. Иконки
|
||||||
|
|
||||||
Зависимость `lucide-svelte` (MIT) — один пакет, иконки рендерятся компонентами, дерево не раздувается за счёт выборочного импорта.
|
Зависимость `lucide-svelte` (MIT) — один пакет, иконки рендерятся компонентами,
|
||||||
|
дерево не раздувается за счёт выборочного импорта.
|
||||||
|
|
||||||
Маппинг ведётся одним объектом `tool-icons.ts` рядом с реестром: 51 запись вида `'crop-png': Crop`. Подбор по смыслу: обрезка — Crop, размер — Scaling, поворот — RotateCw, отражение — FlipHorizontal2, оттенок — Palette, размытие — Droplets, резкость — Focus, удаление фона — Scissors, анализ — Info и так далее; для семейств конвертаций — FileInput/FileOutput варианты.
|
Маппинг ведётся одним объектом `tool-icons.ts` рядом с реестром: 51 запись вида
|
||||||
|
`'crop-png': Crop`. Подбор по смыслу: обрезка — Crop, размер — Scaling, поворот
|
||||||
|
— RotateCw, отражение — FlipHorizontal2, оттенок — Palette, размытие — Droplets,
|
||||||
|
резкость — Focus, удаление фона — Scissors, анализ — Info и так далее; для
|
||||||
|
семейств конвертаций — FileInput/FileOutput варианты.
|
||||||
|
|
||||||
Иконка появляется в трёх местах: карточка выдачи, карточка каталога `/list-tools`, легенда шага цепочки («Шаг N: [icon] Название»).
|
Иконка появляется в трёх местах: карточка выдачи, карточка каталога
|
||||||
|
`/list-tools`, легенда шага цепочки («Шаг N: [icon] Название»).
|
||||||
|
|
||||||
## 4. Карточки выдачи
|
## 4. Карточки выдачи
|
||||||
|
|
||||||
- Сетка: одна колонка на узком экране, две — начиная со среднего героя.
|
- Сетка: одна колонка на узком экране, две — начиная со среднего героя.
|
||||||
- Композиция карточки: квадрат иконки слева, справа название (полужирное) и однострочное описание с обрезкой.
|
- Композиция карточки: квадрат иконки слева, справа название (полужирное) и
|
||||||
|
однострочное описание с обрезкой.
|
||||||
- Состояния hover и активной с клавиатуры — тот же акцент подсветки, что сейчас.
|
- Состояния hover и активной с клавиатуры — тот же акцент подсветки, что сейчас.
|
||||||
- Сортировка выдачи: скоринг запроса ↓, затем популярность ↓, затем название.
|
- Сортировка выдачи: скоринг запроса ↓, затем популярность ↓, затем название.
|
||||||
|
|
||||||
## 5. Этапы и проверки
|
## 5. Этапы и проверки
|
||||||
|
|
||||||
- **A. Реестр** — поля `popularity` и `icon`, словарь иконок на все 51, расширение тестов целостности. Проверка: `pnpm test/check`.
|
- **A. Реестр** — поля `popularity` и `icon`, словарь иконок на все 51,
|
||||||
- **B. Карточки поиска** — сетка выдачи с иконками и учётом популярности, клавиатура без изменений. Проверка: визуально, `check/build`.
|
расширение тестов целостности. Проверка: `pnpm test/check`.
|
||||||
- **C. Распространение** — иконки в каталоге `/list-tools` и легендах звеньев. Проверка: визуально, `build`.
|
- **B. Карточки поиска** — сетка выдачи с иконками и учётом популярности,
|
||||||
|
клавиатура без изменений. Проверка: визуально, `check/build`.
|
||||||
|
- **C. Распространение** — иконки в каталоге `/list-tools` и легендах звеньев.
|
||||||
|
Проверка: визуально, `build`.
|
||||||
- **D. Итог** — полный прогон и смоук §8.
|
- **D. Итог** — полный прогон и смоук §8.
|
||||||
|
|
||||||
## 6. Смоук-чеклист
|
## 6. Смоук-чеклист
|
||||||
|
|
||||||
1. Пустой запрос на герое показывает популярные первыми после первого символа.
|
1. Пустой запрос на герое показывает популярные первыми после первого символа.
|
||||||
2. Запрос «обрез» поднимает карточку обрезки выше прочих совпадений.
|
2. Запрос «обрез» поднимает карточку обрезки выше прочих совпадений.
|
||||||
3. У каждой карточки есть иконка; у большинства — разные, соответствующие смыслу.
|
3. У каждой карточки есть иконка; у большинства — разные, соответствующие
|
||||||
|
смыслу.
|
||||||
4. Наведение и стрелки подсвечивают карточку; Enter открывает инструмент.
|
4. Наведение и стрелки подсвечивают карточку; Enter открывает инструмент.
|
||||||
5. Открытый инструмент работает как раньше: параметры в колонках, результат, скачивание.
|
5. Открытый инструмент работает как раньше: параметры в колонках, результат,
|
||||||
|
скачивание.
|
||||||
6. Цепочка: легенда каждого шага показывает иконку выбранного инструмента.
|
6. Цепочка: легенда каждого шага показывает иконку выбранного инструмента.
|
||||||
7. `/list-tools`: карточки категорий с иконками, ссылки работают.
|
7. `/list-tools`: карточки категорий с иконками, ссылки работают.
|
||||||
8. Регресс: старые сценарии (загрузка файла, цепочка, скачивание) не изменились.
|
8. Регресс: старые сценарии (загрузка файла, цепочка, скачивание) не изменились.
|
||||||
@@ -54,7 +76,8 @@
|
|||||||
|
|
||||||
## 8. Что сознательно не делаем
|
## 8. Что сознательно не делаем
|
||||||
|
|
||||||
- Автоматический расчёт популярности из статистики использования — значения ручные.
|
- Автоматический расчёт популярности из статистики использования — значения
|
||||||
|
ручные.
|
||||||
- Собственный набор SVG-иконок — берём готовый открытый.
|
- Собственный набор SVG-иконок — берём готовый открытый.
|
||||||
- Переводы названий и поиск на других языках — отдельная i18n-волна.
|
- Переводы названий и поиск на других языках — отдельная i18n-волна.
|
||||||
- Изображения-превью в карточках поиска — только иконка и текст.
|
- Изображения-превью в карточках поиска — только иконка и текст.
|
||||||
|
|||||||
+76
-22
@@ -1,52 +1,106 @@
|
|||||||
# План: i18n — русский и английский
|
# План: i18n — русский и английский
|
||||||
|
|
||||||
> **СТАТУС: ВЫПОЛНЕН 25.08.2026.** Базовая локаль — английский (реестр), русский — перевод в словаре; строки инструментов в реестре переведены на английский в рамках этапа C.
|
> **СТАТУС: ВЫПОЛНЕН 25.08.2026.** Базовая локаль — английский (реестр), русский
|
||||||
|
> — перевод в словаре; строки инструментов в реестре переведены на английский в
|
||||||
|
> рамках этапа C.
|
||||||
|
|
||||||
## 1. Что получается
|
## 1. Что получается
|
||||||
|
|
||||||
Сайт работает на двух языках: русский (базовый) и английский. Переключатель в шапке, выбор сохраняется в localStorage, тег `html lang` обновляется на клиенте. Переведено всё: каркас интерфейса, карточки инструментов, цепочки, поиск, тексты ошибок. Поиск находит инструменты по запросу на любом из двух языков независимо от активной локали.
|
Сайт работает на двух языках: русский (базовый) и английский. Переключатель в
|
||||||
|
шапке, выбор сохраняется в localStorage, тег `html lang` обновляется на клиенте.
|
||||||
|
Переведено всё: каркас интерфейса, карточки инструментов, цепочки, поиск, тексты
|
||||||
|
ошибок. Поиск находит инструменты по запросу на любом из двух языков независимо
|
||||||
|
от активной локали.
|
||||||
|
|
||||||
Каталог и поведение не меняются: ни новых инструментов, ни изменений логики существующих.
|
Каталог и поведение не меняются: ни новых инструментов, ни изменений логики
|
||||||
|
существующих.
|
||||||
|
|
||||||
## 2. Решения
|
## 2. Решения
|
||||||
|
|
||||||
- Без сегментов языка в URL. Статика пререндерится на русском; переключение на клиенте, выбор живёт в localStorage. hreflang/canonical и вторая копия страниц для поисковиков — не в этой волне.
|
- Без сегментов языка в URL. Статика пререндерится на русском; переключение на
|
||||||
- Ядро не знает о локалях. Ошибки в core выбрасываются стабильными кодами (например ERR_BAD_HEX); перевод кода в человекочитаемый текст происходит в слое исполнителя/UI.
|
клиенте, выбор живёт в localStorage. hreflang/canonical и вторая копия страниц
|
||||||
- Английский — базовая локаль и фолбэк: строки инструментов в реестре записаны по-английски. Переводы живут в секции tools словарей по ключу id инструмента (title, description, подписи параметров, подписи опций select, тексты-результаты анализаторов); сейчас перевод — русский в ru.ts. Доступ из компонентов только через хелперы toolTitle/toolDescription/paramLabel/optionLabel. Так каждая строка имеет единственный источник: en — реестр, переводы — словари.
|
для поисковиков — не в этой волне.
|
||||||
|
- Ядро не знает о локалях. Ошибки в core выбрасываются стабильными кодами
|
||||||
|
(например ERR_BAD_HEX); перевод кода в человекочитаемый текст происходит в
|
||||||
|
слое исполнителя/UI.
|
||||||
|
- Английский — базовая локаль и фолбэк: строки инструментов в реестре записаны
|
||||||
|
по-английски. Переводы живут в секции tools словарей по ключу id инструмента
|
||||||
|
(title, description, подписи параметров, подписи опций select,
|
||||||
|
тексты-результаты анализаторов); сейчас перевод — русский в ru.ts. Доступ из
|
||||||
|
компонентов только через хелперы
|
||||||
|
toolTitle/toolDescription/paramLabel/optionLabel. Так каждая строка имеет
|
||||||
|
единственный источник: en — реестр, переводы — словари.
|
||||||
|
|
||||||
## 3. Структура i18n-модуля
|
## 3. Структура i18n-модуля
|
||||||
|
|
||||||
- web/src/lib/i18n/locale.svelte.ts — состояние активной локали (runes), персист в localStorage (ключ locale, guard на SSR), обновление document.documentElement.lang.
|
- web/src/lib/i18n/locale.svelte.ts — состояние активной локали (runes), персист
|
||||||
- web/src/lib/i18n/t.ts — функция перевода с фолбэком на базовый язык и интерполяцией переменных вида {n}.
|
в localStorage (ключ locale, guard на SSR), обновление
|
||||||
- web/src/lib/i18n/ru.ts и en.ts — словари одного типа Dict; тип экспортируется из ru.ts, en.ts обязан ему соответствовать — пропущенные ключи ловятся на компиляции.
|
document.documentElement.lang.
|
||||||
- Секции словаря: header, home, catalog, toolPage, chain, sourceCard, resultCard, paramsCard, textInput, textResult, download, infoPanel, dropZone, search, ui (aria кнопок слайдера и пипетки), categories, errors, tools.
|
- web/src/lib/i18n/t.ts — функция перевода с фолбэком на базовый язык и
|
||||||
|
интерполяцией переменных вида {n}.
|
||||||
|
- web/src/lib/i18n/ru.ts и en.ts — словари одного типа Dict; тип экспортируется
|
||||||
|
из ru.ts, en.ts обязан ему соответствовать — пропущенные ключи ловятся на
|
||||||
|
компиляции.
|
||||||
|
- Секции словаря: header, home, catalog, toolPage, chain, sourceCard,
|
||||||
|
resultCard, paramsCard, textInput, textResult, download, infoPanel, dropZone,
|
||||||
|
search, ui (aria кнопок слайдера и пипетки), categories, errors, tools.
|
||||||
|
|
||||||
## 4. Этапы и проверки
|
## 4. Этапы и проверки
|
||||||
|
|
||||||
Каждый этап заканчивается зелёными test/check/build и ревью.
|
Каждый этап заканчивается зелёными test/check/build и ревью.
|
||||||
|
|
||||||
- A. Инфраструктура — модуль состояния, функция перевода с фолбэком и интерполяцией, словари-скелеты со всеми секциями (заполнены header и categories как пилот), переключатель RU/EN в шапке, реактивный lang. Тесты: фолбэк на ru при отсутствии ключа в en, интерполяция, персист локали.
|
- A. Инфраструктура — модуль состояния, функция перевода с фолбэком и
|
||||||
- B. Каркас интерфейса — перенос всех хардкод-строк компонентов и маршрутов в словари: layout (шапка, футер), главная (герой, «вернуть последний», title), каталог, карточки источника/результата/параметров, текстовый вход и результат, «Показать маску», DropZone/DropOverlay, кнопка скачивания (busyText), InfoPanel плюс форматирование чисел через тег локали вместо жёсткого ru-RU, ToolPage (легенды, «Шаг n», ошибки шага), ChainToolBlock, placeholder и пустой результат поиска, страницы инструмента (title, 404). Обёрточные сообщения исполнителя («Ошибка исполнения в воркере») — тоже здесь.
|
интерполяцией, словари-скелеты со всеми секциями (заполнены header и
|
||||||
- C. Контент реестра — переводы 69 инструментов (title/description/labels/options) в tools-секции словаря en; компоненты получают строки через хелперы toolTitle/toolDescription/paramLabel/optionLabel с фолбэком на реестр. Тест целостности: у каждого инструмента есть непустые перевод title/description, полный набор param-ключей и подписи всех опций select; лишние ключи словарей отсутствуют.
|
categories как пилот), переключатель RU/EN в шапке, реактивный lang. Тесты:
|
||||||
- D. Поиск по любому языку — нормализация запроса и полей: нижний регистр, ё→е, снятие диакритики через NFD; скоринг против активной и базовой локали сразу, лучший балл побеждает; сортировка совпадений через Intl.Collator активной локали. Тесты нормализации: ё/е, диакритика, английский запрос при русской локали и наоборот.
|
фолбэк на ru при отсутствии ключа в en, интерполяция, персист локали.
|
||||||
- E. Ошибки на кодах — около 40 мест throw в core, registry, pipeline заменяются на коды; исполнитель переводит код в текст активной локали, неизвестные сообщения показываются как есть. Пять тестов, матчащих русские подстроки ошибок (executor, pipeline, registry, io, alpha), переходят на коды — это упрощает ассерты. Русские describe/it в тестах не трогаем.
|
- B. Каркас интерфейса — перенос всех хардкод-строк компонентов и маршрутов в
|
||||||
|
словари: layout (шапка, футер), главная (герой, «вернуть последний», title),
|
||||||
|
каталог, карточки источника/результата/параметров, текстовый вход и результат,
|
||||||
|
«Показать маску», DropZone/DropOverlay, кнопка скачивания (busyText),
|
||||||
|
InfoPanel плюс форматирование чисел через тег локали вместо жёсткого ru-RU,
|
||||||
|
ToolPage (легенды, «Шаг n», ошибки шага), ChainToolBlock, placeholder и пустой
|
||||||
|
результат поиска, страницы инструмента (title, 404). Обёрточные сообщения
|
||||||
|
исполнителя («Ошибка исполнения в воркере») — тоже здесь.
|
||||||
|
- C. Контент реестра — переводы 69 инструментов
|
||||||
|
(title/description/labels/options) в tools-секции словаря en; компоненты
|
||||||
|
получают строки через хелперы toolTitle/toolDescription/paramLabel/optionLabel
|
||||||
|
с фолбэком на реестр. Тест целостности: у каждого инструмента есть непустые
|
||||||
|
перевод title/description, полный набор param-ключей и подписи всех опций
|
||||||
|
select; лишние ключи словарей отсутствуют.
|
||||||
|
- D. Поиск по любому языку — нормализация запроса и полей: нижний регистр, ё→е,
|
||||||
|
снятие диакритики через NFD; скоринг против активной и базовой локали сразу,
|
||||||
|
лучший балл побеждает; сортировка совпадений через Intl.Collator активной
|
||||||
|
локали. Тесты нормализации: ё/е, диакритика, английский запрос при русской
|
||||||
|
локали и наоборот.
|
||||||
|
- E. Ошибки на кодах — около 40 мест throw в core, registry, pipeline заменяются
|
||||||
|
на коды; исполнитель переводит код в текст активной локали, неизвестные
|
||||||
|
сообщения показываются как есть. Пять тестов, матчащих русские подстроки
|
||||||
|
ошибок (executor, pipeline, registry, io, alpha), переходят на коды — это
|
||||||
|
упрощает ассерты. Русские describe/it в тестах не трогаем.
|
||||||
- F. Итог — полный прогон, смоук чеклист §6, архивация плана.
|
- F. Итог — полный прогон, смоук чеклист §6, архивация плана.
|
||||||
|
|
||||||
## 5. Смоук-чеклист
|
## 5. Смоук-чеклист
|
||||||
|
|
||||||
1. Переключение RU/EN в шапке мгновенно меняет язык без перезагрузки; после перезагрузки выбор сохранён.
|
1. Переключение RU/EN в шапке мгновенно меняет язык без перезагрузки; после
|
||||||
|
перезагрузки выбор сохранён.
|
||||||
2. Тег html lang соответствует активной локали.
|
2. Тег html lang соответствует активной локали.
|
||||||
3. Главная, каталог, страница инструмента, цепочка из трёх звеньев — везде переведено, без смеси языков.
|
3. Главная, каталог, страница инструмента, цепочка из трёх звеньев — везде
|
||||||
4. Форма параметров: подписи полей и опции select переведены у произвольных пяти инструментов из разных категорий.
|
переведено, без смеси языков.
|
||||||
5. Битый вход (некорректный HEX в тонировании, нулевой размер) показывает ошибку на активном языке.
|
4. Форма параметров: подписи полей и опции select переведены у произвольных пяти
|
||||||
6. Поиск: «повер» находит повороты при русской локали; «rotate» находит их же; при английской локали наоборот. Диакритика и ё не мешают совпадению.
|
инструментов из разных категорий.
|
||||||
|
5. Битый вход (некорректный HEX в тонировании, нулевой размер) показывает ошибку
|
||||||
|
на активном языке.
|
||||||
|
6. Поиск: «повер» находит повороты при русской локали; «rotate» находит их же;
|
||||||
|
при английской локали наоборот. Диакритика и ё не мешают совпадению.
|
||||||
7. Числа в панели информации отформатированы по локали.
|
7. Числа в панели информации отформатированы по локали.
|
||||||
8. Скачивание, пипетка, маски, слайдеры работают одинаково на обеих локалях — функциональность не задета.
|
8. Скачивание, пипетка, маски, слайдеры работают одинаково на обеих локалях —
|
||||||
|
функциональность не задета.
|
||||||
|
|
||||||
## 6. Критерии готовности
|
## 6. Критерии готовности
|
||||||
|
|
||||||
- Оба словаря типобезопасны и полны; тест целостности секции tools зелёный.
|
- Оба словаря типобезопасны и полны; тест целостности секции tools зелёный.
|
||||||
- Ни одного пользовательского русского строки вне словарей (кроме тестовых describe/it).
|
- Ни одного пользовательского русского строки вне словарей (кроме тестовых
|
||||||
|
describe/it).
|
||||||
- test/check/build зелёные, смоук пройден.
|
- test/check/build зелёные, смоук пройден.
|
||||||
|
|
||||||
## 7. Что сознательно не делаем
|
## 7. Что сознательно не делаем
|
||||||
|
|||||||
@@ -9,14 +9,26 @@
|
|||||||
|
|
||||||
## 1. Что получается
|
## 1. Что получается
|
||||||
|
|
||||||
Альтернативная раскладка этапа инструмента: три колонки «Исходник | Параметры | Результат» на широких экранах, вертикальный стек на узких. Цель — отдать вариант дизайнеру как отправную точку. Откат к текущей раскладке — замена одной строки в `ToolPage`.
|
Альтернативная раскладка этапа инструмента: три колонки «Исходник | Параметры |
|
||||||
|
Результат» на широких экранах, вертикальный стек на узких. Цель — отдать вариант
|
||||||
|
дизайнеру как отправную точку. Откат к текущей раскладке — замена одной строки в
|
||||||
|
`ToolPage`.
|
||||||
|
|
||||||
## 2. Решения
|
## 2. Решения
|
||||||
|
|
||||||
- Два взаимозаменяемых компонента этапа с одинаковым набором пропсов; `ToolPage` рендерит один тег. Попробовать новое — поменять компонент в этом теге; вернуть старое — поменять обратно. Никаких флагов и конфигов.
|
- Два взаимозаменяемых компонента этапа с одинаковым набором пропсов; `ToolPage`
|
||||||
- Общая типизация пропсов в одном файле рядом с компонентами; сами карточки (`SourceCard`, `ResultCard`, `TextInputCard`, `ParamsCard`) переиспользуются как есть — вся логика исполнения, маски, пипетки и автозапуска остаётся в `ToolPage` и приходит сверху.
|
рендерит один тег. Попробовать новое — поменять компонент в этом теге; вернуть
|
||||||
- Chain-звенья используют ту же панельную систему «Вход | Параметры | Результат» — общие классы `.pane` / `.pane-legend` / `.pane-params` вынесены в app.css, вертикальные поля в узкой колонке заданы один раз глобально.
|
старое — поменять обратно. Никаких флагов и конфигов.
|
||||||
- Один порог отзывчивости: три колонки от ~75rem, ниже — стек «исходник → параметры → результат». Промежуточные перестроения не придумываем до фидбека дизайнера.
|
- Общая типизация пропсов в одном файле рядом с компонентами; сами карточки
|
||||||
|
(`SourceCard`, `ResultCard`, `TextInputCard`, `ParamsCard`) переиспользуются
|
||||||
|
как есть — вся логика исполнения, маски, пипетки и автозапуска остаётся в
|
||||||
|
`ToolPage` и приходит сверху.
|
||||||
|
- Chain-звенья используют ту же панельную систему «Вход | Параметры | Результат»
|
||||||
|
— общие классы `.pane` / `.pane-legend` / `.pane-params` вынесены в app.css,
|
||||||
|
вертикальные поля в узкой колонке заданы один раз глобально.
|
||||||
|
- Один порог отзывчивости: три колонки от ~75rem, ниже — стек «исходник →
|
||||||
|
параметры → результат». Промежуточные перестроения не придумываем до фидбека
|
||||||
|
дизайнера.
|
||||||
|
|
||||||
## 3. Компоненты
|
## 3. Компоненты
|
||||||
|
|
||||||
@@ -27,28 +39,45 @@ src/lib/components/stage/
|
|||||||
ToolStageInline.svelte # новая сетка [source | params | result]
|
ToolStageInline.svelte # новая сетка [source | params | result]
|
||||||
```
|
```
|
||||||
|
|
||||||
- `ToolStageClassic` — вырезка существующего шаблона со стилями; после извлечения страница должна быть пиксель-в-пиксель прежней.
|
- `ToolStageClassic` — вырезка существующего шаблона со стилями; после
|
||||||
- `ToolStageInline` — grid-колонки `minmax(0, 1fr) | фикс-ширина параметров | minmax(0, 1fr)`; легенда «Параметры» над средней колонкой в том же стиле edge-legend.
|
извлечения страница должна быть пиксель-в-пиксель прежней.
|
||||||
- Модификаторы состояний через классы на корне компонента: `no-params` (инструменты без параметров → две колонки), `no-source` (генераторы → параметры и результат), текстовый вход до декодирования занимает левую ячейку как сейчас.
|
- `ToolStageInline` — grid-колонки
|
||||||
|
`minmax(0, 1fr) | фикс-ширина параметров | minmax(0, 1fr)`; легенда
|
||||||
|
«Параметры» над средней колонкой в том же стиле edge-legend.
|
||||||
|
- Модификаторы состояний через классы на корне компонента: `no-params`
|
||||||
|
(инструменты без параметров → две колонки), `no-source` (генераторы →
|
||||||
|
параметры и результат), текстовый вход до декодирования занимает левую ячейку
|
||||||
|
как сейчас.
|
||||||
|
|
||||||
## 4. Этапы и проверки
|
## 4. Этапы и проверки
|
||||||
|
|
||||||
Каждый этап заканчивается зелёными test/check/build и ревью.
|
Каждый этап заканчивается зелёными test/check/build и ревью.
|
||||||
|
|
||||||
- **A. Извлечение классики** — перенос разметки и стилей этапа из `ToolPage` в `ToolStageClassic` без изменений поведения; пропсы собираются в одном объекте. Критерий: страница неотличима, diff по логике `ToolPage` нулевой.
|
- **A. Извлечение классики** — перенос разметки и стилей этапа из `ToolPage` в
|
||||||
- **B. Инлайн-раскладка** — `ToolStageInline`, переключение в `ToolPage` на новый компонент, прогон смоука §5. Ревью в браузере; решение дизайнера — потом, код уже позволяет жить в обоих вариантах.
|
`ToolStageClassic` без изменений поведения; пропсы собираются в одном объекте.
|
||||||
|
Критерий: страница неотличима, diff по логике `ToolPage` нулевой.
|
||||||
|
- **B. Инлайн-раскладка** — `ToolStageInline`, переключение в `ToolPage` на
|
||||||
|
новый компонент, прогон смоука §5. Ревью в браузере; решение дизайнера —
|
||||||
|
потом, код уже позволяет жить в обоих вариантах.
|
||||||
|
|
||||||
## 5. Смоук
|
## 5. Смоук
|
||||||
|
|
||||||
1. Замена компонента этапа — одна строка; обе версии собираются без предупреждений.
|
1. Замена компонента этапа — одна строка; обе версии собираются без
|
||||||
2. Инструмент без параметров (например Автоконтраст): две колонки, пустой середины нет.
|
предупреждений.
|
||||||
|
2. Инструмент без параметров (например Автоконтраст): две колонки, пустой
|
||||||
|
середины нет.
|
||||||
3. Генератор без исходника: параметры и результат в две колонки.
|
3. Генератор без исходника: параметры и результат в две колонки.
|
||||||
4. Текстовый инструмент до декодирования: вход слева, параметры в центре, пустое состояние результата справа.
|
4. Текстовый инструмент до декодирования: вход слева, параметры в центре, пустое
|
||||||
5. Маска, пипетка, слайдеры в средней колонке; изменение параметра перезапускает инструмент как раньше.
|
состояние результата справа.
|
||||||
6. Узкий экран: стек без горизонтального скролла, порядок исходник → параметры → результат.
|
5. Маска, пипетка, слайдеры в средней колонке; изменение параметра перезапускает
|
||||||
|
инструмент как раньше.
|
||||||
|
6. Узкий экран: стек без горизонтального скролла, порядок исходник → параметры →
|
||||||
|
результат.
|
||||||
7. Легенды присутствуют у всех трёх зон в обеих раскладках.
|
7. Легенды присутствуют у всех трёх зон в обеих раскладках.
|
||||||
|
|
||||||
## 6. Что сознательно не делаем
|
## 6. Что сознательно не делаем
|
||||||
|
|
||||||
- Редизайн визуального языка (цвета, типографика, формы) — материал для дизайнера, не для этого плана.
|
- Редизайн визуального языка (цвета, типографика, формы) — материал для
|
||||||
- Новая логика: исполнение, автосохранение, пресеты — раскладка ничего не знает про исполнение.
|
дизайнера, не для этого плана.
|
||||||
|
- Новая логика: исполнение, автосохранение, пресеты — раскладка ничего не знает
|
||||||
|
про исполнение.
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
# Plan: `/preview/list-tools` → design2 (catalog)
|
# Plan: `/preview/list-tools` → design2 (catalog)
|
||||||
|
|
||||||
> **СТАТУС: ВЫПОЛНЕН (2026-09-07) — устарел.** Каталог
|
> **СТАТУС: ВЫПОЛНЕН (2026-09-07) — устарел.** Каталог `/preview/list-tools`
|
||||||
> `/preview/list-tools` переписан на `CatalogHeader` / `CatalogToolbar` /
|
> переписан на `CatalogHeader` / `CatalogToolbar` / `CatalogGroup` / `ToolCard`
|
||||||
> `CatalogGroup` / `ToolCard` (kit), поиск и фильтры функциональны.
|
> (kit), поиск и фильтры функциональны. Доводка верности против рефа остаётся в
|
||||||
> Доводка верности против рефа остаётся в рамках `plan-design-fix.md`.
|
> рамках `plan-design-fix.md`.
|
||||||
|
|
||||||
Ветка `feat/redesign`. Цель — привести каталог инструментов к рефу
|
Ветка `feat/redesign`. Цель — привести каталог инструментов к рефу
|
||||||
`refs-html/list-tools.html`. Сейчас страница вообще не на design2: по
|
`refs-html/list-tools.html`. Сейчас страница вообще не на design2: по
|
||||||
@@ -16,19 +16,19 @@
|
|||||||
`ToolCard` (kit). Нет поиска, фильтров, счётчика, навигация по категориям
|
`ToolCard` (kit). Нет поиска, фильтров, счётчика, навигация по категориям
|
||||||
сделана через `Panel`-блоки, а не через `.catalog-groups`.
|
сделана через `Panel`-блоки, а не через `.catalog-groups`.
|
||||||
- Реф (`list-tools.html`) строит:
|
- Реф (`list-tools.html`) строит:
|
||||||
- `.catalog-page` → `.catalog-head` (`.eyebrow`, `h1` «Tool catalog»,
|
- `.catalog-page` → `.catalog-head` (`.eyebrow`, `h1` «Tool catalog», `p`
|
||||||
`p` lede, `.catalog-total` «NN TOOLS AVAILABLE» с большим числом),
|
lede, `.catalog-total` «NN TOOLS AVAILABLE» с большим числом),
|
||||||
- `.catalog-toolbar` (`.catalog-search` + `.catalog-filters` с кнопками),
|
- `.catalog-toolbar` (`.catalog-search` + `.catalog-filters` с кнопками),
|
||||||
- `.catalog-groups` (grid `repeat(2,minmax(0,1fr))`, gap `56px 28px`) →
|
- `.catalog-groups` (grid `repeat(2,minmax(0,1fr))`, gap `56px 28px`) →
|
||||||
`.catalog-group` (`.group-title` «CAT · NN TOOLS») → `.tool-cards` →
|
`.catalog-group` (`.group-title` «CAT · NN TOOLS») → `.tool-cards` →
|
||||||
`.tool-card`.
|
`.tool-card`.
|
||||||
- `.tool-card` (реф) — `display:grid`, колонки `42px minmax(0,1fr) 24px 18px`,
|
- `.tool-card` (реф) — `display:grid`, колонки `42px minmax(0,1fr) 24px 18px`,
|
||||||
`min-height:106px`, `gap:14px`, `padding:16px`: `.tool-icon` (42×42
|
`min-height:106px`, `gap:14px`, `padding:16px`: `.tool-icon` (42×42 синий
|
||||||
синий размытый квадрат), `.tool-copy` (`strong` 13px mono + `span` 12px mono
|
размытый квадрат), `.tool-copy` (`strong` 13px mono + `span` 12px mono muted),
|
||||||
muted), `.tool-index` (10px mono, сверху), `.tool-arrow` (появляется при hover).
|
`.tool-index` (10px mono, сверху), `.tool-arrow` (появляется при hover).
|
||||||
- Токены `design2.css` уже совпадают с рефом (`--background #eef1f4`,
|
- Токены `design2.css` уже совпадают с рефом (`--background #eef1f4`,
|
||||||
`--panel #f8fafb`, `--foreground #17212b`, `--muted #6d7883`, `--line #cbd3da`,
|
`--panel #f8fafb`, `--foreground #17212b`, `--muted #6d7883`,
|
||||||
`--blue #1769d2`, `--radius 4px`) — править токены не нужно.
|
`--line #cbd3da`, `--blue #1769d2`, `--radius 4px`) — править токены не нужно.
|
||||||
|
|
||||||
## Шаги
|
## Шаги
|
||||||
|
|
||||||
@@ -37,20 +37,21 @@
|
|||||||
- grid-колонки `42px minmax(0,1fr) 24px 18px`, `align-items:center`,
|
- grid-колонки `42px minmax(0,1fr) 24px 18px`, `align-items:center`,
|
||||||
`gap:14px`, `min-height:106px`, `padding:16px`, `border:1px solid line`,
|
`gap:14px`, `min-height:106px`, `padding:16px`, `border:1px solid line`,
|
||||||
`background:panel`, `border-radius:radius`.
|
`background:panel`, `border-radius:radius`.
|
||||||
- `.tool-icon`: 42×42 `place-items:center`, `background:color-mix(in srgb,
|
- `.tool-icon`: 42×42 `place-items:center`,
|
||||||
var(--blue) 12%, transparent)`, `color:var(--blue)` (реф использует
|
`background:color-mix(in srgb, var(--blue) 12%, transparent)`,
|
||||||
`--panel-strong`; вместо нового токена берём этот `color-mix`).
|
`color:var(--blue)` (реф использует `--panel-strong`; вместо нового токена
|
||||||
|
берём этот `color-mix`).
|
||||||
- `.tool-copy strong`: `600 13px var(--font-mono)`; `span`: `12px/1.5 mono`
|
- `.tool-copy strong`: `600 13px var(--font-mono)`; `span`: `12px/1.5 mono`
|
||||||
`muted`.
|
`muted`.
|
||||||
- `.tool-index`: `10px mono muted`, `align-self:start`.
|
- `.tool-index`: `10px mono muted`, `align-self:start`.
|
||||||
- `.tool-arrow`: `color:var(--blue)`, `opacity:0` → `1` + `translate(2px,-2px)`
|
- `.tool-arrow`: `color:var(--blue)`, `opacity:0` → `1` +
|
||||||
на `.tool-card:hover`.
|
`translate(2px,-2px)` на `.tool-card:hover`.
|
||||||
- DOM-порядок как в рефе: `icon, copy, index, arrow`.
|
- DOM-порядок как в рефе: `icon, copy, index, arrow`.
|
||||||
- `href` через `resolve` из `$app/paths` (убрать `svelte-ignore`).
|
- `href` через `resolve` из `$app/paths` (убрать `svelte-ignore`).
|
||||||
|
|
||||||
2. **`kit/CatalogHeader.svelte`**: `.eyebrow` «EASY-PNG-TOOLS / CATALOG»,
|
2. **`kit/CatalogHeader.svelte`**: `.eyebrow` «EASY-PNG-TOOLS / CATALOG», `h1`
|
||||||
`h1` «Tool catalog», lede; `.catalog-total` с динамическим `TOOLS.length`
|
«Tool catalog», lede; `.catalog-total` с динамическим `TOOLS.length` (реф
|
||||||
(реф показывает `32`).
|
показывает `32`).
|
||||||
|
|
||||||
3. **`kit/CatalogToolbar.svelte`**: `.catalog-search` (иконка + `<input>`) и
|
3. **`kit/CatalogToolbar.svelte`**: `.catalog-search` (иконка + `<input>`) и
|
||||||
`.catalog-filters` (кнопки по категориям). Состояние `query` / `category`
|
`.catalog-filters` (кнопки по категориям). Состояние `query` / `category`
|
||||||
@@ -68,8 +69,8 @@
|
|||||||
- `<CatalogToolbar bind:query bind:category />`;
|
- `<CatalogToolbar bind:query bind:category />`;
|
||||||
- `.catalog-groups` из отфильтрованных групп (по `CATEGORIES`, пересчитав
|
- `.catalog-groups` из отфильтрованных групп (по `CATEGORIES`, пересчитав
|
||||||
`tools` с учётом `query`/`category`);
|
`tools` с учётом `query`/`category`);
|
||||||
- внутри `<CatalogGroup>` — `<ToolCard>` на каждый tool (`href=
|
- внутри `<CatalogGroup>` — `<ToolCard>` на каждый tool
|
||||||
"/preview/tools/{id}"`, `icon` из `tool-icons`, `index` по порядку,
|
(`href= "/preview/tools/{id}"`, `icon` из `tool-icons`, `index` по порядку,
|
||||||
`title`/`description` из registry);
|
`title`/`description` из registry);
|
||||||
- убрать `Panel`/`PanelHeading` из этого маршрута.
|
- убрать `Panel`/`PanelHeading` из этого маршрута.
|
||||||
|
|
||||||
@@ -77,9 +78,9 @@
|
|||||||
TRANSPARENCY / COLOR / GEOMETRY / FILTERS / ANALYZE). Наши `CATEGORIES`
|
TRANSPARENCY / COLOR / GEOMETRY / FILTERS / ANALYZE). Наши `CATEGORIES`
|
||||||
(`convert/alpha/color/geometry/filters/text/analyze/generate`) — сопоставить
|
(`convert/alpha/color/geometry/filters/text/analyze/generate`) — сопоставить
|
||||||
(`alpha`→TRANSPARENCY и т.п.); точное сопоставление уточнить по рефу и
|
(`alpha`→TRANSPARENCY и т.п.); точное сопоставление уточнить по рефу и
|
||||||
`i18n/ru.ts`. Фильтры = ALL + те же label'ы. Тексты заголовков/lede — по
|
`i18n/ru.ts`. Фильтры = ALL + те же label'ы. Тексты заголовков/lede — по рефу
|
||||||
рефу (англ. «Tool catalog» / «Focused utilities for working with PNG…»),
|
(англ. «Tool catalog» / «Focused utilities for working with PNG…»), либо
|
||||||
либо оставить RU-вариант, если это сознательное отклонение (зафиксировать).
|
оставить RU-вариант, если это сознательное отклонение (зафиксировать).
|
||||||
|
|
||||||
7. **Адаптив**: перенести правила рефа (`@media max-width:800px`) —
|
7. **Адаптив**: перенести правила рефа (`@media max-width:800px`) —
|
||||||
`.catalog-groups` → 1fr, `.tool-card` → `38px minmax(0,1fr) 18px`, скрыть
|
`.catalog-groups` → 1fr, `.tool-card` → `38px minmax(0,1fr) 18px`, скрыть
|
||||||
@@ -87,16 +88,16 @@
|
|||||||
|
|
||||||
## Верификация
|
## Верификация
|
||||||
|
|
||||||
- `pnpm lint`, `pnpm exec svelte-check --tsconfig ./tsconfig.json`,
|
- `pnpm lint`, `pnpm exec svelte-check --tsconfig ./tsconfig.json`, `pnpm build`
|
||||||
`pnpm build` — без ошибок.
|
— без ошибок.
|
||||||
- `pnpm refs-audit` (теперь мульти-маршрутный) → глянуть секцию
|
- `pnpm refs-audit` (теперь мульти-маршрутный) → глянуть секцию
|
||||||
`/preview/list-tools`: `onlyOurs`/`onlyRef` должны упасть с `257/54`
|
`/preview/list-tools`: `onlyOurs`/`onlyRef` должны упасть с `257/54` примерно
|
||||||
примерно до уровня demo (`18/11`). Остаточный diff (если будет) зафиксировать
|
до уровня demo (`18/11`). Остаточный diff (если будет) зафиксировать в этом
|
||||||
в этом файле.
|
файле.
|
||||||
|
|
||||||
## Примечания
|
## Примечания
|
||||||
|
|
||||||
- `ToolCard` правится на месте — влияет на `preview/+page` и `preview/kit`
|
- `ToolCard` правится на месте — влияет на `preview/+page` и `preview/kit` (там
|
||||||
(там тоже карточки каталога, унификация уместна).
|
тоже карточки каталога, унификация уместна).
|
||||||
- Старый дизайн (`routes/(old)/**`, `components/search/**`) не трогать.
|
- Старый дизайн (`routes/(old)/**`, `components/search/**`) не трогать.
|
||||||
- Коммиты делает автор (агент не коммитит).
|
- Коммиты делает автор (агент не коммитит).
|
||||||
|
|||||||
@@ -4,15 +4,25 @@
|
|||||||
|
|
||||||
## 1. Зачем
|
## 1. Зачем
|
||||||
|
|
||||||
- Первые операции настоящего алгоритмического уровня: свёртки и заливка связной области.
|
- Первые операции настоящего алгоритмического уровня: свёртки и заливка связной
|
||||||
- Витринная фича каталога — удаление однотонного фона без нейросетей: порог по цвету + заливка от краёв изображения + сглаживание границы маски.
|
области.
|
||||||
- Эти операции реально блокируют UI-поток на больших картинках — повод включить давно подготовленный путь: исполнитель `executeStep` переезжает в Web Worker, страницы не меняются вовсе.
|
- Витринная фича каталога — удаление однотонного фона без нейросетей: порог по
|
||||||
|
цвету + заливка от краёв изображения + сглаживание границы маски.
|
||||||
|
- Эти операции реально блокируют UI-поток на больших картинках — повод включить
|
||||||
|
давно подготовленный путь: исполнитель `executeStep` переезжает в Web Worker,
|
||||||
|
страницы не меняются вовсе.
|
||||||
|
|
||||||
## 2. Инструменты — 3
|
## 2. Инструменты — 3
|
||||||
|
|
||||||
- **Удалить фон PNG** (`remove-background-png`, категория «Прозрачность») — параметры: цвет фона, допуск похожести %, режим «только внешние области» (заливка от краёв вместо глобального совпадения), сглаживание границы (проходы мажоритарного фильтра маски). Даёт превью маски через существующий контракт `preview`.
|
- **Удалить фон PNG** (`remove-background-png`, категория «Прозрачность») —
|
||||||
- **Размытие PNG** (`blur-png`, новая категория «Фильтры») — гауссово приближение тремя проходами разделяемого бокса; радиус слайдером 1–32 px.
|
параметры: цвет фона, допуск похожести %, режим «только внешние области»
|
||||||
- **Резкость PNG** (`sharpen-png`, «Фильтры») — свёртка ядром резкости с регулируемой силой (смесь исходного и обработанного), сила слайдером 0–100 %.
|
(заливка от краёв вместо глобального совпадения), сглаживание границы (проходы
|
||||||
|
мажоритарного фильтра маски). Даёт превью маски через существующий контракт
|
||||||
|
`preview`.
|
||||||
|
- **Размытие PNG** (`blur-png`, новая категория «Фильтры») — гауссово
|
||||||
|
приближение тремя проходами разделяемого бокса; радиус слайдером 1–32 px.
|
||||||
|
- **Резкость PNG** (`sharpen-png`, «Фильтры») — свёртка ядром резкости с
|
||||||
|
регулируемой силой (смесь исходного и обработанного), сила слайдером 0–100 %.
|
||||||
|
|
||||||
## 3. Новое ядро (чистые функции, детерминированные, покрытые тестами)
|
## 3. Новое ядро (чистые функции, детерминированные, покрытые тестами)
|
||||||
|
|
||||||
@@ -21,46 +31,67 @@
|
|||||||
- разделяемый бокс-блюр и сборка гауссова приближения из трёх проходов;
|
- разделяемый бокс-блюр и сборка гауссова приближения из трёх проходов;
|
||||||
- ядро резкости и смешивание с оригиналом по силе.
|
- ядро резкости и смешивание с оригиналом по силе.
|
||||||
- `core/background.ts`
|
- `core/background.ts`
|
||||||
- построение маски удаления по цветовому расстоянию (та же метрика, что у удаления цвета);
|
- построение маски удаления по цветовому расстоянию (та же метрика, что у
|
||||||
|
удаления цвета);
|
||||||
- заливка связной области от всех граничных пикселей (очередь, без рекурсии);
|
- заливка связной области от всех граничных пикселей (очередь, без рекурсии);
|
||||||
- мажоритарное сглаживание маски заданным числом проходов;
|
- мажоритарное сглаживание маски заданным числом проходов;
|
||||||
- сборка результата: альфа 0 у удалённых пикселей; плюс функция маски для превью.
|
- сборка результата: альфа 0 у удалённых пикселей; плюс функция маски для
|
||||||
|
превью.
|
||||||
|
|
||||||
## 4. Web Worker
|
## 4. Web Worker
|
||||||
|
|
||||||
- Точка исполнения одна — `tools/executor.ts`; меняется только её внутренность.
|
- Точка исполнения одна — `tools/executor.ts`; меняется только её внутренность.
|
||||||
- Воркер-файл импортирует реестр и исполняет операцию по `toolId`; картинка приходит копией (структурный клон), результат возвращается передаваемым буфером.
|
- Воркер-файл импортирует реестр и исполняет операцию по `toolId`; картинка
|
||||||
- Ленивая инициализация воркера при первом вызове; если воркер недоступен (старый браузер/тестовая среда) — автоматический откат на прямое исполнение в главном потоке. Обе ветви обязаны давать побайтово одинаковый результат.
|
приходит копией (структурный клон), результат возвращается передаваемым
|
||||||
- Генераторы и текстовые представления остаются в главном потоке — они мгновенные.
|
буфером.
|
||||||
|
- Ленивая инициализация воркера при первом вызове; если воркер недоступен
|
||||||
|
(старый браузер/тестовая среда) — автоматический откат на прямое исполнение в
|
||||||
|
главном потоке. Обе ветви обязаны давать побайтово одинаковый результат.
|
||||||
|
- Генераторы и текстовые представления остаются в главном потоке — они
|
||||||
|
мгновенные.
|
||||||
- Пипетка и лупа работают с уже готовыми картинками и не зависят от переноса.
|
- Пипетка и лупа работают с уже готовыми картинками и не зависят от переноса.
|
||||||
|
|
||||||
## 5. Этапы и проверки
|
## 5. Этапы и проверки
|
||||||
|
|
||||||
- **A. Ядро** — свёртки и фон с тестами на малых матрицах (точные ожидания руками). Проверка: `pnpm test/check`.
|
- **A. Ядро** — свёртки и фон с тестами на малых матрицах (точные ожидания
|
||||||
- **B. Реестр** — категория «Фильтры», три записи, превью маски. Проверка: тесты целостности, визуально.
|
руками). Проверка: `pnpm test/check`.
|
||||||
- **C. Web Worker** — перенос исполнителя, отккат-ветвь. Проверка: результаты до/после переключения идентичны байт-в-байт на фиксированной картинке и параметрах; `test/build` зелёные.
|
- **B. Реестр** — категория «Фильтры», три записи, превью маски. Проверка: тесты
|
||||||
|
целостности, визуально.
|
||||||
|
- **C. Web Worker** — перенос исполнителя, отккат-ветвь. Проверка: результаты
|
||||||
|
до/после переключения идентичны байт-в-байт на фиксированной картинке и
|
||||||
|
параметрах; `test/build` зелёные.
|
||||||
- **D. Итог** — полный прогон и смоук §8.
|
- **D. Итог** — полный прогон и смоук §8.
|
||||||
|
|
||||||
## 6. Смоук-чеклист
|
## 6. Смоук-чеклист
|
||||||
|
|
||||||
1. Размытие: радиус 1 и 32 — плавно усиливается, прозрачные края не темнеют.
|
1. Размытие: радиус 1 и 32 — плавно усиливается, прозрачные края не темнеют.
|
||||||
2. Резкость: сила 0 не меняет картинку; сила 100 заметно подчёркивает края без цветового мусора.
|
2. Резкость: сила 0 не меняет картинку; сила 100 заметно подчёркивает края без
|
||||||
|
цветового мусора.
|
||||||
3. Удаление фона на логотипе с однотонным фоном: фон исчез, содержимое цело.
|
3. Удаление фона на логотипе с однотонным фоном: фон исчез, содержимое цело.
|
||||||
4. Режим «только внешние области»: дырка внутри объекта того же цвета остаётся непрозрачной.
|
4. Режим «только внешние области»: дырка внутри объекта того же цвета остаётся
|
||||||
5. Сглаживание 0/2/4 прохода — граница от жёсткой к мягкой, результат воспроизводим.
|
непрозрачной.
|
||||||
6. Превью маски показывает белым удаляемую область; скачивание всегда отдаёт результат.
|
5. Сглаживание 0/2/4 прохода — граница от жёсткой к мягкой, результат
|
||||||
7. Во время размытия большой картинки интерфейс остаётся отзывчивым (чип «Пересчёт…», скролл живой).
|
воспроизводим.
|
||||||
8. Цепочка: размытие → резкость → удаление фона работает; каждый блок скачивает свой промежуток.
|
6. Превью маски показывает белым удаляемую область; скачивание всегда отдаёт
|
||||||
|
результат.
|
||||||
|
7. Во время размытия большой картинки интерфейс остаётся отзывчивым (чип
|
||||||
|
«Пересчёт…», скролл живой).
|
||||||
|
8. Цепочка: размытие → резкость → удаление фона работает; каждый блок скачивает
|
||||||
|
свой промежуток.
|
||||||
9. Регресс старых инструментов и главной-поиска без изменений поведения.
|
9. Регресс старых инструментов и главной-поиска без изменений поведения.
|
||||||
|
|
||||||
## 7. Критерии готовности
|
## 7. Критерии готовности
|
||||||
|
|
||||||
- Три новых инструмента проходят смоук §6; операции ядра покрыты точными юнит-тестами.
|
- Три новых инструмента проходят смоук §6; операции ядра покрыты точными
|
||||||
- Исполнение изображений идёт через воркер там, где он доступен, с гарантированно одинаковым результатом обеих ветвей.
|
юнит-тестами.
|
||||||
|
- Исполнение изображений идёт через воркер там, где он доступен, с
|
||||||
|
гарантированно одинаковым результатом обеих ветвей.
|
||||||
- `test/check/build` зелёные.
|
- `test/check/build` зелёные.
|
||||||
|
|
||||||
## 8. Что сознательно не делаем
|
## 8. Что сознательно не делаем
|
||||||
|
|
||||||
- Многопроходное гауссово ядро «по-настоящему» (O(r²)) — трёхпроходного боксового приближения достаточно визуально и оно быстрее.
|
- Многопроходное гауссово ядро «по-настоящему» (O(r²)) — трёхпроходного
|
||||||
|
боксового приближения достаточно визуально и оно быстрее.
|
||||||
- Хромакей по нескольким цветам, ИИ-сегментация — следующие волны.
|
- Хромакей по нескольким цветам, ИИ-сегментация — следующие волны.
|
||||||
- Отмена выполняющегося прогона воркера — устаревшие результаты отбрасываются токеном, сама работа завершается фоном.
|
- Отмена выполняющегося прогона воркера — устаревшие результаты отбрасываются
|
||||||
|
токеном, сама работа завершается фоном.
|
||||||
|
|||||||
@@ -4,17 +4,26 @@
|
|||||||
|
|
||||||
## 1. Зачем
|
## 1. Зачем
|
||||||
|
|
||||||
Одна пара функций — расширение и сужение непрозрачных областей — открывает целое семейство пользовательских операций: обводка логотипов и иконок, контуры, утолщение и утончение подписей, удаление мелкого мусора и закрытие дырок. Всё это частые запросы в наборах PNG-утилит, а ядро получается компактным и легко покрывается тестами.
|
Одна пара функций — расширение и сужение непрозрачных областей — открывает целое
|
||||||
|
семейство пользовательских операций: обводка логотипов и иконок, контуры,
|
||||||
|
утолщение и утончение подписей, удаление мелкого мусора и закрытие дырок. Всё
|
||||||
|
это частые запросы в наборах PNG-утилит, а ядро получается компактным и легко
|
||||||
|
покрывается тестами.
|
||||||
|
|
||||||
## 2. Инструменты — 7 (все в категории «Прозрачность»)
|
## 2. Инструменты — 7 (все в категории «Прозрачность»)
|
||||||
|
|
||||||
- **Обвести PNG** (`add-stroke-png`) — кольцо выбранного цвета вокруг непрозрачного содержимого; параметры: цвет, толщина 1–10 px.
|
- **Обвести PNG** (`add-stroke-png`) — кольцо выбранного цвета вокруг
|
||||||
- **Найти контур PNG** (`find-contour-png`) — линия по краю непрозрачных областей; параметр: толщина линии.
|
непрозрачного содержимого; параметры: цвет, толщина 1–10 px.
|
||||||
|
- **Найти контур PNG** (`find-contour-png`) — линия по краю непрозрачных
|
||||||
|
областей; параметр: толщина линии.
|
||||||
- **Утолщить PNG** (`make-thicker-png`) — расширение содержимого на N пикселей.
|
- **Утолщить PNG** (`make-thicker-png`) — расширение содержимого на N пикселей.
|
||||||
- **Утончить PNG** (`make-thinner-png`) — сужение содержимого на N пикселей.
|
- **Утончить PNG** (`make-thinner-png`) — сужение содержимого на N пикселей.
|
||||||
- **Жёсткие края PNG** (`harden-alpha-png`) — бинаризация альфы по порогу: полупрозрачность уходит, остаётся два состояния.
|
- **Жёсткие края PNG** (`harden-alpha-png`) — бинаризация альфы по порогу:
|
||||||
- **Убрать мусор PNG** (`despeckle-alpha-png`) — открытие: удаляет одиночные полупрозрачные пиксели и мелкие крапинки.
|
полупрозрачность уходит, остаётся два состояния.
|
||||||
- **Закрыть дыры PNG** (`close-holes-png`) — закрытие: заполняет одиночные прозрачные точки внутри объекта.
|
- **Убрать мусор PNG** (`despeckle-alpha-png`) — открытие: удаляет одиночные
|
||||||
|
полупрозрачные пиксели и мелкие крапинки.
|
||||||
|
- **Закрыть дыры PNG** (`close-holes-png`) — закрытие: заполняет одиночные
|
||||||
|
прозрачные точки внутри объекта.
|
||||||
|
|
||||||
Все операции применяются к альфа-каналу; RGB пикселей сохраняется.
|
Все операции применяются к альфа-каналу; RGB пикселей сохраняется.
|
||||||
|
|
||||||
@@ -23,49 +32,64 @@
|
|||||||
Чистые функции над бинарной маской и над альфа-каналом:
|
Чистые функции над бинарной маской и над альфа-каналом:
|
||||||
|
|
||||||
- `buildAlphaMask(img)` — 1 там, где альфа > 0.
|
- `buildAlphaMask(img)` — 1 там, где альфа > 0.
|
||||||
- `dilateMask(mask, w, h, radius, shape)` — расширение; `shape`: квадрат или диск (для обводок диск даёт ровную круглую линию).
|
- `dilateMask(mask, w, h, radius, shape)` — расширение; `shape`: квадрат или
|
||||||
|
диск (для обводок диск даёт ровную круглую линию).
|
||||||
- `erodeMask(...)` — зеркальная операция.
|
- `erodeMask(...)` — зеркальная операция.
|
||||||
- `dilateImage / erodeImage(img, radiusPx)` — применяют маскирование к альфе изображения, RGB сохраняется.
|
- `dilateImage / erodeImage(img, radiusPx)` — применяют маскирование к альфе
|
||||||
|
изображения, RGB сохраняется.
|
||||||
- Сборщики:
|
- Сборщики:
|
||||||
- обводка: `dilate(mask) − mask` → новое кольцо заливается цветом;
|
- обводка: `dilate(mask) − mask` → новое кольцо заливается цветом;
|
||||||
- контур: `mask − erode(mask)` → линия;
|
- контур: `mask − erode(mask)` → линия;
|
||||||
- открытие: `erode → dilate`; закрытие: `dilate → erode`.
|
- открытие: `erode → dilate`; закрытие: `dilate → erode`.
|
||||||
- `hardenAlpha(img, thresholdPercent)` — отдельно от морфологии, но той же волной.
|
- `hardenAlpha(img, thresholdPercent)` — отдельно от морфологии, но той же
|
||||||
|
волной.
|
||||||
|
|
||||||
Структурирующий элемент при радиусе r — диск радиуса r (расстояние до центра ≤ r); квадрат не используем, чтобы углы обводок не были угловатыми.
|
Структурирующий элемент при радиусе r — диск радиуса r (расстояние до центра ≤
|
||||||
|
r); квадрат не используем, чтобы углы обводок не были угловатыми.
|
||||||
|
|
||||||
## 4. Web Worker
|
## 4. Web Worker
|
||||||
|
|
||||||
Ничего не требуется: новые операции попадают в реестр, исполнитель автоматически исполняет их в воркере. Тесты ядра идут мимо воркера (чистые функции).
|
Ничего не требуется: новые операции попадают в реестр, исполнитель автоматически
|
||||||
|
исполняет их в воркере. Тесты ядра идут мимо воркера (чистые функции).
|
||||||
|
|
||||||
## 5. Этапы и проверки
|
## 5. Этапы и проверки
|
||||||
|
|
||||||
- **A. Ядро** — `morphology.ts` + точные тесты на матрицах 5×5: расширение диска, сужение, открытие убирает одиночный пиксель, закрытие закрывает одиночную дыру, кольцо обводки нужного цвета и толщины. Проверка: `pnpm test/check`.
|
- **A. Ядро** — `morphology.ts` + точные тесты на матрицах 5×5: расширение
|
||||||
- **B. Реестр** — семь записей, тест целостности обновляется (51 → 58). Проверка: `test/build`, визуально.
|
диска, сужение, открытие убирает одиночный пиксель, закрытие закрывает
|
||||||
|
одиночную дыру, кольцо обводки нужного цвета и толщины. Проверка:
|
||||||
|
`pnpm test/check`.
|
||||||
|
- **B. Реестр** — семь записей, тест целостности обновляется (51 → 58).
|
||||||
|
Проверка: `test/build`, визуально.
|
||||||
- **C. Итог** — полный прогон, смоук §7.
|
- **C. Итог** — полный прогон, смоук §7.
|
||||||
|
|
||||||
## 6. Критерии готовности
|
## 6. Критерии готовности
|
||||||
|
|
||||||
- Семь новых инструментов работают на прозрачных PNG: обводка, контур, толще/тоньше, жёсткие края, убрать мусор, закрыть дыры.
|
- Семь новых инструментов работают на прозрачных PNG: обводка, контур,
|
||||||
|
толще/тоньше, жёсткие края, убрать мусор, закрыть дыры.
|
||||||
- Все операции детерминированы и покрыты юнит-тестами с ручными ожиданиями.
|
- Все операции детерминированы и покрыты юнит-тестами с ручными ожиданиями.
|
||||||
- Исполнение автоматически идёт в воркере.
|
- Исполнение автоматически идёт в воркере.
|
||||||
- `test/check/build` зелёные, смоук §7 пройден.
|
- `test/check/build` зелёные, смоук §7 пройден.
|
||||||
|
|
||||||
## 7. Смоук-чеклист
|
## 7. Смоук-чеклист
|
||||||
|
|
||||||
1. Обводка толщиной 3 вокруг круглого стикера — ровное кольцо выбранного цвета, без угловатости.
|
1. Обводка толщиной 3 вокруг круглого стикера — ровное кольцо выбранного цвета,
|
||||||
2. Толщина 1 — тонкая аккуратная линия; 10 — толстая, форма содержимого не искажается сверх роста.
|
без угловатости.
|
||||||
|
2. Толщина 1 — тонкая аккуратная линия; 10 — толстая, форма содержимого не
|
||||||
|
искажается сверх роста.
|
||||||
3. Контур показывает только край содержимого линией.
|
3. Контур показывает только край содержимого линией.
|
||||||
4. Толще/тоньше на тонкой надписи — штрихи растут/сжимаются без разрывов при умеренных значениях.
|
4. Толще/тоньше на тонкой надписи — штрихи растут/сжимаются без разрывов при
|
||||||
|
умеренных значениях.
|
||||||
5. Жёсткие края: полупрозрачность исчезает, силуэт сохраняется.
|
5. Жёсткие края: полупрозрачность исчезает, силуэт сохраняется.
|
||||||
6. Убрать мусор убирает одиночные точки-крапинки, не трогая крупные области.
|
6. Убрать мусор убирает одиночные точки-крапинки, не трогая крупные области.
|
||||||
7. Закрыть дыры заполняет одиночные прозрачные точки внутри логотипа.
|
7. Закрыть дыры заполняет одиночные прозрачные точки внутри логотипа.
|
||||||
8. Цепочка: обводка после удаления фона работает; каждое звено скачивает свой промежуток.
|
8. Цепочка: обводка после удаления фона работает; каждое звено скачивает свой
|
||||||
|
промежуток.
|
||||||
9. Регресс остальных инструментов без изменений.
|
9. Регресс остальных инструментов без изменений.
|
||||||
|
|
||||||
## 8. Что сознательно не делаем
|
## 8. Что сознательно не делаем
|
||||||
|
|
||||||
- Морфология по яркости для непрозрачных сканов — волна фокусируется на альфе прозрачных PNG.
|
- Морфология по яркости для непрозрачных сканов — волна фокусируется на альфе
|
||||||
|
прозрачных PNG.
|
||||||
- Выбор структурирующего элемента пользователем — всегда диск.
|
- Выбор структурирующего элемента пользователем — всегда диск.
|
||||||
- Feather/свечение краёв — отдельная будущая задача поверх размытия.
|
- Feather/свечение краёв — отдельная будущая задача поверх размытия.
|
||||||
- Произвольные SVG-маски и градиентные обводки.
|
- Произвольные SVG-маски и градиентные обводки.
|
||||||
|
|||||||
@@ -11,10 +11,9 @@
|
|||||||
> Архивная копия, ниже — состояние на момент закрытия.
|
> Архивная копия, ниже — состояние на момент закрытия.
|
||||||
|
|
||||||
> Статус: **переведено 121 из 125; остальные 4 — отложены/закрыты решениями.**
|
> Статус: **переведено 121 из 125; остальные 4 — отложены/закрыты решениями.**
|
||||||
> Текущее
|
> Текущее состояние: **121 инструмент переведён в `registry-new`**, старый UI
|
||||||
> состояние: **121 инструмент переведён в `registry-new`**, старый UI уже живёт
|
> уже живёт на `(old)/`-маршрутах, линтер-изоляция old↔preview на месте (плагин
|
||||||
> на `(old)/`-маршрутах, линтер-изоляция old↔preview на месте
|
> `isolation/no-mixed-imports`). Здесь — что сделать, чтобы:
|
||||||
> (плагин `isolation/no-mixed-imports`). Здесь — что сделать, чтобы:
|
|
||||||
>
|
>
|
||||||
> 1. в preview был **весь** каталог инструментов (плюс закрыты известные долги);
|
> 1. в preview был **весь** каталог инструментов (плюс закрыты известные долги);
|
||||||
> 2. физически перенести старую ветку в папки `old/` (шаг 37 основного плана);
|
> 2. физически перенести старую ветку в папки `old/` (шаг 37 основного плана);
|
||||||
@@ -31,44 +30,44 @@
|
|||||||
Не переведены только: `png-info` (отложен, хвост-фича с exif), `compress-png`
|
Не переведены только: `png-info` (отложен, хвост-фича с exif), `compress-png`
|
||||||
и `reduce-to-size-png` (закрыто решениями, см. ниже), `watermark-image-png`
|
и `reduce-to-size-png` (закрыто решениями, см. ниже), `watermark-image-png`
|
||||||
(overlay — единственный реальный остаток).
|
(overlay — единственный реальный остаток).
|
||||||
- Preview (`/preview`) показывает переведённые; старый UI работает
|
- Preview (`/preview`) показывает переведённые; старый UI работает на
|
||||||
на `/`-маршрутах (группа `(old)/`): `+page`, `demo`, `list-tools`,
|
`/`-маршрутах (группа `(old)/`): `+page`, `demo`, `list-tools`, `tools/[id]`.
|
||||||
`tools/[id]`. Переведено всё кроме четырёх хвостов: color 18, filters 3,
|
Переведено всё кроме четырёх хвостов: color 18, filters 3, geometry 13, alpha
|
||||||
geometry 13, alpha 13 (+watermark-image в остатке), generate 10 (mix/sort),
|
13 (+watermark-image в остатке), generate 10 (mix/sort), analyze 12 (6 масок +
|
||||||
analyze 12 (6 масок + verify + 5 вердиктов), convert 14 (jpg/webp/bmp +
|
verify + 5 вердиктов), convert 14 (jpg/webp/bmp + 5 png→texт + 6 text→png),
|
||||||
5 png→texт + 6 text→png), text `watermark-tile-png`.
|
text `watermark-tile-png`.
|
||||||
- Плагин `isolation/no-mixed-imports` (`web/eslint-plugins/isolation/`)
|
- Плагин `isolation/no-mixed-imports` (`web/eslint-plugins/isolation/`)
|
||||||
полностью разделяет ветки: старый `lib/registry.ts|/registry/**`,
|
полностью разделяет ветки: старый `lib/registry.ts|/registry/**`,
|
||||||
`lib/registry-helpers.ts`, `lib/categories.ts`, `lib/tools/**`,
|
`lib/registry-helpers.ts`, `lib/categories.ts`, `lib/tools/**`,
|
||||||
`lib/components/**`(кроме `kit/`) vs новый `routes/preview/**`,
|
`lib/components/**`(кроме `kit/`) vs новый `routes/preview/**`,
|
||||||
`lib/registry-new/**`, `lib/preview/**`, `lib/registry-schema.ts`,
|
`lib/registry-new/**`, `lib/preview/**`, `lib/registry-schema.ts`,
|
||||||
`lib/components/kit/**`. Общее: `core/`, `i18n/`, `theme`, `assets/`,
|
`lib/components/kit/**`. Общее: `core/`, `i18n/`, `theme`, `assets/`, корневой
|
||||||
корневой `lib`.
|
`lib`.
|
||||||
- Правило копий (из основного плана): файл, используемый `(old)`, не трогаем —
|
- Правило копий (из основного плана): файл, используемый `(old)`, не трогаем —
|
||||||
делаем копию в `lib/preview/` (пример: `lib/preview/tool-icons.ts`).
|
делаем копию в `lib/preview/` (пример: `lib/preview/tool-icons.ts`).
|
||||||
- Известные долги (предсуществующие, не создавались нами): ошибка
|
- Известные долги (предсуществующие, не создавались нами): ошибка
|
||||||
`ToolCard.svelte:19:36` (svelte-check), фейл `palette.test.ts` (ENOENT
|
`ToolCard.svelte:19:36` (svelte-check), фейл `palette.test.ts` (ENOENT
|
||||||
`src/app.css` — из-за него `pnpm test` красный при 607 passed), ~335
|
`src/app.css` — из-за него `pnpm test` красный при 607 passed), ~335
|
||||||
design-tokens ошибок `lint:all` в старых kit-компонентах, a11y-warning в
|
design-tokens ошибок `lint:all` в старых kit-компонентах, a11y-warning в
|
||||||
`ColorField.svelte`, пустой ruleset в `Toggle.svelte`, неиспользуемые токены
|
`ColorField.svelte`, пустой ruleset в `Toggle.svelte`, неиспользуемые токены в
|
||||||
в `preview.css`.
|
`preview.css`.
|
||||||
|
|
||||||
## Скоуп перевода: переведено всё переводимое (отложено 4 хвоста)
|
## Скоуп перевода: переведено всё переводимое (отложено 4 хвоста)
|
||||||
|
|
||||||
Каталог дожат до предела текущих решений: переведено 121 из 125. Оставшиеся
|
Каталог дожат до предела текущих решений: переведено 121 из 125. Оставшиеся 4
|
||||||
4 инструмента **отложены осознанно** (не делаем сейчас):
|
инструмента **отложены осознанно** (не делаем сейчас):
|
||||||
|
|
||||||
- **`watermark-image-png` (alpha)** — **отложен**. Нужна overlay-механика
|
- **`watermark-image-png` (alpha)** — **отложен**. Нужна overlay-механика
|
||||||
(второе изображение-знак через `getOverlay`/store + параметры
|
(второе изображение-знак через `getOverlay`/store + параметры
|
||||||
scale/opacity/position/margin/aspect); решается отдельно, вместе с вопросом,
|
scale/opacity/position/margin/aspect); решается отдельно, вместе с вопросом,
|
||||||
как второй источник изображения вписывается в schema-driven preview.
|
как второй источник изображения вписывается в schema-driven preview.
|
||||||
- **`png-info`** — **отложен осознанно**: будет серьёзно дорабатываться
|
- **`png-info`** — **отложен осознанно**: будет серьёзно дорабатываться
|
||||||
отдельным райзом (exif-теги, редактирование, структурированный вывод),
|
отдельным райзом (exif-теги, редактирование, структурированный вывод), для
|
||||||
для него нужен свой отдельный случай в UI, не «ещё один text/verdict».
|
него нужен свой отдельный случай в UI, не «ещё один text/verdict».
|
||||||
- **`compress-png`** — **закрыт как дубликат** `decrease-color-count-png`:
|
- **`compress-png`** — **закрыт как дубликат** `decrease-color-count-png`:
|
||||||
пресеты сжатия 192/96/44 добавлены прямо в схему `decrease-color-count`.
|
пресеты сжатия 192/96/44 добавлены прямо в схему `decrease-color-count`.
|
||||||
- **`reduce-to-size-png`** — **отложен** до достройки кнопки Download
|
- **`reduce-to-size-png`** — **отложен** до достройки кнопки Download (backlog
|
||||||
(backlog №7, «экспорт с лимитом размера»).
|
№7, «экспорт с лимитом размера»).
|
||||||
|
|
||||||
### Переведено в этом проходе (text-механика)
|
### Переведено в этом проходе (text-механика)
|
||||||
|
|
||||||
@@ -77,10 +76,10 @@
|
|||||||
qualityParamId) для download.
|
qualityParamId) для download.
|
||||||
- **Executor:** `executeFromText` / `executeToText` / `executeTextToText`
|
- **Executor:** `executeFromText` / `executeToText` / `executeTextToText`
|
||||||
(прямые, без worker), экспорт в `preview/executor/index.ts`.
|
(прямые, без worker), экспорт в `preview/executor/index.ts`.
|
||||||
- **UI:** `SchemaTextSource` (textarea + Render), `SchemaTextResult`
|
- **UI:** `SchemaTextSource` (textarea + Render), `SchemaTextResult` (кол-блок с
|
||||||
(кол-блок с Copy/Download .txt или бейдж-вердикт по префиксу Yes/No),
|
Copy/Download .txt или бейдж-вердикт по префиксу Yes/No), интеграция в
|
||||||
интеграция в `SchemaPreview`/`SchemaToolView` (режимы file/text/generate,
|
`SchemaPreview`/`SchemaToolView` (режимы file/text/generate, рендер
|
||||||
рендер image/text/verdict, скрытие image-download для text-результата).
|
image/text/verdict, скрытие image-download для text-результата).
|
||||||
- **Инструменты:** convert 5×png→text (base64/data-uri/hex/bytes/rgb-values),
|
- **Инструменты:** convert 5×png→text (base64/data-uri/hex/bytes/rgb-values),
|
||||||
6×text→png (base64/data-uri/hex/bytes/rgb-values/svg, +width), analyze
|
6×text→png (base64/data-uri/hex/bytes/rgb-values/svg, +width), analyze
|
||||||
verify-is-png (text→text) и 5 вердиктов (grayscale/file-size/transparent/
|
verify-is-png (text→text) и 5 вердиктов (grayscale/file-size/transparent/
|
||||||
@@ -92,9 +91,9 @@
|
|||||||
## Известные долги перед переездом (завести tasks и закрыть)
|
## Известные долги перед переездом (завести tasks и закрыть)
|
||||||
|
|
||||||
По AGENTS.md техдолг «чинится только по заведённым tasks, не игнорируется»:
|
По AGENTS.md техдолг «чинится только по заведённым tasks, не игнорируется»:
|
||||||
`lint:all` (~335 design-tokens в kit), неиспользуемые токены preview.css,
|
`lint:all` (~335 design-tokens в kit), неиспользуемые токены preview.css, a11y
|
||||||
a11y `ColorField`, пустой ruleset `Toggle`. Плюс два, мешающих зелёной
|
`ColorField`, пустой ruleset `Toggle`. Плюс два, мешающих зелёной проверке
|
||||||
проверке до/после переезда: svelte-check error `ToolCard.svelte:19:36` и фейл
|
до/после переезда: svelte-check error `ToolCard.svelte:19:36` и фейл
|
||||||
`palette.test.ts` (ENOENT `src/app.css`). После их закрытия целевое состояние
|
`palette.test.ts` (ENOENT `src/app.css`). После их закрытия целевое состояние
|
||||||
проверок перед переездом: `pnpm --dir web exec svelte-check` → 0 errors,
|
проверок перед переездом: `pnpm --dir web exec svelte-check` → 0 errors,
|
||||||
`pnpm --dir web test` → зелёный, `pnpm --dir web lint:all` → только задокумент.
|
`pnpm --dir web test` → зелёный, `pnpm --dir web lint:all` → только задокумент.
|
||||||
@@ -102,9 +101,9 @@ a11y `ColorField`, пустой ruleset `Toggle`. Плюс два, мешающ
|
|||||||
|
|
||||||
## Шаги перевода остатка (порядок)
|
## Шаги перевода остатка (порядок)
|
||||||
|
|
||||||
1. **Простые пачки** — **выполнено**: color (18) → filters (3) → geometry (13)
|
1. **Простые пачки** — **выполнено**: color (18) → filters (3) → geometry (13) →
|
||||||
→ alpha (13/14, кроме watermark-image) → generate (10, включая mix/sort
|
alpha (13/14, кроме watermark-image) → generate (10, включая mix/sort через
|
||||||
через kind `colors`) → analyze-маски (6) → text `watermark-tile-png`.
|
kind `colors`) → analyze-маски (6) → text `watermark-tile-png`.
|
||||||
2. **Text-механика** — **выполнено**: типы `input`/`result` + output-формат,
|
2. **Text-механика** — **выполнено**: типы `input`/`result` + output-формат,
|
||||||
executor `executeFromText`/`toText`/`textToText`, UI
|
executor `executeFromText`/`toText`/`textToText`, UI
|
||||||
`SchemaTextSource`/`SchemaTextResult` + интеграция в preview, перевод
|
`SchemaTextSource`/`SchemaTextResult` + интеграция в preview, перевод
|
||||||
@@ -113,8 +112,8 @@ a11y `ColorField`, пустой ruleset `Toggle`. Плюс два, мешающ
|
|||||||
3. **Шаг про overlay не делается** — `watermark-image-png` **отложен решением**
|
3. **Шаг про overlay не делается** — `watermark-image-png` **отложен решением**
|
||||||
(см. «Скоуп перевода» выше), отдельным райзом вместе с вопросом второго
|
(см. «Скоуп перевода» выше), отдельным райзом вместе с вопросом второго
|
||||||
источника изображения в schema-driven preview.
|
источника изображения в schema-driven preview.
|
||||||
4. После категории — `schema.layout` для сгруппированных инструментов
|
4. После категории — `schema.layout` для сгруппированных инструментов (шаг 32-33
|
||||||
(шаг 32-33 паттерн) и ручная проверка в `/preview`.
|
паттерн) и ручная проверка в `/preview`.
|
||||||
|
|
||||||
## Переезд в `old/` (шаг 37 основного плана)
|
## Переезд в `old/` (шаг 37 основного плана)
|
||||||
|
|
||||||
@@ -130,19 +129,19 @@ a11y `ColorField`, пустой ruleset `Toggle`. Плюс два, мешающ
|
|||||||
`lib/registry/**…` заменить на `lib/old/**` (+ `lib/old/components/**` и
|
`lib/registry/**…` заменить на `lib/old/**` (+ `lib/old/components/**` и
|
||||||
т.п.), «новое» и «общее» не меняются. Проверить, что правило по-прежнему
|
т.п.), «новое» и «общее» не меняются. Проверить, что правило по-прежнему
|
||||||
разделяет (тест фолс-позитивов: временный импорт old↔new ловится).
|
разделяет (тест фолс-позитивов: временный импорт old↔new ловится).
|
||||||
3. **Импорты внутри старого кода** — относительные пути остаются рабочими
|
3. **Импорты внутри старого кода** — относительные пути остаются рабочими (папки
|
||||||
(папки двигаются целиком); поправить только то, что ссылалось на `$lib/...`
|
двигаются целиком); поправить только то, что ссылалось на `$lib/...` в другом
|
||||||
в другом стиле. Старые тесты (`registry.test.ts` и др.) переезжают вместе с
|
стиле. Старые тесты (`registry.test.ts` и др.) переезжают вместе с
|
||||||
исходниками и продолжают проходить.
|
исходниками и продолжают проходить.
|
||||||
4. **Гейты переезда:** `svelte-check` 0 errors, `pnpm --dir web test` зелёный,
|
4. **Гейты переезда:** `svelte-check` 0 errors, `pnpm --dir web test` зелёный,
|
||||||
`prettier --check .` чистый, `lint:all` = задокументированный остаток,
|
`prettier --check .` чистый, `lint:all` = задокументированный остаток, обе
|
||||||
обе ветки открываются (старая `/`, новая `/preview`).
|
ветки открываются (старая `/`, новая `/preview`).
|
||||||
|
|
||||||
## Проверка и сбор отзывов после переезда
|
## Проверка и сбор отзывов после переезда
|
||||||
|
|
||||||
1. **Регресс-чеклист по каталогу** — пройти все инструменты в `/preview`:
|
1. **Регресс-чеклист по каталогу** — пройти все инструменты в `/preview`:
|
||||||
дефолты совпадают со старыми, валидация/клампы поведения не меняют,
|
дефолты совпадают со старыми, валидация/клампы поведения не меняют, результат
|
||||||
результат эквивалентен старому UI (сверить на одинаковых входах).
|
эквивалентен старому UI (сверить на одинаковых входах).
|
||||||
2. **Тяжёлые кейсы руками:** генераторы, маски, font-style/plate/gradient на
|
2. **Тяжёлые кейсы руками:** генераторы, маски, font-style/plate/gradient на
|
||||||
всем наборе, text-source, output-формат. (`watermark-image` — позже, после
|
всем наборе, text-source, output-формат. (`watermark-image` — позже, после
|
||||||
overlay-райза.)
|
overlay-райза.)
|
||||||
@@ -150,16 +149,16 @@ a11y `ColorField`, пустой ruleset `Toggle`. Плюс два, мешающ
|
|||||||
зафиксировать в этом документе по мере находок (или ссылкой на tasks).
|
зафиксировать в этом документе по мере находок (или ссылкой на tasks).
|
||||||
4. Сверка каталога с `tools-map.md` (покрытие/дедупликация
|
4. Сверка каталога с `tools-map.md` (покрытие/дедупликация
|
||||||
`convert-png-to-jpg`/`webp`), при необходимости — обновить cards/мета.
|
`convert-png-to-jpg`/`webp`), при необходимости — обновить cards/мета.
|
||||||
5. **Решение об удалении старого** — после завершения ревью и фидбека,
|
5. **Решение об удалении старого** — после завершения ревью и фидбека, отдельным
|
||||||
отдельным шагом: тогда чистятся `lib/old/**`, `routes/(old)/**`,
|
шагом: тогда чистятся `lib/old/**`, `routes/(old)/**`, `old.css`, `exports`,
|
||||||
`old.css`, `exports`, изоляционный плагин (glob-ы больше не нужны) и
|
изоляционный плагин (glob-ы больше не нужны) и открываются «правило
|
||||||
открываются «правило копий»-дубли.
|
копий»-дубли.
|
||||||
|
|
||||||
## Как ревьюить каждый шаг (гайды)
|
## Как ревьюить каждый шаг (гайды)
|
||||||
|
|
||||||
- Диф на **один инструмент** (или фундамент без инструментов) < ~500 строк.
|
- Диф на **один инструмент** (или фундамент без инструментов) < ~500 строк.
|
||||||
- Старый `params`/`run`/`defaultParams`/`sanitizeParams` не изменены;
|
- Старый `params`/`run`/`defaultParams`/`sanitizeParams` не изменены; в
|
||||||
в `registry-new` копия логики + `schema` (дефолты равны старым).
|
`registry-new` копия логики + `schema` (дефолты равны старым).
|
||||||
- Старый UI и старые тесты продолжают работать: `pnpm --dir web test`,
|
- Старый UI и старые тесты продолжают работать: `pnpm --dir web test`,
|
||||||
`pnpm --dir web exec svelte-check`.
|
`pnpm --dir web exec svelte-check`.
|
||||||
- Новый инструмент проверяется **руками в `/preview`** end-to-end (применение +
|
- Новый инструмент проверяется **руками в `/preview`** end-to-end (применение +
|
||||||
|
|||||||
@@ -4,36 +4,63 @@
|
|||||||
|
|
||||||
## 1. Целевое поведение
|
## 1. Целевое поведение
|
||||||
|
|
||||||
1. «Процентные» параметры (яркость, контраст, качество JPEG/WebP, порог похожести) — слайдеры с числовым значением рядом; точные координаты и размеры (resize/crop) остаются полями ввода.
|
1. «Процентные» параметры (яркость, контраст, качество JPEG/WebP, порог
|
||||||
2. У цветовых параметров появляется кнопка-переключатель «Пипетка»: активна → клик по превью исходника снимает цвет пикселя и подставляет его в поле цвета; после выбора режим гаснет.
|
похожести) — слайдеры с числовым значением рядом; точные координаты и размеры
|
||||||
3. В режиме пипетки возле курсора видна лупа: увеличенный блок пикселей вокруг точки с перекрестием и подписью hex текущего пикселя; клик выбирает центр перекрестия.
|
(resize/crop) остаются полями ввода.
|
||||||
4. Активная пипетка видна на превью перекрестием курсора; повторный клик по кнопке отменяет режим без выбора.
|
2. У цветовых параметров появляется кнопка-переключатель «Пипетка»: активна →
|
||||||
5. Всё работает поверх текущего процесса: авто-применение с задержкой, токен запусков, маски — без изменений.
|
клик по превью исходника снимает цвет пикселя и подставляет его в поле цвета;
|
||||||
|
после выбора режим гаснет.
|
||||||
|
3. В режиме пипетки возле курсора видна лупа: увеличенный блок пикселей вокруг
|
||||||
|
точки с перекрестием и подписью hex текущего пикселя; клик выбирает центр
|
||||||
|
перекрестия.
|
||||||
|
4. Активная пипетка видна на превью перекрестием курсора; повторный клик по
|
||||||
|
кнопке отменяет режим без выбора.
|
||||||
|
5. Всё работает поверх текущего процесса: авто-применение с задержкой, токен
|
||||||
|
запусков, маски — без изменений.
|
||||||
|
|
||||||
## 2. Слайдеры
|
## 2. Слайдеры
|
||||||
|
|
||||||
- В реестре новый тип параметра `'slider'` с обязательными `min`/`max`; валидируется как число (дефолт в диапазоне, минимум меньше максимума).
|
- В реестре новый тип параметра `'slider'` с обязательными `min`/`max`;
|
||||||
- Форма маппит его на готовый `SliderField` из `ui/`; санитизация параметров обрабатывает slider так же, как number.
|
валидируется как число (дефолт в диапазоне, минимум меньше максимума).
|
||||||
- Переводятся: яркость и контраст, качество JPEG, качество WebP, порог похожести. Остальные инструменты — без изменений.
|
- Форма маппит его на готовый `SliderField` из `ui/`; санитизация параметров
|
||||||
|
обрабатывает slider так же, как number.
|
||||||
|
- Переводятся: яркость и контраст, качество JPEG, качество WebP, порог
|
||||||
|
похожести. Остальные инструменты — без изменений.
|
||||||
|
|
||||||
## 3. Пипетка и лупа
|
## 3. Пипетка и лупа
|
||||||
|
|
||||||
- В ядро добавляется чистая функция `rgbToHex(r, g, b)` → строка вида `#rrggbb`, с юнит-тестами; чтение пикселя — из уже отрисованного canvas превью исходника через `getImageData(x, y, 1, 1)`.
|
- В ядро добавляется чистая функция `rgbToHex(r, g, b)` → строка вида `#rrggbb`,
|
||||||
- `Preview.svelte` получает опциональный режим пипетки: пропсы «активна» и колбэк «цвет выбран»; курсор перекрестие, клик вычисляет координату пикселя с учётом разницы CSS-размера canvas и его внутренней ширины.
|
с юнит-тестами; чтение пикселя — из уже отрисованного canvas превью исходника
|
||||||
- **Лупа** — компонент `PipetteLoupe.svelte`: канвас около 110×110 px у курсора (позиция fixed, `pointer-events: none`); рисует блок 15×15 пикселей из исходника с увеличением ×8 без сглаживания, перекрестие в центре, подпись hex текущего пикселя. Координаты блока клампятся у краёв изображения; лупа скрыта вне превью и вне режима пипетки. Тач-устройства: прямой тап выбирает пиксель без лупы.
|
через `getImageData(x, y, 1, 1)`.
|
||||||
- Проводка: кнопка у каждого цветового поля в карточке параметров → состояние «активное цветовое поле» живёт в оркестраторе → выбор цвета записывает hex в это поле и выключает режим.
|
- `Preview.svelte` получает опциональный режим пипетки: пропсы «активна» и
|
||||||
- Пипетка работает только по левой панели (исходнику): удаляемый цвет и подложка берутся с оригинала.
|
колбэк «цвет выбран»; курсор перекрестие, клик вычисляет координату пикселя с
|
||||||
|
учётом разницы CSS-размера canvas и его внутренней ширины.
|
||||||
|
- **Лупа** — компонент `PipetteLoupe.svelte`: канвас около 110×110 px у курсора
|
||||||
|
(позиция fixed, `pointer-events: none`); рисует блок 15×15 пикселей из
|
||||||
|
исходника с увеличением ×8 без сглаживания, перекрестие в центре, подпись hex
|
||||||
|
текущего пикселя. Координаты блока клампятся у краёв изображения; лупа скрыта
|
||||||
|
вне превью и вне режима пипетки. Тач-устройства: прямой тап выбирает пиксель
|
||||||
|
без лупы.
|
||||||
|
- Проводка: кнопка у каждого цветового поля в карточке параметров → состояние
|
||||||
|
«активное цветовое поле» живёт в оркестраторе → выбор цвета записывает hex в
|
||||||
|
это поле и выключает режим.
|
||||||
|
- Пипетка работает только по левой панели (исходнику): удаляемый цвет и подложка
|
||||||
|
берутся с оригинала.
|
||||||
|
|
||||||
## 4. Этапы и проверки
|
## 4. Этапы и проверки
|
||||||
|
|
||||||
- **A. Слайдеры** — тип в реестре, перевод четырёх инструментов, тесты реестра и санитизации, тест `rgbToHex`. Проверка: `pnpm test/check`, визуально слайдеры.
|
- **A. Слайдеры** — тип в реестре, перевод четырёх инструментов, тесты реестра и
|
||||||
- **B. Пипетка** — режим в Preview, проводка через карточки до полей цвета. Проверка: смоук §6 пп. 5–7.
|
санитизации, тест `rgbToHex`. Проверка: `pnpm test/check`, визуально слайдеры.
|
||||||
- **B2. Лупа** — компонент лупы поверх режима пипетки. Проверка: смоук §6 пп. 8–10.
|
- **B. Пипетка** — режим в Preview, проводка через карточки до полей цвета.
|
||||||
|
Проверка: смоук §6 пп. 5–7.
|
||||||
|
- **B2. Лупа** — компонент лупы поверх режима пипетки. Проверка: смоук §6 пп.
|
||||||
|
8–10.
|
||||||
- **C. Итог** — полный прогон `test/check/build`.
|
- **C. Итог** — полный прогон `test/check/build`.
|
||||||
|
|
||||||
## 5. Изменения по файлам
|
## 5. Изменения по файлам
|
||||||
|
|
||||||
- `registry.ts` — тип `'slider'` в `ParamDef`, перевод четырёх записей, ветка в `sanitizeParams`.
|
- `registry.ts` — тип `'slider'` в `ParamDef`, перевод четырёх записей, ветка в
|
||||||
|
`sanitizeParams`.
|
||||||
- `ParamForm.svelte` — ветка slider → `SliderField`.
|
- `ParamForm.svelte` — ветка slider → `SliderField`.
|
||||||
- `core/color.ts` — `rgbToHex`.
|
- `core/color.ts` — `rgbToHex`.
|
||||||
- `Preview.svelte` — режим пипетки (курсор, клик, колбэк).
|
- `Preview.svelte` — режим пипетки (курсор, клик, колбэк).
|
||||||
@@ -44,21 +71,29 @@
|
|||||||
|
|
||||||
## 6. Смоук-чеклист
|
## 6. Смоук-чеклист
|
||||||
|
|
||||||
1. Яркость/контраст — слайдеры со значением справа, результат обновляется сам при перетаскивании.
|
1. Яркость/контраст — слайдеры со значением справа, результат обновляется сам
|
||||||
|
при перетаскивании.
|
||||||
2. Качество JPEG/WebP — слайдер от 1 до 100.
|
2. Качество JPEG/WebP — слайдер от 1 до 100.
|
||||||
3. Порог похожести — слайдер от 0 до 100.
|
3. Порог похожести — слайдер от 0 до 100.
|
||||||
4. Resize и crop остались числовыми полями.
|
4. Resize и crop остались числовыми полями.
|
||||||
5. Удаление цвета: «Пипетка» → клик по красной области исходника → поле цвета получил hex этого пикселя, режим погас, результат пересчитался.
|
5. Удаление цвета: «Пипетка» → клик по красной области исходника → поле цвета
|
||||||
6. Повторный клик по кнопке или выбор без подтверждения — режим снят, цвет не изменился.
|
получил hex этого пикселя, режим погас, результат пересчитался.
|
||||||
|
6. Повторный клик по кнопке или выбор без подтверждения — режим снят, цвет не
|
||||||
|
изменился.
|
||||||
7. Подложка JPEG: пипетка проставляет цвет подложки тем же способом.
|
7. Подложка JPEG: пипетка проставляет цвет подложки тем же способом.
|
||||||
8. В режиме пипетки лупа следует за курсором, перекрестие показывает пиксель под ним, подпись hex совпадает с выбранным при клике.
|
8. В режиме пипетки лупа следует за курсором, перекрестие показывает пиксель под
|
||||||
9. У края картинки блок лупи корректно клампится — за границей тянется чёрный/прозрачный фон, выбор по-прежнему точен.
|
ним, подпись hex совпадает с выбранным при клике.
|
||||||
10. Курсор ушёл с превью или режим выключен — лупа скрыта; клики по кнопкам под превью не перехватываются лупой.
|
9. У края картинки блок лупи корректно клампится — за границей тянется
|
||||||
11. Слайдеры и пипетка дружат с быстрыми правками: одна финальная версия результата, чип «Пересчёт…» на месте.
|
чёрный/прозрачный фон, выбор по-прежнему точен.
|
||||||
|
10. Курсор ушёл с превью или режим выключен — лупа скрыта; клики по кнопкам под
|
||||||
|
превью не перехватываются лупой.
|
||||||
|
11. Слайдеры и пипетка дружат с быстрыми правками: одна финальная версия
|
||||||
|
результата, чип «Пересчёт…» на месте.
|
||||||
|
|
||||||
## 7. Критерии готовности
|
## 7. Критерии готовности
|
||||||
|
|
||||||
- Все процентные параметры — слайдеры; числовые поля остались только там, где нужен точный ввод.
|
- Все процентные параметры — слайдеры; числовые поля остались только там, где
|
||||||
|
нужен точный ввод.
|
||||||
- Цвет любого color-параметра можно взять кликом с исходника.
|
- Цвет любого color-параметра можно взять кликом с исходника.
|
||||||
- `test/check/build` зелёные, чеклист §6 пройден.
|
- `test/check/build` зелёные, чеклист §6 пройден.
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +1,26 @@
|
|||||||
# План: тестирование preview перед промоушеном в главную
|
# План: тестирование preview перед промоушеном в главную
|
||||||
|
|
||||||
> Статус: **реализация завершена**. Playwright-набор в `web/e2e/`
|
> Статус: **реализация завершена**. Playwright-набор в `web/e2e/` (108 тестов:
|
||||||
> (108 тестов: 104 прошли, 4 `test.fixme` — известные баги №1–4 ниже),
|
> 104 прошли, 4 `test.fixme` — известные баги №1–4 ниже), ручной чек-лист —
|
||||||
> ручной чек-лист — `docs/checklist-manual-testing.md`. План ветки preview
|
> `docs/checklist-manual-testing.md`. План ветки preview
|
||||||
> (`web/src/routes/preview/**`, регистр `lib/registry-new/`, 121 инструмент)
|
> (`web/src/routes/preview/**`, регистр `lib/registry-new/`, 121 инструмент)
|
||||||
> перед шагом C17 из `docs/plan-redesign.md` (переезд preview → реальные
|
> перед шагом C17 из `docs/plan-redesign.md` (переезд preview → реальные
|
||||||
> маршруты).
|
> маршруты).
|
||||||
>
|
>
|
||||||
> Охват: **документация плана** — реализация тестов (Playwright-раннер,
|
> Охват: **документация плана** — реализация тестов (Playwright-раннер, сами
|
||||||
> сами тесты) делается отдельными задачами после ревью этого документа.
|
> тесты) делается отдельными задачами после ревью этого документа.
|
||||||
>
|
>
|
||||||
> Приоритет: **отсутствие падений/ошибок** > пиксельная корректность.
|
> Приоритет: **отсутствие падений/ошибок** > пиксельная корректность. Дизайн
|
||||||
> Дизайн доделываем по ходу, визуальная сверка — ручная и вторичная.
|
> доделываем по ходу, визуальная сверка — ручная и вторичная.
|
||||||
>
|
>
|
||||||
> Команды для проверки в процессе:
|
> Команды для проверки в процессе:
|
||||||
>
|
>
|
||||||
> - `pnpm --dir web test` — существующий Vitest (42 файла, 121 инструмент частично покрыт юнит-тестами).
|
> - `pnpm --dir web test` — существующий Vitest (42 файла, 121 инструмент
|
||||||
> - `pnpm --dir web test:e2e` — Playwright (`web/e2e/`), сборка + `scripts/serve-static.mjs` на 4173.
|
> частично покрыт юнит-тестами).
|
||||||
> - `pnpm --dir web exec svelte-check --tsconfig ./tsconfig.json` — проверка типов.
|
> - `pnpm --dir web test:e2e` — Playwright (`web/e2e/`), сборка +
|
||||||
|
> `scripts/serve-static.mjs` на 4173.
|
||||||
|
> - `pnpm --dir web exec svelte-check --tsconfig ./tsconfig.json` — проверка
|
||||||
|
> типов.
|
||||||
> - `pnpm --dir web lint:all` — ESLint + stylelint + токен-аудит.
|
> - `pnpm --dir web lint:all` — ESLint + stylelint + токен-аудит.
|
||||||
> - `pnpm --dir web build` — статическая сборка (adapter-static).
|
> - `pnpm --dir web build` — статическая сборка (adapter-static).
|
||||||
|
|
||||||
@@ -44,21 +47,21 @@ GEOMETRY / FILTERS / TEXT / ANALYZE / GENERATE. Режимы инструмен
|
|||||||
- **text-output** (`result: "text"`, file): `toText` → текстовый результат;
|
- **text-output** (`result: "text"`, file): `toText` → текстовый результат;
|
||||||
- **generate**: без входа, `executeGenerate` → картинка.
|
- **generate**: без входа, `executeGenerate` → картинка.
|
||||||
|
|
||||||
Исполнение: `preview/executor` — worker с fallback на main-thread
|
Исполнение: `preview/executor` — worker с fallback на main-thread (domOnly / нет
|
||||||
(domOnly / нет Worker / ошибка worker). Auto-run с debounce 200 мс.
|
Worker / ошибка worker). Auto-run с debounce 200 мс.
|
||||||
|
|
||||||
## 1. Функциональные блоки для проверки
|
## 1. Функциональные блоки для проверки
|
||||||
|
|
||||||
### A. Инфраструктура и навигация
|
### A. Инфраструктура и навигация
|
||||||
|
|
||||||
1. Все 4 маршрута + 404 открываются без console errors, uncaught exceptions
|
1. Все 4 маршрута + 404 открываются без console errors, uncaught exceptions и
|
||||||
и failed requests.
|
failed requests.
|
||||||
2. Поиск на `/preview` — фильтрация по названию/описанию, пустой результат →
|
2. Поиск на `/preview` — фильтрация по названию/описанию, пустой результат →
|
||||||
аккуратное empty-state.
|
аккуратное empty-state.
|
||||||
3. Каталог `/preview/list-tools` — 8 групп, счётчик 121, фильтр категории,
|
3. Каталог `/preview/list-tools` — 8 групп, счётчик 121, фильтр категории,
|
||||||
иконки не падают.
|
иконки не падают.
|
||||||
4. Тема light/dark и язык RU/EN переключаются на превью-страницах без
|
4. Тема light/dark и язык RU/EN переключаются на превью-страницах без регресса
|
||||||
регресса рендера.
|
рендера.
|
||||||
5. 404 на несуществующий id инструмента → EmptyState, без краша.
|
5. 404 на несуществующий id инструмента → EmptyState, без краша.
|
||||||
6. Prerender: `pnpm --dir web build` проходит; все 121 страницы
|
6. Prerender: `pnpm --dir web build` проходит; все 121 страницы
|
||||||
`/preview/tools/[id]` генерируются (проверка `entries()`/`getTool`).
|
`/preview/tools/[id]` генерируются (проверка `entries()`/`getTool`).
|
||||||
@@ -66,7 +69,8 @@ GEOMETRY / FILTERS / TEXT / ANALYZE / GENERATE. Режимы инструмен
|
|||||||
### B. Конвейер инструмента (file-инструменты)
|
### B. Конвейер инструмента (file-инструменты)
|
||||||
|
|
||||||
7. Upload → авто-run → результатный тайл появился, поле ошибки пустое.
|
7. Upload → авто-run → результатный тайл появился, поле ошибки пустое.
|
||||||
8. Download — событие download, имя `<toolId>.<ext>`, MIME соответствует `output`.
|
8. Download — событие download, имя `<toolId>.<ext>`, MIME соответствует
|
||||||
|
`output`.
|
||||||
9. Reset — возвращает дефолты, сбрасывает результат и ошибку.
|
9. Reset — возвращает дефолты, сбрасывает результат и ошибку.
|
||||||
10. Смена параметра → авто-перезапуск (debounce 200 мс) без «залипания»
|
10. Смена параметра → авто-перезапуск (debounce 200 мс) без «залипания»
|
||||||
running-стейта.
|
running-стейта.
|
||||||
@@ -75,8 +79,8 @@ GEOMETRY / FILTERS / TEXT / ANALYZE / GENERATE. Режимы инструмен
|
|||||||
### C. Текстовые инструменты и вердикты
|
### C. Текстовые инструменты и вердикты
|
||||||
|
|
||||||
12. Text-source: ввод → текстовый результат или картинка; copy/download .txt.
|
12. Text-source: ввод → текстовый результат или картинка; copy/download .txt.
|
||||||
13. Вердикты (analyze): корректный вердикт на PNG с альфой, на PNG без альфы,
|
13. Вердикты (analyze): корректный вердикт на PNG с альфой, на PNG без альфы, на
|
||||||
на не-PNG входе → без падений.
|
не-PNG входе → без падений.
|
||||||
14. Пустой text-source → run пропускается без ошибки.
|
14. Пустой text-source → run пропускается без ошибки.
|
||||||
|
|
||||||
### D. Генераторы
|
### D. Генераторы
|
||||||
@@ -103,11 +107,11 @@ GEOMETRY / FILTERS / TEXT / ANALYZE / GENERATE. Режимы инструмен
|
|||||||
|
|
||||||
### ✅ Playwright — детерминированные сквозные потоки и «нет падений»
|
### ✅ Playwright — детерминированные сквозные потоки и «нет падений»
|
||||||
|
|
||||||
- Все маршруты + 404: открытие, отсутствие console errors / uncaught
|
- Все маршруты + 404: открытие, отсутствие console errors / uncaught exceptions
|
||||||
exceptions / failed requests (слушатели `page.on('console'/'pageerror')`).
|
/ failed requests (слушатели `page.on('console'/'pageerror')`).
|
||||||
- Поиск и каталог: фильтрация, счётчики, переходы.
|
- Поиск и каталог: фильтрация, счётчики, переходы.
|
||||||
- Upload (`setInputFiles` с фикстурой PNG) → авто-результат → Download
|
- Upload (`setInputFiles` с фикстурой PNG) → авто-результат → Download (ожидание
|
||||||
(ожидание `download` события, проверка имени/расширения/suggestedFilename).
|
`download` события, проверка имени/расширения/suggestedFilename).
|
||||||
- Reset / смена параметра → перегенерация, running не «залипает».
|
- Reset / смена параметра → перегенерация, running не «залипает».
|
||||||
- Тема light/dark и язык RU/EN.
|
- Тема light/dark и язык RU/EN.
|
||||||
- Текстовые инструменты: ввод → текстовый результат → копия/скачивание.
|
- Текстовые инструменты: ввод → текстовый результат → копия/скачивание.
|
||||||
@@ -129,12 +133,12 @@ GEOMETRY / FILTERS / TEXT / ANALYZE / GENERATE. Режимы инструмен
|
|||||||
- **Краевые PNG:** большие (мегапиксели), 1×1, анимация, палитровые,
|
- **Краевые PNG:** большие (мегапиксели), 1×1, анимация, палитровые,
|
||||||
чекерборд/полупрозрачность, повреждённые файлы → нет зависаний, понятные
|
чекерборд/полупрозрачность, повреждённые файлы → нет зависаний, понятные
|
||||||
сообщения об ошибке.
|
сообщения об ошибке.
|
||||||
- **Края параметров:** экстремальные значения, NaN/пустые поля, инвалидные
|
- **Края параметров:** экстремальные значения, NaN/пустые поля, инвалидные цвета
|
||||||
цвета в плашках/градиентах → корректная санитизация (silent repair).
|
в плашках/градиентах → корректная санитизация (silent repair).
|
||||||
- **Производительность/отзывчивость:** задержка авто-реранов на слайдерах,
|
- **Производительность/отзывчивость:** задержка авто-реранов на слайдерах, нет
|
||||||
нет «залипания» UI, скролл при длинных настройках.
|
«залипания» UI, скролл при длинных настройках.
|
||||||
- **Визуальный слой дизайна** (доделываем по ходу): сетки, выравнивание,
|
- **Визуальный слой дизайна** (доделываем по ходу): сетки, выравнивание, отступы
|
||||||
отступы на брейкпоинтах mobile/tablet/desktop (640/800/1100).
|
на брейкпоинтах mobile/tablet/desktop (640/800/1100).
|
||||||
- **Буфер обмена** (copy) в реальном браузере.
|
- **Буфер обмена** (copy) в реальном браузере.
|
||||||
- **Drag-n-drop** файла на dropzone (UX-ощущения).
|
- **Drag-n-drop** файла на dropzone (UX-ощущения).
|
||||||
- **Разные браузеры:** Firefox/Safari-нюансы (webkit в Playwright опционален).
|
- **Разные браузеры:** Firefox/Safari-нюансы (webkit в Playwright опционален).
|
||||||
@@ -144,8 +148,8 @@ GEOMETRY / FILTERS / TEXT / ANALYZE / GENERATE. Режимы инструмен
|
|||||||
## 4. Критерии приёмки (доступ в главную)
|
## 4. Критерии приёмки (доступ в главную)
|
||||||
|
|
||||||
1. Все 4 маршрута + 404 открываются без console errors и исключений.
|
1. Все 4 маршрута + 404 открываются без console errors и исключений.
|
||||||
2. Репрезентативный прогон по 8 категориям: ни один инструмент не падает и
|
2. Репрезентативный прогон по 8 категориям: ни один инструмент не падает и не
|
||||||
не показывает поле ошибки.
|
показывает поле ошибки.
|
||||||
3. Upload → результат → download завершается для file-инструментов;
|
3. Upload → результат → download завершается для file-инструментов;
|
||||||
текст/вердикты/генераторы дают корректный output-тип.
|
текст/вердикты/генераторы дают корректный output-тип.
|
||||||
4. `pnpm --dir web test`, `pnpm --dir web lint:all`, `pnpm --dir web build`
|
4. `pnpm --dir web test`, `pnpm --dir web lint:all`, `pnpm --dir web build`
|
||||||
@@ -156,17 +160,17 @@ GEOMETRY / FILTERS / TEXT / ANALYZE / GENERATE. Режимы инструмен
|
|||||||
|
|
||||||
- **~60–70%** «нет падений/ошибок» закрывается Playwright детерминированно
|
- **~60–70%** «нет падений/ошибок» закрывается Playwright детерминированно
|
||||||
(маршруты, сквозные потоки, отсутствие ошибок).
|
(маршруты, сквозные потоки, отсутствие ошибок).
|
||||||
- **~30–40%** — руками: краевые PNG/параметры, реальные пользовательские
|
- **~30–40%** — руками: краевые PNG/параметры, реальные пользовательские файлы,
|
||||||
файлы, буфер обмена, кросс-браузеры, визуальный/производительный слой,
|
буфер обмена, кросс-браузеры, визуальный/производительный слой, субъективная
|
||||||
субъективная корректность.
|
корректность.
|
||||||
|
|
||||||
## 6. Шаги реализации (после ревью)
|
## 6. Шаги реализации (после ревью)
|
||||||
|
|
||||||
1. ✅ Playwright-раннер: `web/playwright.config.ts` (webServer на `pnpm build +
|
1. ✅ Playwright-раннер: `web/playwright.config.ts` (webServer на
|
||||||
`scripts/serve-static.mjs` `--port 4173`), папка `web/e2e/`, npm-скрипт
|
`pnpm build + `scripts/serve-static.mjs` `--port
|
||||||
`test:e2e`.
|
4173`), папка `web/e2e/`, npm-скрипт `test:e2e`.
|
||||||
2. ✅ Фикстуры PNG (генерируются в рантайме, `web/e2e/helpers/fixtures.ts`):
|
2. ✅ Фикстуры PNG (генерируются в рантайме, `web/e2e/helpers/fixtures.ts`): с
|
||||||
с альфой, без альфы, 1×1, большой, повреждённый, ландшафт.
|
альфой, без альфы, 1×1, большой, повреждённый, ландшафт.
|
||||||
3. ✅ E2E по блокам A–D и секции 3 (авто-часть): `navigation.spec.ts`,
|
3. ✅ E2E по блокам A–D и секции 3 (авто-часть): `navigation.spec.ts`,
|
||||||
`catalog.spec.ts`, `pipeline.spec.ts`, `text-and-verdicts.spec.ts`,
|
`catalog.spec.ts`, `pipeline.spec.ts`, `text-and-verdicts.spec.ts`,
|
||||||
`tools-smoke.spec.ts` (матрица ~71 инструмента), `generators.spec.ts`,
|
`tools-smoke.spec.ts` (матрица ~71 инструмента), `generators.spec.ts`,
|
||||||
@@ -174,14 +178,13 @@ GEOMETRY / FILTERS / TEXT / ANALYZE / GENERATE. Режимы инструмен
|
|||||||
4. ✅ Ручной чек-лист из секции 3 (ручная часть) —
|
4. ✅ Ручной чек-лист из секции 3 (ручная часть) —
|
||||||
`docs/checklist-manual-testing.md` (разделы A–G, включая известные баги).
|
`docs/checklist-manual-testing.md` (разделы A–G, включая известные баги).
|
||||||
5. ✅ Найденные баги (зафиксированы как `test.fixme` + чек-лист §G):
|
5. ✅ Найденные баги (зафиксированы как `test.fixme` + чек-лист §G):
|
||||||
1. генераторы (21/121) не имеют кнопки «Generate» и полей схемы —
|
1. генераторы (21/121) не имеют кнопки «Generate» и полей схемы — результат
|
||||||
результат через UI недостижим;
|
через UI недостижим;
|
||||||
2. `resize-png` с дефолтом 0×0 → `errors.resizeSize` при любом входе;
|
2. `resize-png` с дефолтом 0×0 → `errors.resizeSize` при любом входе;
|
||||||
3. `crop-png` с дефолтом 0×0 → `errors.cropSize` при любом входе;
|
3. `crop-png` с дефолтом 0×0 → `errors.cropSize` при любом входе;
|
||||||
4. ключи ошибок показываются сырыми (`errors.*`) вместо переведённого
|
4. ключи ошибок показываются сырыми (`errors.*`) вместо переведённого текста
|
||||||
текста из `en.ts`/`ru.ts`.
|
из `en.ts`/`ru.ts`. Исправления — отдельными атомарными задачами по
|
||||||
Исправления — отдельными атомарными задачами по конвенции AGENTS.md
|
конвенции AGENTS.md (после правок: `lint:all`, `test:e2e`).
|
||||||
(после правок: `lint:all`, `test:e2e`).
|
|
||||||
|
|
||||||
## 7. Итоговое состояние на момент сдачи
|
## 7. Итоговое состояние на момент сдачи
|
||||||
|
|
||||||
|
|||||||
@@ -4,41 +4,78 @@
|
|||||||
|
|
||||||
## 1. Что получается
|
## 1. Что получается
|
||||||
|
|
||||||
Четыре инструмента новой категории «Текст»: надпись на изображении, дата-штамп, защитная плитка (повторяющийся полупрозрачный текст под углом), водяной знак картинкой. Каталог вырастает с 69 до 73. Это первые операции, рисующие через canvas 2D — класс perceptual по роадмапу: точные пиксельные тесты на них не пишутся, юнит-тестами покрывается чистая математика раскладки.
|
Четыре инструмента новой категории «Текст»: надпись на изображении, дата-штамп,
|
||||||
|
защитная плитка (повторяющийся полупрозрачный текст под углом), водяной знак
|
||||||
|
картинкой. Каталог вырастает с 69 до 73. Это первые операции, рисующие через
|
||||||
|
canvas 2D — класс perceptual по роадмапу: точные пиксельные тесты на них не
|
||||||
|
пишутся, юнит-тестами покрывается чистая математика раскладки.
|
||||||
|
|
||||||
## 2. Решения
|
## 2. Решения
|
||||||
|
|
||||||
- Вся геометрия — чистые функции без DOM: якорь из 9-позиционной сетки → координаты, перенос текста по ширине с инъекцией измерителя, раскладка плитки с углом и шагом. Canvas-вызовы — тонкий слой рядом (`core/textdraw.ts`), в юнит-тесты не попадает.
|
- Вся геометрия — чистые функции без DOM: якорь из 9-позиционной сетки →
|
||||||
- Один движок рендера текста на все три текстовых инструмента: шрифт из системного набора (sans/serif/mono), размер, цвет, жирность, прозрачность, поворот. Различия инструментов — только в пресетах параметров и количестве слоёв (одна надпись vs плитка).
|
координаты, перенос текста по ширине с инъекцией измерителя, раскладка плитки
|
||||||
- Дата-штамп — надпись с автотекстом: строка формата (`YYYY-MM-DD hh:mm`) разворачивается в текущую дату в момент запуска; свой мини-форматтер без зависимостей.
|
с углом и шагом. Canvas-вызовы — тонкий слой рядом (`core/textdraw.ts`), в
|
||||||
- Водяной знак картинкой — первый двухисточниковый инструмент: вторая картинка грузится отдельной зоной в этапе, живёт только в состоянии страницы и сознательно не сохраняется ни в workspace-pipeline, ни в экспорте JSON; после восстановления цепочки знак нужно выбрать заново. Ограничение фиксируем в описании инструмента.
|
юнит-тесты не попадает.
|
||||||
- Новая категория `text` встаёт после «Фильтров»; названия в обоих словарях, порядок в CATEGORIES один.
|
- Один движок рендера текста на все три текстовых инструмента: шрифт из
|
||||||
|
системного набора (sans/serif/mono), размер, цвет, жирность, прозрачность,
|
||||||
|
поворот. Различия инструментов — только в пресетах параметров и количестве
|
||||||
|
слоёв (одна надпись vs плитка).
|
||||||
|
- Дата-штамп — надпись с автотекстом: строка формата (`YYYY-MM-DD hh:mm`)
|
||||||
|
разворачивается в текущую дату в момент запуска; свой мини-форматтер без
|
||||||
|
зависимостей.
|
||||||
|
- Водяной знак картинкой — первый двухисточниковый инструмент: вторая картинка
|
||||||
|
грузится отдельной зоной в этапе, живёт только в состоянии страницы и
|
||||||
|
сознательно не сохраняется ни в workspace-pipeline, ни в экспорте JSON; после
|
||||||
|
восстановления цепочки знак нужно выбрать заново. Ограничение фиксируем в
|
||||||
|
описании инструмента.
|
||||||
|
- Новая категория `text` встаёт после «Фильтров»; названия в обоих словарях,
|
||||||
|
порядок в CATEGORIES один.
|
||||||
|
|
||||||
## 3. Инструменты
|
## 3. Инструменты
|
||||||
|
|
||||||
- `add-text-png` — параметры: текст (строка), размер (слайдер 8–200), цвет, шрифт (select), жирность (чекбокс), позиция (select 3×3: углы/стороны/центр), отступ (слайдер). Опциональная подложка: цвет + прозрачность плашки за текстом.
|
- `add-text-png` — параметры: текст (строка), размер (слайдер 8–200), цвет,
|
||||||
- `date-stamp-png` — тот же набор минус текст, плюс формат даты; дефолт — нижний правый угол.
|
шрифт (select), жирность (чекбокс), позиция (select 3×3: углы/стороны/центр),
|
||||||
- `watermark-tile-png` — текст, размер, цвет, прозрачность (0–100), угол (-90…90), шаг по X/Y; плитка заполняет весь холст.
|
отступ (слайдер). Опциональная подложка: цвет + прозрачность плашки за
|
||||||
- `watermark-image-png` — второй источник (загрузка PNG с альфой), масштаб знака (% от ширины холста), прозрачность, позиция 3×3, отступ.
|
текстом.
|
||||||
|
- `date-stamp-png` — тот же набор минус текст, плюс формат даты; дефолт — нижний
|
||||||
|
правый угол.
|
||||||
|
- `watermark-tile-png` — текст, размер, цвет, прозрачность (0–100), угол
|
||||||
|
(-90…90), шаг по X/Y; плитка заполняет весь холст.
|
||||||
|
- `watermark-image-png` — второй источник (загрузка PNG с альфой), масштаб знака
|
||||||
|
(% от ширины холста), прозрачность, позиция 3×3, отступ.
|
||||||
|
|
||||||
## 4. Этапы и проверки
|
## 4. Этапы и проверки
|
||||||
|
|
||||||
Каждый этап заканчивается зелёными test/check/build и ревью.
|
Каждый этап заканчивается зелёными test/check/build и ревью.
|
||||||
|
|
||||||
- **A. Фундамент** — категория `text` (categories + словари + порядок), `core/textdraw.ts`: anchor9, wrap, tileLayout с юнит-тестами на математике (координаты углов/центра, переносы слов, плотность плитки), тонкий canvas-модуль `domText.ts` (без тестов). Реестр пока пуст в категории.
|
- **A. Фундамент** — категория `text` (categories + словари + порядок),
|
||||||
- **B. Надпись и дата** — `add-text-png`, `date-stamp-png` на общем движке; иконки, переводы, записи реестра. Смоук: обе локали, тёмная тема (плашка читаема), длинный текст переносится и не вылезает за край.
|
`core/textdraw.ts`: anchor9, wrap, tileLayout с юнит-тестами на математике
|
||||||
- **C. Плитка** — `watermark-tile-png`; смоук: отрицательные и нулевой угол, прозрачность 0/50/100, мелкий шаг не подвешивает страницу на больших холстах (ограничение количества плиток капом).
|
(координаты углов/центра, переносы слов, плотность плитки), тонкий
|
||||||
- **D. Знак картинкой** — двухисточниковый UX: вторая drop-зона в этапе (компонент рядом с SourceCard, виден только у этого инструмента), масштаб/позиция/отступ; ограничение про сохранение — в описании и подсказке. Этап режется без ущерба остальным, если решим отложить.
|
canvas-модуль `domText.ts` (без тестов). Реестр пока пуст в категории.
|
||||||
|
- **B. Надпись и дата** — `add-text-png`, `date-stamp-png` на общем движке;
|
||||||
|
иконки, переводы, записи реестра. Смоук: обе локали, тёмная тема (плашка
|
||||||
|
читаема), длинный текст переносится и не вылезает за край.
|
||||||
|
- **C. Плитка** — `watermark-tile-png`; смоук: отрицательные и нулевой угол,
|
||||||
|
прозрачность 0/50/100, мелкий шаг не подвешивает страницу на больших холстах
|
||||||
|
(ограничение количества плиток капом).
|
||||||
|
- **D. Знак картинкой** — двухисточниковый UX: вторая drop-зона в этапе
|
||||||
|
(компонент рядом с SourceCard, виден только у этого инструмента),
|
||||||
|
масштаб/позиция/отступ; ограничение про сохранение — в описании и подсказке.
|
||||||
|
Этап режется без ущерба остальным, если решим отложить.
|
||||||
- **E. Итог** — полный прогон, смоук §6, архивация плана.
|
- **E. Итог** — полный прогон, смоук §6, архивация плана.
|
||||||
|
|
||||||
## 5. Критерии готовности
|
## 5. Критерии готовности
|
||||||
|
|
||||||
- 73 инструмента; вся математика раскладки покрыта юнит-тестами; canvas-слой изолирован в одном модуле.
|
- 73 инструмента; вся математика раскладки покрыта юнит-тестами; canvas-слой
|
||||||
- test/check/build зелёные; новые инструменты работают в цепочке как обычные шаги (кроме источника знака — см. ограничение).
|
изолирован в одном модуле.
|
||||||
|
- test/check/build зелёные; новые инструменты работают в цепочке как обычные
|
||||||
|
шаги (кроме источника знака — см. ограничение).
|
||||||
|
|
||||||
## 6. Что сознательно не делаем
|
## 6. Что сознательно не делаем
|
||||||
|
|
||||||
- Выбор произвольных пользовательских шрифтов (файлами) и веб-шрифтов — только системный набор.
|
- Выбор произвольных пользовательских шрифтов (файлами) и веб-шрифтов — только
|
||||||
- Кернинг/линии/многострочное редактирование с абзацами — один абзац с автопереносом.
|
системный набор.
|
||||||
|
- Кернинг/линии/многострочное редактирование с абзацами — один абзац с
|
||||||
|
автопереносом.
|
||||||
- Сохранение картинки-знака в пайплайне и её передача между сессиями.
|
- Сохранение картинки-знака в пайплайне и её передача между сессиями.
|
||||||
- Текст по контуру, градиентные заливки текста.
|
- Текст по контуру, градиентные заливки текста.
|
||||||
|
|||||||
+25
-10
@@ -4,30 +4,44 @@
|
|||||||
|
|
||||||
## 1. Что получается
|
## 1. Что получается
|
||||||
|
|
||||||
Переключатель light/dark в шапке рядом с языковым. Выбор в localStorage (`theme`), первый визит — по `prefers-color-scheme`. Тёмная палитра — переопределение токенов селектором `[data-theme='dark']`; компоненты не правятся.
|
Переключатель light/dark в шапке рядом с языковым. Выбор в localStorage
|
||||||
|
(`theme`), первый визит — по `prefers-color-scheme`. Тёмная палитра —
|
||||||
|
переопределение токенов селектором `[data-theme='dark']`; компоненты не
|
||||||
|
правятся.
|
||||||
|
|
||||||
## 2. Решения
|
## 2. Решения
|
||||||
|
|
||||||
- Два состояния, без «авто»: ключ хранит только явный выбор, отсутствие ключа = системная тема.
|
- Два состояния, без «авто»: ключ хранит только явный выбор, отсутствие ключа =
|
||||||
- Анти-вспышка: инлайн-скрипт в `app.html` до гидрации ставит `data-theme` из localStorage или `matchMedia`; пререндеренная разметка остаётся светлой.
|
системная тема.
|
||||||
- `color-scheme: dark` на корне — нативные контролы (input[type=color], range, select, скроллбары) темнеют без правок.
|
- Анти-вспышка: инлайн-скрипт в `app.html` до гидрации ставит `data-theme` из
|
||||||
- Модуль `theme.svelte.ts` по образцу locale: состояние, персист, применение атрибута. Стартовую установку делает скрипт из `app.html`, модуль синхронизирует runes-состояние после гидрации.
|
localStorage или `matchMedia`; пререндеренная разметка остаётся светлой.
|
||||||
|
- `color-scheme: dark` на корне — нативные контролы (input[type=color], range,
|
||||||
|
select, скроллбары) темнеют без правок.
|
||||||
|
- Модуль `theme.svelte.ts` по образцу locale: состояние, персист, применение
|
||||||
|
атрибута. Стартовую установку делает скрипт из `app.html`, модуль
|
||||||
|
синхронизирует runes-состояние после гидрации.
|
||||||
|
|
||||||
## 3. Палитра
|
## 3. Палитра
|
||||||
|
|
||||||
- Переопределяем: `--bg`, `--surface`, `--border`, `--text`, `--text-muted`.
|
- Переопределяем: `--bg`, `--surface`, `--border`, `--text`, `--text-muted`.
|
||||||
- Акцент `#2563eb` не меняется; `--accent-hover` — светлее для тёмного фона.
|
- Акцент `#2563eb` не меняется; `--accent-hover` — светлее для тёмного фона.
|
||||||
- `--danger` осветлить для читаемости.
|
- `--danger` осветлить для читаемости.
|
||||||
- `--check-a`/`--check-b` шахматки — приглушённые серые: паттерн прозрачности остаётся узнаваемым, но не слепит.
|
- `--check-a`/`--check-b` шахматки — приглушённые серые: паттерн прозрачности
|
||||||
|
остаётся узнаваемым, но не слепит.
|
||||||
- `--shadow-card` — почти чёрная с низкой альфой вместо синей.
|
- `--shadow-card` — почти чёрная с низкой альфой вместо синей.
|
||||||
- Все `color-mix(… var(--surface))` пересчитаются сами — проверить визуально легенды, recalc-бейдж, drag-оверлеи.
|
- Все `color-mix(… var(--surface))` пересчитаются сами — проверить визуально
|
||||||
|
легенды, recalc-бейдж, drag-оверлеи.
|
||||||
|
|
||||||
## 4. Этапы и проверки
|
## 4. Этапы и проверки
|
||||||
|
|
||||||
Каждый этап заканчивается зелёными test/check/build и ревью.
|
Каждый этап заканчивается зелёными test/check/build и ревью.
|
||||||
|
|
||||||
- **A. Инфраструктура** — `theme.svelte.ts`, скрипт в `app.html`, переключатель в шапке, черновая тёмная палитра. Тесты: персист выбора, игнор мусора в localStorage, применение атрибута (стаб document).
|
- **A. Инфраструктура** — `theme.svelte.ts`, скрипт в `app.html`, переключатель
|
||||||
- **B. Палитра** — выверка значений: герой, каталог, страница инструмента, цепочка, баннер ошибки, маска, текстовые инструменты; фокус-ринги и hover; обе локали.
|
в шапке, черновая тёмная палитра. Тесты: персист выбора, игнор мусора в
|
||||||
|
localStorage, применение атрибута (стаб document).
|
||||||
|
- **B. Палитра** — выверка значений: герой, каталог, страница инструмента,
|
||||||
|
цепочка, баннер ошибки, маска, текстовые инструменты; фокус-ринги и hover; обе
|
||||||
|
локали.
|
||||||
|
|
||||||
## 5. Смоук
|
## 5. Смоук
|
||||||
|
|
||||||
@@ -43,4 +57,5 @@ Grep: ни одного цвета вне токенов в компонента
|
|||||||
|
|
||||||
## 7. Не делаем
|
## 7. Не делаем
|
||||||
|
|
||||||
Три позиции в переключателе; пользовательские цветовые схемы; автосмену по времени суток; отдельную тему для превью изображений.
|
Три позиции в переключателе; пользовательские цветовые схемы; автосмену по
|
||||||
|
времени суток; отдельную тему для превью изображений.
|
||||||
|
|||||||
@@ -4,57 +4,87 @@
|
|||||||
|
|
||||||
## 1. Зачем
|
## 1. Зачем
|
||||||
|
|
||||||
- Страница инструмента (`ToolPage.svelte`) разрослась: состояние процесса, загрузка файлов, вставка из буфера и разметка трёх зон живут в одном файле.
|
- Страница инструмента (`ToolPage.svelte`) разрослась: состояние процесса,
|
||||||
- Назревают два требования к процессу: параметры применяются автоматически без кнопки; у части утилит на экране показывается одно — визуализация фильтра/маски, а скачивается (и в будущем передаётся следующему инструменту) другое — результат.
|
загрузка файлов, вставка из буфера и разметка трёх зон живут в одном файле.
|
||||||
|
- Назревают два требования к процессу: параметры применяются автоматически без
|
||||||
|
кнопки; у части утилит на экране показывается одно — визуализация
|
||||||
|
фильтра/маски, а скачивается (и в будущем передаётся следующему инструменту)
|
||||||
|
другое — результат.
|
||||||
|
|
||||||
## 2. Контракт «результат и маска» в реестре
|
## 2. Контракт «результат и маска» в реестре
|
||||||
|
|
||||||
- `run(img, params)` — как сейчас: возвращает итоговое изображение. Его скачивают и будут передавать дальше по цепочке.
|
- `run(img, params)` — как сейчас: возвращает итоговое изображение. Его
|
||||||
- Запись реестра получает опциональное поле `preview?(img, params)` — возвращает картинку для показа на экране (маску/фильтр).
|
скачивают и будут передавать дальше по цепочке.
|
||||||
- Если у записи есть `preview`, в карточке результата появляется переключатель «Показать маску». Включён — на превью маска, выключен (по умолчанию) — результат. Скачивание всегда отдаёт результат `run`, независимо от положения переключателя.
|
- Запись реестра получает опциональное поле `preview?(img, params)` — возвращает
|
||||||
- Первая реализация — удаление цвета: в ядро добавляется чистая функция `colorMask(img, hex, tolerance)` — чёрно-белая маска, где удаляемые пиксели белые; с юнит-тестами; `preview` записи вызывает её.
|
картинку для показа на экране (маску/фильтр).
|
||||||
|
- Если у записи есть `preview`, в карточке результата появляется переключатель
|
||||||
|
«Показать маску». Включён — на превью маска, выключен (по умолчанию) —
|
||||||
|
результат. Скачивание всегда отдаёт результат `run`, независимо от положения
|
||||||
|
переключателя.
|
||||||
|
- Первая реализация — удаление цвета: в ядро добавляется чистая функция
|
||||||
|
`colorMask(img, hex, tolerance)` — чёрно-белая маска, где удаляемые пиксели
|
||||||
|
белые; с юнит-тестами; `preview` записи вызывает её.
|
||||||
|
|
||||||
## 3. Декомпозиция страницы
|
## 3. Декомпозиция страницы
|
||||||
|
|
||||||
Новая папка `components/tool/`; карточки — чисто отображательные, состояние живёт только в оркестраторе:
|
Новая папка `components/tool/`; карточки — чисто отображательные, состояние
|
||||||
|
живёт только в оркестраторе:
|
||||||
|
|
||||||
- **`SourceCard.svelte`** — левая половина сцены: пусто → зона перетаскивания; загружено → превью исходника под оверлеем замены с кнопкой «Заменить изображение».
|
- **`SourceCard.svelte`** — левая половина сцены: пусто → зона перетаскивания;
|
||||||
- **`ResultCard.svelte`** — правая половина: пустые состояния, превью результата или сводка, чип «Пересчёт…», кнопка скачивания, при наличии `preview` — переключатель маски.
|
загружено → превью исходника под оверлеем замены с кнопкой «Заменить
|
||||||
- **`ParamsCard.svelte`** — форма параметров и подпись «Изменения применяются автоматически»; для инструментов без параметров не выводится.
|
изображение».
|
||||||
- **`ToolPage.svelte`** остаётся оркестратором: состояние (исходник, результат, сводка, статус, ошибка, значения), декодирование файлов, вставка из буфера, сброс, токен запусков и эффект с задержкой. Ожидаемо худеет примерно вдвое.
|
- **`ResultCard.svelte`** — правая половина: пустые состояния, превью результата
|
||||||
|
или сводка, чип «Пересчёт…», кнопка скачивания, при наличии `preview` —
|
||||||
|
переключатель маски.
|
||||||
|
- **`ParamsCard.svelte`** — форма параметров и подпись «Изменения применяются
|
||||||
|
автоматически»; для инструментов без параметров не выводится.
|
||||||
|
- **`ToolPage.svelte`** остаётся оркестратором: состояние (исходник, результат,
|
||||||
|
сводка, статус, ошибка, значения), декодирование файлов, вставка из буфера,
|
||||||
|
сброс, токен запусков и эффект с задержкой. Ожидаемо худеет примерно вдвое.
|
||||||
|
|
||||||
Карточки переиспользуются будущим многошаговым режимом.
|
Карточки переиспользуются будущим многошаговым режимом.
|
||||||
|
|
||||||
## 4. Реактивные параметры (в этом же проходе)
|
## 4. Реактивные параметры (в этом же проходе)
|
||||||
|
|
||||||
- Кнопки «Применить» больше нет ни у одного инструмента.
|
- Кнопки «Применить» больше нет ни у одного инструмента.
|
||||||
- Изменение параметра запускает вычисление через 300 мс после последнего изменения; каждое новое изменение перезапускает таймер.
|
- Изменение параметра запускает вычисление через 300 мс после последнего
|
||||||
- Токен запусков: если во время вычисления параметры поменялись, устаревший ответ отбрасывается.
|
изменения; каждое новое изменение перезапускает таймер.
|
||||||
- Загрузка и замена файла считают сразу и подавляют ровно один авто-запуск эффекта.
|
- Токен запусков: если во время вычисления параметры поменялись, устаревший
|
||||||
|
ответ отбрасывается.
|
||||||
|
- Загрузка и замена файла считают сразу и подавляют ровно один авто-запуск
|
||||||
|
эффекта.
|
||||||
- Ошибка пересчёта — баннер сверху, прежний результат не сбрасывается.
|
- Ошибка пересчёта — баннер сверху, прежний результат не сбрасывается.
|
||||||
|
|
||||||
## 5. Этапы и проверки
|
## 5. Этапы и проверки
|
||||||
|
|
||||||
- **A. Декомпозиция** — перенос разметки в три карточки один к одному, поведение не меняется. Проверка: `check/build`, визуально идентично.
|
- **A. Декомпозиция** — перенос разметки в три карточки один к одному, поведение
|
||||||
- **B. Реактивность** — токен, задержка, чип пересчёта, минус кнопка. Проверка: смоук §6 пп. 1–6.
|
не меняется. Проверка: `check/build`, визуально идентично.
|
||||||
- **C. Маска** — типы реестра, `colorMask` с тестами, переключатель в карточке результата, пример на удалении цвета. Проверка: тесты + смоук §6 пп. 7–9.
|
- **B. Реактивность** — токен, задержка, чип пересчёта, минус кнопка. Проверка:
|
||||||
|
смоук §6 пп. 1–6.
|
||||||
|
- **C. Маска** — типы реестра, `colorMask` с тестами, переключатель в карточке
|
||||||
|
результата, пример на удалении цвета. Проверка: тесты + смоук §6 пп. 7–9.
|
||||||
- **D. Итог** — полный прогон `test/check/build`.
|
- **D. Итог** — полный прогон `test/check/build`.
|
||||||
|
|
||||||
## 6. Смоук-чеклист
|
## 6. Смоук-чеклист
|
||||||
|
|
||||||
1. Загрузка файла → результат появляется сразу.
|
1. Загрузка файла → результат появляется сразу.
|
||||||
2. Изменить параметр → около 0.3 с и результат обновился сам.
|
2. Изменить параметр → около 0.3 с и результат обновился сам.
|
||||||
3. Быстро изменить яркость несколько раз → одна финальная версия, промежуточные не мелькают.
|
3. Быстро изменить яркость несколько раз → одна финальная версия, промежуточные
|
||||||
|
не мелькают.
|
||||||
4. Во время пересчёта чип виден, предыдущий результат не исчезает.
|
4. Во время пересчёта чип виден, предыдущий результат не исчезает.
|
||||||
5. Инструмент без параметров и информационная страница ведут себя как раньше.
|
5. Инструмент без параметров и информационная страница ведут себя как раньше.
|
||||||
6. Некорректные параметры (обрезка вне картинки) → баннер ошибки, прежний результат цел.
|
6. Некорректные параметры (обрезка вне картинки) → баннер ошибки, прежний
|
||||||
7. Переключатель «Показать маску» у удаления цвета: включён — белым по чёрному видно, что удалится; выключен — обычный полупрозрачный результат.
|
результат цел.
|
||||||
8. С включённой маской скачивание всё равно сохраняет прозрачный результат, а не маску.
|
7. Переключатель «Показать маску» у удаления цвета: включён — белым по чёрному
|
||||||
|
видно, что удалится; выключен — обычный полупрозрачный результат.
|
||||||
|
8. С включённой маской скачивание всё равно сохраняет прозрачный результат, а не
|
||||||
|
маску.
|
||||||
9. Замена исходника возвращает переключатель маски в выключенное состояние.
|
9. Замена исходника возвращает переключатель маски в выключенное состояние.
|
||||||
|
|
||||||
## 7. Критерии готовности
|
## 7. Критерии готовности
|
||||||
|
|
||||||
- `ToolPage` содержит только оркестрацию состояния и эффектов; вся разметка панелей — в `components/tool/*`.
|
- `ToolPage` содержит только оркестрацию состояния и эффектов; вся разметка
|
||||||
|
панелей — в `components/tool/*`.
|
||||||
- Кнопки «Применить» нет ни на одной странице.
|
- Кнопки «Применить» нет ни на одной странице.
|
||||||
- Скачивание всегда соответствует `run`; экран может показывать `preview`.
|
- Скачивание всегда соответствует `run`; экран может показывать `preview`.
|
||||||
- `test/check/build` зелёные, смоук §6 пройден.
|
- `test/check/build` зелёные, смоук §6 пройден.
|
||||||
@@ -62,5 +92,7 @@
|
|||||||
## 8. Что сознательно не делаем
|
## 8. Что сознательно не делаем
|
||||||
|
|
||||||
- Вынос вычислений в веб-воркер; настраиваемая задержка — фиксированные 300 мс.
|
- Вынос вычислений в веб-воркер; настраиваемая задержка — фиксированные 300 мс.
|
||||||
- Редактор масок (кисть, слои, история) — только автогенерация из параметров инструмента.
|
- Редактор масок (кисть, слои, история) — только автогенерация из параметров
|
||||||
- Передача результата между инструментами — придёт вместе с многошаговым режимом позже.
|
инструмента.
|
||||||
|
- Передача результата между инструментами — придёт вместе с многошаговым режимом
|
||||||
|
позже.
|
||||||
|
|||||||
@@ -4,48 +4,89 @@
|
|||||||
>
|
>
|
||||||
> **Что сделано по этапам:**
|
> **Что сделано по этапам:**
|
||||||
>
|
>
|
||||||
> - A — общая MIME-валидация `isSupportedImage` + `unsupportedImageMessage` в `core/io.ts`; `DropZone` переведён на них ✔
|
> - A — общая MIME-валидация `isSupportedImage` + `unsupportedImageMessage` в
|
||||||
> - B — постоянная двухпанельная раскладка: одна карточка-«сцена» с равными половинами «Исходник | Результат», кнопки внутри своих половин, параметры — отдельной карточкой ниже; контент сайта растянут на всю ширину окна ✔
|
> `core/io.ts`; `DropZone` переведён на них ✔
|
||||||
> - C — Ctrl+V: `<svelte:window onpaste>`, поиск image-item в буфере, общая валидация; текстовые вставки не затрагиваются ✔
|
> - B — постоянная двухпанельная раскладка: одна карточка-«сцена» с равными
|
||||||
> - D — drag-замена: компонент `DropOverlay` с вуалью-подсказкой и счётчиком `dragenter/dragleave` против мерцания; тот же счётчик в `DropZone` ✔
|
> половинами «Исходник | Результат», кнопки внутри своих половин, параметры —
|
||||||
> - E — верификация: тесты 85/85, svelte-check 0 ошибок, статический билд с 11 страницами; ручной смоук-чеклист §5 пройден ✔
|
> отдельной карточкой ниже; контент сайта растянут на всю ширину окна ✔
|
||||||
|
> - C — Ctrl+V: `<svelte:window onpaste>`, поиск image-item в буфере, общая
|
||||||
|
> валидация; текстовые вставки не затрагиваются ✔
|
||||||
|
> - D — drag-замена: компонент `DropOverlay` с вуалью-подсказкой и счётчиком
|
||||||
|
> `dragenter/dragleave` против мерцания; тот же счётчик в `DropZone` ✔
|
||||||
|
> - E — верификация: тесты 85/85, svelte-check 0 ошибок, статический билд с 11
|
||||||
|
> страницами; ручной смоук-чеклист §5 пройден ✔
|
||||||
>
|
>
|
||||||
> **Отступления от исходного плана в ходе реализации:** вместо пропа `overlay` у `DropZone` сделан отдельный компонент-обёртка `DropOverlay` (drag-события ловит контент под курсором); раскладка доработана по ревью — одна карточка равных половин вместо двух независимых колонок, сайт без ограничения ширины.
|
> **Отступления от исходного плана в ходе реализации:** вместо пропа `overlay` у
|
||||||
|
> `DropZone` сделан отдельный компонент-обёртка `DropOverlay` (drag-события
|
||||||
|
> ловит контент под курсором); раскладка доработана по ревью — одна карточка
|
||||||
|
> равных половин вместо двух независимых колонок, сайт без ограничения ширины.
|
||||||
|
|
||||||
## 1. Целевое поведение
|
## 1. Целевое поведение
|
||||||
|
|
||||||
1. **Постоянная двухпанельная раскладка** страницы инструмента — отдельного состояния «страница загрузки» больше нет:
|
1. **Постоянная двухпанельная раскладка** страницы инструмента — отдельного
|
||||||
- **Слева — исходник**, панель есть всегда. Не загружено → пустое дроп-поле (drag&drop или клик). Загружено → превью исходника.
|
состояния «страница загрузки» больше нет:
|
||||||
- **Справа — результат**: до загрузки — `EmptyState` «Результат появится здесь»; во время обработки — `EmptyState` «Обработка…»; после — превью результата (для info-инструмента — `InfoPanel`).
|
- **Слева — исходник**, панель есть всегда. Не загружено → пустое дроп-поле
|
||||||
2. **Ctrl+V** — картинка из буфера обмена вставляется как исходник с любого места страницы.
|
(drag&drop или клик). Загружено → превью исходника.
|
||||||
3. **Замена перетаскиванием** — новый файл, брошенный на левую панель с загруженной картинкой, заменяет исходник (с оверлеем-подсказкой во время перетаскивания).
|
- **Справа — результат**: до загрузки — `EmptyState` «Результат появится
|
||||||
4. **Кнопка «Заменить»** под превью исходника (переносится туда текущая «Загрузить другое изображение»).
|
здесь»; во время обработки — `EmptyState` «Обработка…»; после — превью
|
||||||
|
результата (для info-инструмента — `InfoPanel`).
|
||||||
|
2. **Ctrl+V** — картинка из буфера обмена вставляется как исходник с любого
|
||||||
|
места страницы.
|
||||||
|
3. **Замена перетаскиванием** — новый файл, брошенный на левую панель с
|
||||||
|
загруженной картинкой, заменяет исходник (с оверлеем-подсказкой во время
|
||||||
|
перетаскивания).
|
||||||
|
4. **Кнопка «Заменить»** под превью исходника (переносится туда текущая
|
||||||
|
«Загрузить другое изображение»).
|
||||||
|
|
||||||
На мобильной ширине панели складываются в столбец: исходник сверху, результат снизу.
|
На мобильной ширине панели складываются в столбец: исходник сверху, результат
|
||||||
|
снизу.
|
||||||
|
|
||||||
## 2. Изменения по файлам
|
## 2. Изменения по файлам
|
||||||
|
|
||||||
Новые компоненты не вводим — `EmptyState`, `Preview`, `DropZone` покрывают всё. Меняем три файла:
|
Новые компоненты не вводим — `EmptyState`, `Preview`, `DropZone` покрывают всё.
|
||||||
|
Меняем три файла:
|
||||||
|
|
||||||
- **`core/io.ts`** — экспорт `isSupportedImage(file): boolean`: общая MIME-проверка на базе `ACCEPTED_IMAGE_TYPES`.
|
- **`core/io.ts`** — экспорт `isSupportedImage(file): boolean`: общая
|
||||||
- **`DropZone.svelte`** — переходит на `isSupportedImage`; новый опциональный проп `overlay` — режим «невидимая зона приёма поверх контента» для замены перетаскиванием.
|
MIME-проверка на базе `ACCEPTED_IMAGE_TYPES`.
|
||||||
- **`ToolPage.svelte`** — постоянная сетка из двух панелей; `<svelte:window onpaste>`; drag-оверлей левой панели; кнопки замены/сброса переезжают в левую панель; правая панель — три состояния через `EmptyState`.
|
- **`DropZone.svelte`** — переходит на `isSupportedImage`; новый опциональный
|
||||||
|
проп `overlay` — режим «невидимая зона приёма поверх контента» для замены
|
||||||
|
перетаскиванием.
|
||||||
|
- **`ToolPage.svelte`** — постоянная сетка из двух панелей;
|
||||||
|
`<svelte:window onpaste>`; drag-оверлей левой панели; кнопки замены/сброса
|
||||||
|
переезжают в левую панель; правая панель — три состояния через `EmptyState`.
|
||||||
|
|
||||||
## 3. Этапы и проверки
|
## 3. Этапы и проверки
|
||||||
|
|
||||||
- **A. Общая валидация** — `isSupportedImage` в `io.ts`, `DropZone` переведён на неё. Проверка: `pnpm check`.
|
- **A. Общая валидация** — `isSupportedImage` в `io.ts`, `DropZone` переведён на
|
||||||
- **B. Раскладка** — две постоянные колонки в ToolPage; правая панель через EmptyState; кнопки уходят в левую панель; обёртка `{#if !source}` на всю страницу убирается. Проверка: визуальный осмотр всех инструментов, `check/build`.
|
неё. Проверка: `pnpm check`.
|
||||||
- **C. Ctrl+V** — `onpaste` на окне → поиск image-item в `clipboardData` → тот же `handleFile`; не-изображения игнорируются молча. Проверка: ручной смоук со скриншотом из буфера.
|
- **B. Раскладка** — две постоянные колонки в ToolPage; правая панель через
|
||||||
- **D. Drag-замена** — оверлей DropZone(overlay) при dragover на левой панели; drop → `handleFile` нового файла. Проверка: ручной смоук «бросить файл на загруженный исходник».
|
EmptyState; кнопки уходят в левую панель; обёртка `{#if !source}` на всю
|
||||||
- **E. Итог** — полный ручной смоук-чеклист (§5). Проверка: `test/check/build` зелёные.
|
страницу убирается. Проверка: визуальный осмотр всех инструментов,
|
||||||
|
`check/build`.
|
||||||
|
- **C. Ctrl+V** — `onpaste` на окне → поиск image-item в `clipboardData` → тот
|
||||||
|
же `handleFile`; не-изображения игнорируются молча. Проверка: ручной смоук со
|
||||||
|
скриншотом из буфера.
|
||||||
|
- **D. Drag-замена** — оверлей DropZone(overlay) при dragover на левой панели;
|
||||||
|
drop → `handleFile` нового файла. Проверка: ручной смоук «бросить файл на
|
||||||
|
загруженный исходник».
|
||||||
|
- **E. Итог** — полный ручной смоук-чеклист (§5). Проверка: `test/check/build`
|
||||||
|
зелёные.
|
||||||
|
|
||||||
## 4. Нюансы и решения
|
## 4. Нюансы и решения
|
||||||
|
|
||||||
- **Мерцание dragleave** — классическая проблема оверлеев: используем счётчик `dragenter/dragleave`, оверлей скрывается только когда счётчик обнулился и произошёл `drop`/`dragend`.
|
- **Мерцание dragleave** — классическая проблема оверлеев: используем счётчик
|
||||||
- **Paste при фокусе в поле параметров** — обрабатываем всегда: если в буфере именно изображение, пользователь очевидно хочет заменить исходник; текстовые вставки обработчик не трогает.
|
`dragenter/dragleave`, оверлей скрывается только когда счётчик обнулился и
|
||||||
- **Форматы буфера** — скриншоты приходят как PNG, часть приложений отдаёт JPEG/WebP; все проходят общую `isSupportedImage`. GIF/BMP из буфера редки, но тоже допустимы.
|
произошёл `drop`/`dragend`.
|
||||||
- **Info-инструменты** — ведут себя так же: слева исходник, справа после загрузки появляется `InfoPanel`.
|
- **Paste при фокусе в поле параметров** — обрабатываем всегда: если в буфере
|
||||||
- **Состояние ошибки** — баннер остаётся сверху страницы; неудачная замена не сбрасывает старый исходник.
|
именно изображение, пользователь очевидно хочет заменить исходник; текстовые
|
||||||
|
вставки обработчик не трогает.
|
||||||
|
- **Форматы буфера** — скриншоты приходят как PNG, часть приложений отдаёт
|
||||||
|
JPEG/WebP; все проходят общую `isSupportedImage`. GIF/BMP из буфера редки, но
|
||||||
|
тоже допустимы.
|
||||||
|
- **Info-инструменты** — ведут себя так же: слева исходник, справа после
|
||||||
|
загрузки появляется `InfoPanel`.
|
||||||
|
- **Состояние ошибки** — баннер остаётся сверху страницы; неудачная замена не
|
||||||
|
сбрасывает старый исходник.
|
||||||
|
|
||||||
## 5. Чеклист ручного смоука
|
## 5. Чеклист ручного смоука
|
||||||
|
|
||||||
@@ -54,7 +95,8 @@
|
|||||||
3. Ctrl+V со скриншотом → исходник заменился, результат пересчитался.
|
3. Ctrl+V со скриншотом → исходник заменился, результат пересчитался.
|
||||||
4. Drag файла на загруженную левую панель → оверлей виден, после drop — замена.
|
4. Drag файла на загруженную левую панель → оверлей виден, после drop — замена.
|
||||||
5. Отмена перетаскивания (Esc / мимо) → оверлей скрылся, исходник не изменился.
|
5. Отмена перетаскивания (Esc / мимо) → оверлей скрылся, исходник не изменился.
|
||||||
6. Бросок неподдерживаемого файла (например .txt) → баннер ошибки, старый исходник цел.
|
6. Бросок неподдерживаемого файла (например .txt) → баннер ошибки, старый
|
||||||
|
исходник цел.
|
||||||
7. Кнопка «Заменить» открывает выбор файла.
|
7. Кнопка «Заменить» открывает выбор файла.
|
||||||
8. png-info: работает та же схема, справа сводка.
|
8. png-info: работает та же схема, справа сводка.
|
||||||
9. Мобильная ширина: панели в столбец, порядок исходник → результат.
|
9. Мобильная ширина: панели в столбец, порядок исходник → результат.
|
||||||
@@ -62,7 +104,8 @@
|
|||||||
|
|
||||||
## 6. Критерии готовности
|
## 6. Критерии готовности
|
||||||
|
|
||||||
- На странице инструмента нет состояния «только дропзона на всю страницу» — раскладка двухпанельная всегда.
|
- На странице инструмента нет состояния «только дропзона на всю страницу» —
|
||||||
|
раскладка двухпанельная всегда.
|
||||||
- Загрузка работает тремя способами: клик, drag&drop (включая замену), Ctrl+V.
|
- Загрузка работает тремя способами: клик, drag&drop (включая замену), Ctrl+V.
|
||||||
- Ошибочные файлы и отмена перетаскивания не портят текущий исходник.
|
- Ошибочные файлы и отмена перетаскивания не портят текущий исходник.
|
||||||
- `test/check/build` зелёные; чеклист §5 пройден.
|
- `test/check/build` зелёные; чеклист §5 пройден.
|
||||||
@@ -71,4 +114,5 @@
|
|||||||
|
|
||||||
- Множественная загрузка / пакетная обработка — вне скоупа этого плана.
|
- Множественная загрузка / пакетная обработка — вне скоупа этого плана.
|
||||||
- Камера/скриншот-инструменты захвата — только буфер обмена.
|
- Камера/скриншот-инструменты захвата — только буфер обмена.
|
||||||
- Прогресс-бары декодирования — файлы читаются быстро, достаточно индикации «Обработка…».
|
- Прогресс-бары декодирования — файлы читаются быстро, достаточно индикации
|
||||||
|
«Обработка…».
|
||||||
|
|||||||
@@ -1,75 +1,130 @@
|
|||||||
# План: главная страница как рабочая область с цепочкой
|
# План: главная страница как рабочая область с цепочкой
|
||||||
|
|
||||||
> **СТАТУС: ВЫПОЛНЕН 2026-08-23.** Заменяет идею отдельной страницы рабочего пространства.
|
> **СТАТУС: ВЫПОЛНЕН 2026-08-23.** Заменяет идею отдельной страницы рабочего
|
||||||
|
> пространства.
|
||||||
|
|
||||||
## 1. Модель
|
## 1. Модель
|
||||||
|
|
||||||
1. **Главная страница — это рабочая область.** Без выбранного инструмента на ней большой поиск: начните вводить название — список отфильтровывается; выбор разворачивает инструмент прямо здесь, без перехода.
|
1. **Главная страница — это рабочая область.** Без выбранного инструмента на ней
|
||||||
2. Развернутый инструмент выглядит как привычная страница: слева исходник, справа результат со своей кнопкой скачивания, параметры — в отдельной карточке под сценой.
|
большой поиск: начните вводить название — список отфильтровывается; выбор
|
||||||
3. Под результатом рядом с кнопкой скачивания — кнопка **«⛓ Chain with…»**: добавляет пустую секцию, внутри которой такой же поиск; выбор заполняет секцию следующим инструментом. Так цепочка растёт сколько угодно.
|
разворачивает инструмент прямо здесь, без перехода.
|
||||||
4. Каждая заполненная секция цепи — полноценная: свои параметры, своё превью, своя кнопка скачивания промежуточного результата. Крестик убирает секцию; кнопка «✂ Remove chain» снимает всю цепь разом (без подтверждения).
|
2. Развернутый инструмент выглядит как привычная страница: слева исходник,
|
||||||
5. Чекбокс «Показать маску» живёт в карточке параметров базового инструмента и влияет только на его превью: в цепь всегда передаётся сам результат.
|
справа результат со своей кнопкой скачивания, параметры — в отдельной
|
||||||
6. Сетка-каталог инструментов переезжает на отдельную страницу `/list-tools` — для SEO и визуального поиска. Основная работа происходит на главной; в шапке появляются ссылки на обе.
|
карточке под сценой.
|
||||||
7. Прямые адреса `/tools/<id>` сохраняются: тот же развернутый инструмент для глубоких ссылок.
|
3. Под результатом рядом с кнопкой скачивания — кнопка **«⛓ Chain with…»**:
|
||||||
8. Цепочка переживает перезагрузку (автосохранение в localStorage). Экспорт/импорт файлом отложен до появления CLI/wasm-части.
|
добавляет пустую секцию, внутри которой такой же поиск; выбор заполняет
|
||||||
|
секцию следующим инструментом. Так цепочка растёт сколько угодно.
|
||||||
|
4. Каждая заполненная секция цепи — полноценная: свои параметры, своё превью,
|
||||||
|
своя кнопка скачивания промежуточного результата. Крестик убирает секцию;
|
||||||
|
кнопка «✂ Remove chain» снимает всю цепь разом (без подтверждения).
|
||||||
|
5. Чекбокс «Показать маску» живёт в карточке параметров базового инструмента и
|
||||||
|
влияет только на его превью: в цепь всегда передаётся сам результат.
|
||||||
|
6. Сетка-каталог инструментов переезжает на отдельную страницу `/list-tools` —
|
||||||
|
для SEO и визуального поиска. Основная работа происходит на главной; в шапке
|
||||||
|
появляются ссылки на обе.
|
||||||
|
7. Прямые адреса `/tools/<id>` сохраняются: тот же развернутый инструмент для
|
||||||
|
глубоких ссылок.
|
||||||
|
8. Цепочка переживает перезагрузку (автосохранение в localStorage).
|
||||||
|
Экспорт/импорт файлом отложен до появления CLI/wasm-части.
|
||||||
|
|
||||||
## 2. Архитектура
|
## 2. Архитектура
|
||||||
|
|
||||||
- Страница инструмента уже собрана как самодостаточный компонент, принимающий запись реестра, — он и становится встраиваемым блоком. Маршруты `/tools/[id]` и главная используют один и тот же компонент: первая — по адресу из URL, вторая — по выбору из поиска.
|
- Страница инструмента уже собрана как самодостаточный компонент, принимающий
|
||||||
- Состояние главной: выбран ли инструмент; если да — рендерится блок инструмента, целиком владеющий своим исходником, параметрами, результатом и цепочкой.
|
запись реестра, — он и становится встраиваемым блоком. Маршруты `/tools/[id]`
|
||||||
|
и главная используют один и тот же компонент: первая — по адресу из URL,
|
||||||
|
вторая — по выбору из поиска.
|
||||||
|
- Состояние главной: выбран ли инструмент; если да — рендерится блок
|
||||||
|
инструмента, целиком владеющий своим исходником, параметрами, результатом и
|
||||||
|
цепочкой.
|
||||||
- Смена инструмента на главной (через повторный поиск) сбрасывает блок целиком.
|
- Смена инструмента на главной (через повторный поиск) сбрасывает блок целиком.
|
||||||
- Цепочка хранится массивом шагов; у пустого звена инструмент ещё не выбран — такие звенья не сохраняются в localStorage и не участвуют в прогоне.
|
- Цепочка хранится массивом шагов; у пустого звена инструмент ещё не выбран —
|
||||||
- Исполнение — последовательная свёртка: выход каждого звена — вход следующего; результаты всех звеньев доступны их секциям. Общий авто-раннер даёт токен отмены и задержку ~300 мс.
|
такие звенья не сохраняются в localStorage и не участвуют в прогоне.
|
||||||
|
- Исполнение — последовательная свёртка: выход каждого звена — вход следующего;
|
||||||
|
результаты всех звеньев доступны их секциям. Общий авто-раннер даёт токен
|
||||||
|
отмены и задержку ~300 мс.
|
||||||
|
|
||||||
## 3. Переиспользование и доработки существующего
|
## 3. Переиспользование и доработки существующего
|
||||||
|
|
||||||
Готовое берётся как есть: `SourceCard`, `Preview`, `ParamForm`, `DownloadButton`, `EmptyState`, модуль пайплайна, авто-раннер, `PNG_OUTPUT`.
|
Готовое берётся как есть: `SourceCard`, `Preview`, `ParamForm`,
|
||||||
|
`DownloadButton`, `EmptyState`, модуль пайплайна, авто-раннер, `PNG_OUTPUT`.
|
||||||
|
|
||||||
Доработки:
|
Доработки:
|
||||||
|
|
||||||
- **Блок страницы инструмента** — расширяется секцией цепочки: список заполненных звеньев, каждое со своими параметрами, превью и скачиванием, плюс пустые звенья с поиском. Кнопки «⛓» и «✂ Remove chain» — рядом со скачиванием базового результата.
|
- **Блок страницы инструмента** — расширяется секцией цепочки: список
|
||||||
- **`ParamsCard`** — опциональный блок «Показать маску» (флаг preview у инструмента + двусторонний флаг).
|
заполненных звеньев, каждое со своими параметрами, превью и скачиванием, плюс
|
||||||
- **Карточка результата** — упрощается: получает готовую картинку, маска уходит в карточку параметров.
|
пустые звенья с поиском. Кнопки «⛓» и «✂ Remove chain» — рядом со скачиванием
|
||||||
- **Существующая главная-каталог** — переезжает на маршрут `/list-tools` почти без изменений.
|
базового результата.
|
||||||
|
- **`ParamsCard`** — опциональный блок «Показать маску» (флаг preview у
|
||||||
|
инструмента + двусторонний флаг).
|
||||||
|
- **Карточка результата** — упрощается: получает готовую картинку, маска уходит
|
||||||
|
в карточку параметров.
|
||||||
|
- **Существующая главная-каталог** — переезжает на маршрут `/list-tools` почти
|
||||||
|
без изменений.
|
||||||
|
|
||||||
Удаляется: черновой маршрут рабочего пространства и его компоненты списка/селектора — вместо них новые, под вертикальную модель.
|
Удаляется: черновой маршрут рабочего пространства и его компоненты
|
||||||
|
списка/селектора — вместо них новые, под вертикальную модель.
|
||||||
|
|
||||||
## 4. Новые компоненты
|
## 4. Новые компоненты
|
||||||
|
|
||||||
- **`ToolSearch.svelte`** — поиск с нечётким совпадением: поле ввода, отфильтрованный список (регистронезависимая подпоследовательность по названию и описанию, приоритет совпадению с начала), клавиатура ↑↓ Enter Esc; два размера — герой на главной и компактный внутри пустого звена. Предлагает только инструменты-трансформеры.
|
- **`ToolSearch.svelte`** — поиск с нечётким совпадением: поле ввода,
|
||||||
- **`ChainStep.svelte`** — секция одного звена: пустое звено показывает компактный поиск; заполненное — заголовок с номером и крестиком, форму параметров, превью результата, кнопку скачивания.
|
отфильтрованный список (регистронезависимая подпоследовательность по названию
|
||||||
- **`ChainBar.svelte`** — строка под результатом базового инструмента: «⛓ Chain with…» и «✂ Remove chain» (видна только при непустой цепи).
|
и описанию, приоритет совпадению с начала), клавиатура ↑↓ Enter Esc; два
|
||||||
|
размера — герой на главной и компактный внутри пустого звена. Предлагает
|
||||||
|
только инструменты-трансформеры.
|
||||||
|
- **`ChainStep.svelte`** — секция одного звена: пустое звено показывает
|
||||||
|
компактный поиск; заполненное — заголовок с номером и крестиком, форму
|
||||||
|
параметров, превью результата, кнопку скачивания.
|
||||||
|
- **`ChainBar.svelte`** — строка под результатом базового инструмента: «⛓ Chain
|
||||||
|
with…» и «✂ Remove chain» (видна только при непустой цепи).
|
||||||
|
|
||||||
## 5. Этапы и проверки
|
## 5. Этапы и проверки
|
||||||
|
|
||||||
- **A. Поиск и главная** — `ToolSearch`, герой поиска на главной, встраивание блока инструмента, перенос каталога на `/list-tools`, ссылки в шапке. Проверка: `check/build`, визуально.
|
- **A. Поиск и главная** — `ToolSearch`, герой поиска на главной, встраивание
|
||||||
- **B. Цепочка** — состояние цепи в блоке инструмента, пустые звенья с поиском, прогон, промежуточные превью и скачивания, ошибки звеньев. Проверка: смоук §8 пп. 1–6.
|
блока инструмента, перенос каталога на `/list-tools`, ссылки в шапке.
|
||||||
- **C. Маска и упрощение** — блок маски в `ParamsCard`, упрощение карточки результата. Проверка: смоук §8 п. 7.
|
Проверка: `check/build`, визуально.
|
||||||
- **D. Хранение и итог** — автосохранение и восстановление цепочки, удаление чернового маршрута рабочего пространства, полный регресс каталога, `test/check/build`.
|
- **B. Цепочка** — состояние цепи в блоке инструмента, пустые звенья с поиском,
|
||||||
|
прогон, промежуточные превью и скачивания, ошибки звеньев. Проверка: смоук §8
|
||||||
|
пп. 1–6.
|
||||||
|
- **C. Маска и упрощение** — блок маски в `ParamsCard`, упрощение карточки
|
||||||
|
результата. Проверка: смоук §8 п. 7.
|
||||||
|
- **D. Хранение и итог** — автосохранение и восстановление цепочки, удаление
|
||||||
|
чернового маршрута рабочего пространства, полный регресс каталога,
|
||||||
|
`test/check/build`.
|
||||||
|
|
||||||
## 6. Смоук-чеклист
|
## 6. Смоук-чеклист
|
||||||
|
|
||||||
1. На главной — большое поле поиска; ввод фильтрует список по мере набора; Enter выбирает подсвеченный.
|
1. На главной — большое поле поиска; ввод фильтрует список по мере набора; Enter
|
||||||
2. Выбранный инструмент разворачивается на той же странице: исходник, параметры, результат.
|
выбирает подсвеченный.
|
||||||
3. «⛓ Chain with…» добавляет пустую секцию с компактным поиском; выбор заполняет её.
|
2. Выбранный инструмент разворачивается на той же странице: исходник, параметры,
|
||||||
|
результат.
|
||||||
|
3. «⛓ Chain with…» добавляет пустую секцию с компактным поиском; выбор заполняет
|
||||||
|
её.
|
||||||
4. У каждой секции своё превью и своё скачивание промежуточного результата.
|
4. У каждой секции своё превью и своё скачивание промежуточного результата.
|
||||||
5. Крестик убирает секцию; «✂ Remove chain» снимает все звенья разом.
|
5. Крестик убирает секцию; «✂ Remove chain» снимает все звенья разом.
|
||||||
6. Ошибка звена — баннер «Шаг N (имя): …»; последние успешные результаты секций не исчезают.
|
6. Ошибка звена — баннер «Шаг N (имя): …»; последние успешные результаты секций
|
||||||
7. «Показать маску» переключает превью базового инструмента; в цепь идёт результат, скачивание базового отдаёт результат, не маску.
|
не исчезают.
|
||||||
8. Перезагрузка восстанавливает цепочку заполненных звеньев; пустые звенья при этом исчезают.
|
7. «Показать маску» переключает превью базового инструмента; в цепь идёт
|
||||||
|
результат, скачивание базового отдаёт результат, не маску.
|
||||||
|
8. Перезагрузка восстанавливает цепочку заполненных звеньев; пустые звенья при
|
||||||
|
этом исчезают.
|
||||||
9. Быстрые правки подряд — один финальный пересчёт всей вертикали.
|
9. Быстрые правки подряд — один финальный пересчёт всей вертикали.
|
||||||
10. `/list-tools` открывает старый каталог по категориям; прямые адреса `/tools/<id>` работают.
|
10. `/list-tools` открывает старый каталог по категориям; прямые адреса
|
||||||
|
`/tools/<id>` работают.
|
||||||
|
|
||||||
## 7. Критерии готовности
|
## 7. Критерии готовности
|
||||||
|
|
||||||
- Работа начинается с главной: поиск → инструмент → цепочка звеньями, каждый со своим превью и скачиванием.
|
- Работа начинается с главной: поиск → инструмент → цепочка звеньями, каждый со
|
||||||
- Отдельной страницы рабочего пространства нет; каталог доступен на `/list-tools`; прямые ссылки инструментов работают.
|
своим превью и скачиванием.
|
||||||
|
- Отдельной страницы рабочего пространства нет; каталог доступен на
|
||||||
|
`/list-tools`; прямые ссылки инструментов работают.
|
||||||
- Пустые звенья не попадают в сохранение и не ломают прогон.
|
- Пустые звенья не попадают в сохранение и не ломают прогон.
|
||||||
- `test/check/build` зелёные, смоук §6 пройден.
|
- `test/check/build` зелёные, смоук §6 пройден.
|
||||||
|
|
||||||
## 8. Что сознательно не делаем
|
## 8. Что сознательно не делаем
|
||||||
|
|
||||||
- Ветвления и прицепывание от середины цепи — новое звено всегда добавляется в конец.
|
- Ветвления и прицепывание от середины цепи — новое звено всегда добавляется в
|
||||||
|
конец.
|
||||||
- Пипетка и маски внутри звеньев цепи — они остаются фичей базового инструмента.
|
- Пипетка и маски внутри звеньев цепи — они остаются фичей базового инструмента.
|
||||||
- Экспорт и импорт цепочки файлом — отложено до появления CLI/wasm-части.
|
- Экспорт и импорт цепочки файлом — отложено до появления CLI/wasm-части.
|
||||||
- Несколько параллельных инструментов на главной одновременно — один активный блок за раз.
|
- Несколько параллельных инструментов на главной одновременно — один активный
|
||||||
|
блок за раз.
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
> (`web/e2e/`, `pnpm --dir web test:e2e`). Автоматизированная часть закрывает
|
> (`web/e2e/`, `pnpm --dir web test:e2e`). Автоматизированная часть закрывает
|
||||||
> «нет падений/ошибок»; здесь — то, что руками, и что Playwright не покрывает.
|
> «нет падений/ошибок»; здесь — то, что руками, и что Playwright не покрывает.
|
||||||
>
|
>
|
||||||
> Перед прогоном: `pnpm --dir web build`, поднять локально `web/` (dev или
|
> Перед прогоном: `pnpm --dir web build`, поднять локально `web/` (dev или build
|
||||||
> build → `node scripts/serve-static.mjs`).
|
> → `node scripts/serve-static.mjs`).
|
||||||
>
|
>
|
||||||
> Критерий приёмки большинства пунктов: **нет зависит, нет «залипания»,
|
> Критерий приёмки большинства пунктов: **нет зависит, нет «залипания»,
|
||||||
> сообщения об ошибках человекочитаемые**.
|
> сообщения об ошибках человекочитаемые**.
|
||||||
@@ -25,8 +25,8 @@
|
|||||||
|
|
||||||
## B. Края параметров
|
## B. Края параметров
|
||||||
|
|
||||||
- [ ] Экстремальные значения слайдеров (0 и max) в blur/sharpen/pixelate —
|
- [ ] Экстремальные значения слайдеров (0 и max) в blur/sharpen/pixelate — не
|
||||||
не «залипает» running.
|
«залипает» running.
|
||||||
- [ ] Пустые/NaN поля (напр. width/height) — санитизация (silent repair), нет
|
- [ ] Пустые/NaN поля (напр. width/height) — санитизация (silent repair), нет
|
||||||
красного alert-текста в виде `errors.*`.
|
красного alert-текста в виде `errors.*`.
|
||||||
- [ ] Невалидные цвета в плашках/градиентах (мисс-спелл `#xyz`, короткие hex,
|
- [ ] Невалидные цвета в плашках/градиентах (мисс-спелл `#xyz`, короткие hex,
|
||||||
@@ -37,17 +37,17 @@
|
|||||||
|
|
||||||
## C. Буфер обмена и drag-n-drop
|
## C. Буфер обмена и drag-n-drop
|
||||||
|
|
||||||
- [ ] В реальном браузере кнопка copy на текстовом результате (png-to-base64
|
- [ ] В реальном браузере кнопка copy на текстовом результате (png-to-base64 и
|
||||||
и т.п.) кладёт текст в буфер (в Playwright не тестируем из-за permissions).
|
т.п.) кладёт текст в буфер (в Playwright не тестируем из-за permissions).
|
||||||
- [ ] Drag-n-drop файла на dropzone страницы инструмента (не только
|
- [ ] Drag-n-drop файла на dropzone страницы инструмента (не только файл-диалог)
|
||||||
файл-диалог) — UX-ощущения и корректность.
|
— UX-ощущения и корректность.
|
||||||
|
|
||||||
## D. Производительность и отзывчивость
|
## D. Производительность и отзывчивость
|
||||||
|
|
||||||
- [ ] Долгие авто-рераны на больших картинках (jpeg-artifacts, dithering,
|
- [ ] Долгие авто-рераны на больших картинках (jpeg-artifacts, dithering,
|
||||||
quantize на 4К) — UI не фризит, канселяция/дебаунс срабатывает.
|
quantize на 4К) — UI не фризит, канселяция/дебаунс срабатывает.
|
||||||
- [ ] Скролл страницы при длинных настройках (например, text-инструменты) —
|
- [ ] Скролл страницы при длинных настройках (например, text-инструменты) — нет
|
||||||
нет залипаний, панели не прыгают.
|
залипаний, панели не прыгают.
|
||||||
- [ ] Вкладка не «съедает» память при 10+ повторах blur/sharpen на мегапикселе
|
- [ ] Вкладка не «съедает» память при 10+ повторах blur/sharpen на мегапикселе
|
||||||
(утечек blob-URL/bitmap быть не должно).
|
(утечек blob-URL/bitmap быть не должно).
|
||||||
|
|
||||||
|
|||||||
+195
-181
@@ -1,40 +1,41 @@
|
|||||||
# План: переезд на новый дизайн (refs) — параллельная сборка
|
# План: переезд на новый дизайн (refs) — параллельная сборка
|
||||||
|
|
||||||
> Статус: план к выполнению. Шаги 5–6 (C17–C21) остаются **следующим этапом**.
|
> Статус: план к выполнению. Шаги 5–6 (C17–C21) остаются **следующим этапом**.
|
||||||
> Они больше не привязаны к «верности против рефа»: `docs/archive/plan-design-fix.md`
|
> Они больше не привязаны к «верности против рефа»:
|
||||||
> закрыт (2026-09-07, дизайн ушёл вперёд от `refs-html/`), аудит против рефа
|
> `docs/archive/plan-design-fix.md` закрыт (2026-09-07, дизайн ушёл вперёд от
|
||||||
> не информативен и прекращён. Дальнейшее качество — ручное ревью + `lint:all`.
|
> `refs-html/`), аудит против рефа не информативен и прекращён. Дальнейшее
|
||||||
> Шаг 37 (переезд старых файлов в папки `old/`) вынесен в `backlog.md`.
|
> качество — ручное ревью + `lint:all`. Шаг 37 (переезд старых файлов в папки
|
||||||
|
> `old/`) вынесен в `backlog.md`.
|
||||||
>
|
>
|
||||||
> Источники: `refs/` — Next/React-референс (правда по пикселям и интерактиву),
|
> Источники: `refs/` — Next/React-референс (правда по пикселям и интерактиву),
|
||||||
> `refs-html/` — статические HTML-снимки для быстрого просмотра в браузере.
|
> `refs-html/` — статические HTML-снимки для быстрого просмотра в браузере.
|
||||||
>
|
>
|
||||||
> **Стратегия изменена.** Вместо in-place рестайла (старый план A–F, правка
|
> **Стратегия изменена.** Вместо in-place рестайла (старый план A–F, правка
|
||||||
> общих компонентов на месте с алиасами токенов) — **параллельная сборка**:
|
> общих компонентов на месте с алиасами токенов) — **параллельная сборка**:
|
||||||
> новый дизайн строится отдельно, изолируется от старого, проверяется
|
> новый дизайн строится отдельно, изолируется от старого, проверяется целиком,
|
||||||
> целиком, затем становится основным и старый удаляется. Старое приложение
|
> затем становится основным и старый удаляется. Старое приложение остаётся
|
||||||
> остаётся рабочим и шippable на всём протяжении перехода.
|
> рабочим и шippable на всём протяжении перехода.
|
||||||
|
|
||||||
## 0. Почему меняем стратегию
|
## 0. Почему меняем стратегию
|
||||||
|
|
||||||
- **Старый план (A–F):** правим `ui/*` и страницы на месте, держим старые
|
- **Старый план (A–F):** правим `ui/*` и страницы на месте, держим старые токены
|
||||||
токены как алиасы, чтобы ничего не сломалось до этапа F. Риск: общие
|
как алиасы, чтобы ничего не сломалось до этапа F. Риск: общие компоненты
|
||||||
компоненты редактируются вживую → регресс старого UI во время работы,
|
редактируются вживую → регресс старого UI во время работы, новый дизайн нельзя
|
||||||
новый дизайн нельзя принять изолированно.
|
принять изолированно.
|
||||||
- **Новый подход (5 фаз):** (1) строим новый ui-kit, (2) строим новые
|
- **Новый подход (5 фаз):** (1) строим новый ui-kit, (2) строим новые страницы
|
||||||
страницы рядом, не трогая старые, (3) проверяем новый дизайн, (4) делаем
|
рядом, не трогая старые, (3) проверяем новый дизайн, (4) делаем его основным,
|
||||||
его основным, (5) удаляем старый. Ниже фазы 1–5 — это требуемые шаги.
|
(5) удаляем старый. Ниже фазы 1–5 — это требуемые шаги.
|
||||||
- **Главный технический риск и его решение.** Сейчас `web/src/app.css`
|
- **Главный технический риск и его решение.** Сейчас `web/src/app.css`
|
||||||
импортируется в **корневом** `web/src/routes/+layout.svelte:2` и потому
|
импортируется в **корневом** `web/src/routes/+layout.svelte:2` и потому
|
||||||
грузится на ВСЕХ маршрутах, включая новые. Решение — **не префиксовать
|
грузится на ВСЕХ маршрутах, включая новые. Решение — **не префиксовать новый
|
||||||
новый CSS (никакого `.ds2`), а разнести глобальные стили по layout'ам без
|
CSS (никакого `.ds2`), а разнести глобальные стили по layout'ам без
|
||||||
наследования**: (а) убрать `import '../app.css'` из корневого layout;
|
наследования**: (а) убрать `import '../app.css'` из корневого layout; (б)
|
||||||
(б) старые маршруты обернуть группой `(old)/` с собственным layout,
|
старые маршруты обернуть группой `(old)/` с собственным layout, импортирующим
|
||||||
импортирующим `app.css`; (в) новые маршруты (`preview/*`) — собственный
|
`app.css`; (в) новые маршруты (`preview/*`) — собственный layout,
|
||||||
layout, импортирующий `design2.css`. SvelteKit делает code-splitting CSS
|
импортирующий `design2.css`. SvelteKit делает code-splitting CSS по маршрутам,
|
||||||
по маршрутам, а стили компонентов скоупятся сами — поэтому глобальные
|
а стили компонентов скоупятся сами — поэтому глобальные стили каждого дизайна
|
||||||
стили каждого дизайна живут только в своей ветке, и префикс-обёртка
|
живут только в своей ветке, и префикс-обёртка `.ds2` не нужна. Старые `ui/` и
|
||||||
`.ds2` не нужна. Старые `ui/` и страницы не трогаются до фазы 5.
|
страницы не трогаются до фазы 5.
|
||||||
|
|
||||||
## 1. Источники и их оценка
|
## 1. Источники и их оценка
|
||||||
|
|
||||||
@@ -57,10 +58,11 @@
|
|||||||
`panel-heading`, `setting-group`, `settings-footer`, `settings-panel`,
|
`panel-heading`, `setting-group`, `settings-footer`, `settings-panel`,
|
||||||
`segmented`, `status`, `step-card` (`step-index`/`step-heading`/`step-type`/
|
`segmented`, `status`, `step-card` (`step-index`/`step-heading`/`step-type`/
|
||||||
`step-body`/`drag`), `color-field` (`swatch`/`native-color`),
|
`step-body`/`drag`), `color-field` (`swatch`/`native-color`),
|
||||||
`preview-toolbar`/`checker` (`tile-canvas`/`remover-canvas`/`comparison-grid`),
|
`preview-toolbar`/`checker`
|
||||||
`result-meta`/`meta-row`, `tool-card`/`image-card` (`tool-icon`/`tool-index`/
|
(`tile-canvas`/`remover-canvas`/`comparison-grid`), `result-meta`/`meta-row`,
|
||||||
`tool-copy`), `code-block`, `icon-btn`, `download-btn`, `reset-btn`, `lede`,
|
`tool-card`/`image-card` (`tool-icon`/`tool-index`/ `tool-copy`),
|
||||||
`auto-note`, `label`, `toggle`/`toggle-group`, `direction-grid`, `gradient-bar`.
|
`code-block`, `icon-btn`, `download-btn`, `reset-btn`, `lede`, `auto-note`,
|
||||||
|
`label`, `toggle`/`toggle-group`, `direction-grid`, `gradient-bar`.
|
||||||
- **Дыры в refs-html:** нет состояний empty/error/loading, нет глобальных
|
- **Дыры в refs-html:** нет состояний empty/error/loading, нет глобальных
|
||||||
модалок/тултипов, i18n не представлен (все лейблы — английские брендовые).
|
модалок/тултипов, i18n не представлен (все лейблы — английские брендовые).
|
||||||
Решения — см. §9.
|
Решения — см. §9.
|
||||||
@@ -68,9 +70,9 @@
|
|||||||
## 2. Токены (фундамент нового дизайна)
|
## 2. Токены (фундамент нового дизайна)
|
||||||
|
|
||||||
Живут в `src/lib/styles/design2.css` как обычный `:root { … }` (плюс
|
Живут в `src/lib/styles/design2.css` как обычный `:root { … }` (плюс
|
||||||
`[data-theme='dark'] { … }`). Файл грузится **только** layout'ом новой
|
`[data-theme='dark'] { … }`). Файл грузится **только** layout'ом новой ветки
|
||||||
ветки (§5), поэтому глобально не конфликтует со старым `app.css`. Маппинг
|
(§5), поэтому глобально не конфликтует со старым `app.css`. Маппинг старых
|
||||||
старых токенов `web/src/app.css` → новые:
|
токенов `web/src/app.css` → новые:
|
||||||
|
|
||||||
| Старый | Новый | Light | Dark |
|
| Старый | Новый | Light | Dark |
|
||||||
| -------------- | -------------- | --------- | ------------------------ |
|
| -------------- | -------------- | --------- | ------------------------ |
|
||||||
@@ -90,45 +92,45 @@
|
|||||||
- Радиусы: один `--radius: 4px` (мягкость старой темы уходит).
|
- Радиусы: один `--radius: 4px` (мягкость старой темы уходит).
|
||||||
- Шрифты: `IBM Plex Sans` + `IBM Plex Mono`, self-host через `@fontsource`
|
- Шрифты: `IBM Plex Sans` + `IBM Plex Mono`, self-host через `@fontsource`
|
||||||
(offline-first, CDN нельзя). Mono — рабочий шрифт для лейблов/метаданных/
|
(offline-first, CDN нельзя). Mono — рабочий шрифт для лейблов/метаданных/
|
||||||
цифр; Sans — заголовки и текст. Задаются на `.app-shell` (корень новой
|
цифр; Sans — заголовки и текст. Задаются на `.app-shell` (корень новой ветки),
|
||||||
ветки), чтобы не зависеть от старых глобальных правил `body`.
|
чтобы не зависеть от старых глобальных правил `body`.
|
||||||
- Фон-сетка (32px blueprint-grid) и шахматка канвасов — утилиты `.page-grid`
|
- Фон-сетка (32px blueprint-grid) и шахматка канвасов — утилиты `.page-grid` и
|
||||||
и `.checker-canvas` внутри `.ds2`, не токены.
|
`.checker-canvas` внутри `.ds2`, не токены.
|
||||||
- Тёмная тема — на `[data-theme='dark']` (инлайн-скрипт уже есть в
|
- Тёмная тема — на `[data-theme='dark']` (инлайн-скрипт уже есть в `app.html`);
|
||||||
`app.html`); класс `.dark-mode` из рефа не переносим, используем наш
|
класс `.dark-mode` из рефа не переносим, используем наш механизм. На
|
||||||
механизм. На переходный период **никаких алиасов старых токенов** (старые
|
переходный период **никаких алиасов старых токенов** (старые страницы сами
|
||||||
страницы сами живут на своём `app.css`).
|
живут на своём `app.css`).
|
||||||
|
|
||||||
## 3. Базовые примитивы дизайн-системы (все — компоненты)
|
## 3. Базовые примитивы дизайн-системы (все — компоненты)
|
||||||
|
|
||||||
Правило фазы 1: **любой повторяющийся паттерн — компонент**, даже «просто
|
Правило фазы 1: **любой повторяющийся паттерн — компонент**, даже «просто div с
|
||||||
div с двумя стилями». Никакой копипасты классов между страницами. Каждый
|
двумя стилями». Никакой копипасты классов между страницами. Каждый примитив ниже
|
||||||
примитив ниже — отдельный `.svelte` в `src/lib/components/kit/`.
|
— отдельный `.svelte` в `src/lib/components/kit/`.
|
||||||
|
|
||||||
- `MonoLabel` — микро-лейбл: mono 10px, uppercase, letter-spacing .1em,
|
- `MonoLabel` — микро-лейбл: mono 10px, uppercase, letter-spacing .1em, muted;
|
||||||
muted; слот-модификатор accent (синие eyebrow/version).
|
слот-модификатор accent (синие eyebrow/version).
|
||||||
- `StatusDot` / `StatusLine` — точка `--success` + подпись
|
- `StatusDot` / `StatusLine` — точка `--success` + подпись («AUTO PIPELINE»,
|
||||||
(«AUTO PIPELINE», «LIVE PREVIEW»).
|
«LIVE PREVIEW»).
|
||||||
- `Panel` / `PanelHeading` / `SettingGroup` / `SettingsFooter` — панель с
|
- `Panel` / `PanelHeading` / `SettingGroup` / `SettingsFooter` — панель с
|
||||||
1px-бордером, составные части с разделителями.
|
1px-бордером, составные части с разделителями.
|
||||||
- `CheckerCanvas` — рабочая поверхность превью: тёмная подложка + шахматка.
|
- `CheckerCanvas` — рабочая поверхность превью: тёмная подложка + шахматка.
|
||||||
- `MetaRow` / `MetaList` — пары «CAPTION значение».
|
- `MetaRow` / `MetaList` — пары «CAPTION значение».
|
||||||
- `Segmented` — сегмент-переключатель (язык, тип градиента, пресеты).
|
- `Segmented` — сегмент-переключатель (язык, тип градиента, пресеты).
|
||||||
- `Badge` — тег типа шага (BACKGROUND/TRANSFORM/STYLE), чип «✓ AUTO».
|
- `Badge` — тег типа шага (BACKGROUND/TRANSFORM/STYLE), чип «✓ AUTO».
|
||||||
- `StepCard` — нумерованная карточка этапа: индекс `01`, drag-handle,
|
- `StepCard` — нумерованная карточка этапа: индекс `01`, drag-handle, Badge
|
||||||
Badge типа, заголовок, кнопка удаления; слот для параметров.
|
типа, заголовок, кнопка удаления; слот для параметров.
|
||||||
- `ToolCard` — карточка каталога (icon, index, title, copy, arrow).
|
- `ToolCard` — карточка каталога (icon, index, title, copy, arrow).
|
||||||
- `ImageCard` / `PreviewTile` — label сверху + canvas-превью.
|
- `ImageCard` / `PreviewTile` — label сверху + canvas-превью.
|
||||||
- `AppShell` — корневой контейнер `.app-shell` + `.page-grid` (обёртка
|
- `AppShell` — корневой контейнер `.app-shell` + `.page-grid` (обёртка новой
|
||||||
новой ветки маршрутов); задаёт шрифты и фон нового дизайна.
|
ветки маршрутов); задаёт шрифты и фон нового дизайна.
|
||||||
- `BlueprintGrid` — фоновая сетка (используется в `AppShell`).
|
- `BlueprintGrid` — фоновая сетка (используется в `AppShell`).
|
||||||
- `IconButton`, `Icon` (обёртка `lucide-svelte`, 16–17px, muted).
|
- `IconButton`, `Icon` (обёртка `lucide-svelte`, 16–17px, muted).
|
||||||
|
|
||||||
## 4. Минимальный UI-kit (`src/lib/components/kit/`)
|
## 4. Минимальный UI-kit (`src/lib/components/kit/`)
|
||||||
|
|
||||||
Контролы, поверх примитивов §3. API проектируем сразу под новый дизайн
|
Контролы, поверх примитивов §3. API проектируем сразу под новый дизайн (обратной
|
||||||
(обратной совместимости со старым `ui/` не нужно — старые страницы его
|
совместимости со старым `ui/` не нужно — старые страницы его продолжают
|
||||||
продолжают использовать).
|
использовать).
|
||||||
|
|
||||||
| Компонент | Назначение |
|
| Компонент | Назначение |
|
||||||
| ------------------------- | ----------------------------------------------------------------- |
|
| ------------------------- | ----------------------------------------------------------------- |
|
||||||
@@ -156,33 +158,33 @@ div с двумя стилями». Никакой копипасты класс
|
|||||||
- **Корневой layout `src/routes/+layout.svelte`** перестаёт импортировать
|
- **Корневой layout `src/routes/+layout.svelte`** перестаёт импортировать
|
||||||
`../app.css`. В нём остаётся только общая логика (init темы/locale) и
|
`../app.css`. В нём остаётся только общая логика (init темы/locale) и
|
||||||
нейтральный `<div>{children}</div>` без глобальных стилей дизайна.
|
нейтральный `<div>{children}</div>` без глобальных стилей дизайна.
|
||||||
- **Старая ветка `src/routes/(old)/+layout.svelte`** импортирует `app.css`
|
- **Старая ветка `src/routes/(old)/+layout.svelte`** импортирует `app.css` и
|
||||||
и содержит текущую шапку/подвал (разметку из нынешнего корневого layout).
|
содержит текущую шапку/подвал (разметку из нынешнего корневого layout). Все
|
||||||
Все старые страницы переносятся внутрь `(old)/` (группа в скобках не
|
старые страницы переносятся внутрь `(old)/` (группа в скобках не меняет URL):
|
||||||
меняет URL): `(old)/+page.svelte`, `(old)/demo/...`,
|
`(old)/+page.svelte`, `(old)/demo/...`, `(old)/list-tools/...`,
|
||||||
`(old)/list-tools/...`, `(old)/tools/[id]/...`.
|
`(old)/tools/[id]/...`.
|
||||||
- **Новая ветка `src/routes/preview/+layout.svelte`** импортирует
|
- **Новая ветка `src/routes/preview/+layout.svelte`** импортирует `design2.css`
|
||||||
`design2.css` и рендерит `<AppShell>`. Сюда же — новые страницы (§6).
|
и рендерит `<AppShell>`. Сюда же — новые страницы (§6).
|
||||||
- Благодаря code-splitting CSS в SvelteKit: на старых маршрутах грузится
|
- Благодаря code-splitting CSS в SvelteKit: на старых маршрутах грузится только
|
||||||
только `app.css`, на `preview/*` — только `design2.css`. Скоуп стилей
|
`app.css`, на `preview/*` — только `design2.css`. Скоуп стилей
|
||||||
Svelte-компонентов добавляет второй уровень изоляции. Никаких `.ds2`.
|
Svelte-компонентов добавляет второй уровень изоляции. Никаких `.ds2`.
|
||||||
- Переключатель для ручной проверки: временный флаг `previewDesign` в
|
- Переключатель для ручной проверки: временный флаг `previewDesign` в
|
||||||
`localStorage` + кнопка в старой шапке («Preview v2»), ведущая на
|
`localStorage` + кнопка в старой шапке («Preview v2»), ведущая на `/preview`.
|
||||||
`/preview`. На фазе 4 флаг убираем.
|
На фазе 4 флаг убираем.
|
||||||
|
|
||||||
> Почему не `(new)`-группа с теми же путями вместо `preview/*`: две группы
|
> Почему не `(new)`-группа с теми же путями вместо `preview/*`: две группы не
|
||||||
> не могут одновременно определять один и тот же URL (`(old)/+page.svelte`
|
> могут одновременно определять один и тот же URL (`(old)/+page.svelte` и
|
||||||
> и `(new)/+page.svelte` оба → `/` = конфликт маршрутов). Поэтому новая
|
> `(new)/+page.svelte` оба → `/` = конфликт маршрутов). Поэтому новая ветка
|
||||||
> ветка живёт под префиксом `preview/`, а на фазе 4 файлы переносятся на
|
> живёт под префиксом `preview/`, а на фазе 4 файлы переносятся на реальные пути
|
||||||
> реальные пути и группа `(old)` удаляется.
|
> и группа `(old)` удаляется.
|
||||||
>
|
>
|
||||||
> Это совпадает с рекомендованным в документации SvelteKit паттерном:
|
> Это совпадает с рекомендованным в документации SvelteKit паттерном: «put your
|
||||||
> «put your entire app inside one or more groups except the routes that
|
> entire app inside one or more groups except the routes that should not inherit
|
||||||
> should not inherit the common layouts» — старое целиком в `(old)`, новое
|
> the common layouts» — старое целиком в `(old)`, новое снаружи. Синтаксис
|
||||||
> снаружи. Синтаксис `@`-breakout (`+layout@.svelte`, `+page@.svelte`) здесь
|
> `@`-breakout (`+layout@.svelte`, `+page@.svelte`) здесь не требуется:
|
||||||
> не требуется: «выломаться» можно лишь до корня, а нам нужно, чтобы именно
|
> «выломаться» можно лишь до корня, а нам нужно, чтобы именно у корня НЕ было
|
||||||
> у корня НЕ было `app.css`; если позже захотим держать новые маршруты внутри
|
> `app.css`; если позже захотим держать новые маршруты внутри группы, `@`
|
||||||
> группы, `@` пригодится для сброса родительского layout.
|
> пригодится для сброса родительского layout.
|
||||||
|
|
||||||
## 6. Маршрутизация параллельных страниц
|
## 6. Маршрутизация параллельных страниц
|
||||||
|
|
||||||
@@ -202,55 +204,50 @@ src/routes/preview/list-tools/+page.svelte
|
|||||||
src/routes/preview/tools/[id]/+page.svelte
|
src/routes/preview/tools/[id]/+page.svelte
|
||||||
```
|
```
|
||||||
|
|
||||||
- Каждая новая страница импортирует **только** `kit/*`. Старые страницы
|
- Каждая новая страница импортирует **только** `kit/*`. Старые страницы не
|
||||||
не изменяются (только перемещены в `(old)/`).
|
изменяются (только перемещены в `(old)/`).
|
||||||
- Фаза 4: содержимое `preview/*` переносится на реальные маршруты
|
- Фаза 4: содержимое `preview/*` переносится на реальные маршруты (move-файлы),
|
||||||
(move-файлы), группа `(old)/` удаляется, `design2.css` подключается в
|
группа `(old)/` удаляется, `design2.css` подключается в корневом
|
||||||
корневом `+layout.svelte` (как новый основной слой), временный флаг/кнопка
|
`+layout.svelte` (как новый основной слой), временный флаг/кнопка убираются.
|
||||||
убираются.
|
|
||||||
|
|
||||||
## 7. Фазы выполнения (1–5 по запросу)
|
## 7. Фазы выполнения (1–5 по запросу)
|
||||||
|
|
||||||
Каждая фаза заканчивается зелёными `test`/`check`/`build` и ревью в браузере
|
Каждая фаза заканчивается зелёными `test`/`check`/`build` и ревью в браузере
|
||||||
(обе темы) против `refs-html/`.
|
(обе темы) против `refs-html/`.
|
||||||
|
|
||||||
- **Фаза 1 — Фундамент + UI-kit.** Токены §2, `design2.css` по §5, все
|
- **Фаза 1 — Фундамент + UI-kit.** Токены §2, `design2.css` по §5, все примитивы
|
||||||
примитивы §3 и контролы §4 как отдельные компоненты. Критерий:
|
§3 и контролы §4 как отдельные компоненты. Критерий: демо-страница kit'а
|
||||||
демо-страница kit'а (`/preview` или временная) показывает все состояния
|
(`/preview` или временная) показывает все состояния (light/dark,
|
||||||
(light/dark, hover/disabled, все варианты Button/Segmented/Badge);
|
hover/disabled, все варианты Button/Segmented/Badge); unit-тесты форм не
|
||||||
unit-тесты форм не падают; **старые страницы визуально не изменились**
|
падают; **старые страницы визуально не изменились** (изоляция работает).
|
||||||
(изоляция работает).
|
|
||||||
|
|
||||||
- **Фаза 2 — Новые страницы.** Под `preview/*` (§6): воркспейс, demo,
|
- **Фаза 2 — Новые страницы.** Под `preview/*` (§6): воркспейс, demo,
|
||||||
list-tools, tool-page (двухпанельная раскладка из рефа: settings-panel +
|
list-tools, tool-page (двухпанельная раскладка из рефа: settings-panel +
|
||||||
preview-panel, step-cards для chain). Критерий: разметка соответствует
|
preview-panel, step-cards для chain). Критерий: разметка соответствует
|
||||||
`refs-html/index.html`, `gradient.html`, `background-remover.html`,
|
`refs-html/index.html`, `gradient.html`, `background-remover.html`,
|
||||||
`list-tools.html`; интерактив (слайдеры/тоглы/загрузка/автозапуск) работает
|
`list-tools.html`; интерактив (слайдеры/тоглы/загрузка/автозапуск) работает в
|
||||||
в новом стиле; старые маршруты продолжают работать как раньше.
|
новом стиле; старые маршруты продолжают работать как раньше.
|
||||||
|
|
||||||
- **Фазa 3 — Проверка нового дизайна.** Смоук §8 (обе темы, узкий экран,
|
- **Фазa 3 — Проверка нового дизайна.** Смоук §8 (обе темы, узкий экран, 800px),
|
||||||
800px), скриншоты против `refs-html/`, прогон старого тест-сьюта для
|
скриншоты против `refs-html/`, прогон старого тест-сьюта для доказательства
|
||||||
доказательства отсутствия регресса, RU/EN-локализация новых лейблов.
|
отсутствия регресса, RU/EN-локализация новых лейблов. Критерий: визуальное
|
||||||
Критерий: визуальное совпадение с рефом, зелёные тесты, записанные
|
совпадение с рефом, зелёные тесты, записанные скриншоты приняты.
|
||||||
скриншоты приняты.
|
|
||||||
|
|
||||||
- **Фаза 4 — Сделать новый дизайн основным.** Перенос `preview/*` →
|
- **Фаза 4 — Сделать новый дизайн основным.** Перенос `preview/*` → реальные
|
||||||
реальные маршруты; `design2.css` подключается в корневом `+layout.svelte`;
|
маршруты; `design2.css` подключается в корневом `+layout.svelte`; удаление
|
||||||
удаление preview-группы и временного флага/кнопки. Старые страницы теперь
|
preview-группы и временного флага/кнопки. Старые страницы теперь не
|
||||||
не используются (мёртвый код), но физически ещё в репо.
|
используются (мёртвый код), но физически ещё в репо. Критерий: реальные
|
||||||
Критерий: реальные маршруты отдают новый дизайн, тесты зелёные, билд без
|
маршруты отдают новый дизайн, тесты зелёные, билд без предупреждений.
|
||||||
предупреждений.
|
|
||||||
|
|
||||||
- **Фаза 5 — Удалить старый дизайн.** Удаление старых страниц, старого
|
- **Фаза 5 — Удалить старый дизайн.** Удаление старых страниц, старого `ui/`,
|
||||||
`ui/`, старого `app.css` (и его токенов), неиспользуемых классов;
|
старого `app.css` (и его токенов), неиспользуемых классов; переименование
|
||||||
переименование `kit/` → `ui/` (финальное имя) либо оставление `kit/` как
|
`kit/` → `ui/` (финальное имя) либо оставление `kit/` как основного — решается
|
||||||
основного — решается на фазе 1. Финальный проход по брейкпоинтам
|
на фазе 1. Финальный проход по брейкпоинтам 1200/1100/800/480. Критерий:
|
||||||
1200/1100/800/480. Критерий: `grep` по старым токенам/классам пуст,
|
`grep` по старым токенам/классам пуст, build чистый, старый дизайн нигде не
|
||||||
build чистый, старый дизайн нигде не упоминается.
|
упоминается.
|
||||||
|
|
||||||
Оценка объёма: Фаза 1 — фундамент+kit (~1 день), Фаза 2 — страницы
|
Оценка объёма: Фаза 1 — фундамент+kit (~1 день), Фаза 2 — страницы (~1–2 дня),
|
||||||
(~1–2 дня), Фаза 3 — проверка (~полдня), Фаза 4–5 — переключение и чистка
|
Фаза 3 — проверка (~полдня), Фаза 4–5 — переключение и чистка (~полдня).
|
||||||
(~полдня).
|
|
||||||
|
|
||||||
## 8. Смоук (после фазы 3, контроль на фазе 4)
|
## 8. Смоук (после фазы 3, контроль на фазе 4)
|
||||||
|
|
||||||
@@ -262,35 +259,34 @@ src/routes/preview/tools/[id]/+page.svelte
|
|||||||
шахматке.
|
шахматке.
|
||||||
4. Chain из 3+ инструментов: step-cards рендерятся, удаление/сброс работают.
|
4. Chain из 3+ инструментов: step-cards рендерятся, удаление/сброс работают.
|
||||||
5. Узкий экран 800px: настройки над превью, sticky выключается как в рефе.
|
5. Узкий экран 800px: настройки над превью, sticky выключается как в рефе.
|
||||||
6. RU/EN: новые лейблы локализованы; mono-лейблы рефа — либо ключи i18n,
|
6. RU/EN: новые лейблы локализованы; mono-лейблы рефа — либо ключи i18n, либо
|
||||||
либо осознанно английские брендовые элементы (см. §9).
|
осознанно английские брендовые элементы (см. §9).
|
||||||
|
|
||||||
## 9. Открытые вопросы
|
## 9. Открытые вопросы
|
||||||
|
|
||||||
- **Имя финального каталога компонентов:** оставить `kit/` как основное имя
|
- **Имя финального каталога компонентов:** оставить `kit/` как основное имя или
|
||||||
или переименовать в `ui/` на фазе 5 (удалив старое `ui/`). Рекомендую
|
переименовать в `ui/` на фазе 5 (удалив старое `ui/`). Рекомендую сразу писать
|
||||||
сразу писать в `kit/`, на фазе 5 переименовать в `ui/`. Решить на фазе 1.
|
в `kit/`, на фазе 5 переименовать в `ui/`. Решить на фазе 1.
|
||||||
- **Изоляция через layout:** `app.css` вынесен из корневого layout в
|
- **Изоляция через layout:** `app.css` вынесен из корневого layout в
|
||||||
`(old)/+layout.svelte`; новый `design2.css` — только в `preview/+layout`.
|
`(old)/+layout.svelte`; новый `design2.css` — только в `preview/+layout`.
|
||||||
Проверить на фазе 1, что на `/preview/*` не попадают стили старого
|
Проверить на фазе 1, что на `/preview/*` не попадают стили старого дизайна
|
||||||
дизайна (DevTools → вкладка Styles, нет правил из `app.css`).
|
(DevTools → вкладка Styles, нет правил из `app.css`).
|
||||||
- **Переключатель для проверки:** рекомендую `localStorage`-флаг +
|
- **Переключатель для проверки:** рекомендую `localStorage`-флаг + кнопка
|
||||||
кнопка «Preview v2» в старой шапке + группа `preview/*`. Убрать на фазе 4.
|
«Preview v2» в старой шапке + группа `preview/*`. Убрать на фазе 4.
|
||||||
- **Mono-лейблы рефа на английском** (`PROCESSING PIPELINE`): завести i18n-
|
- **Mono-лейблы рефа на английском** (`PROCESSING PIPELINE`): завести i18n-
|
||||||
ключи, но допустить часть английских брендовых («приборная панель»).
|
ключи, но допустить часть английских брендовых («приборная панель»).
|
||||||
- **Версия в шапке/футере:** из `package.json` через `define` или
|
- **Версия в шапке/футере:** из `package.json` через `define` или захардкодить —
|
||||||
захардкодить — мелочь, решить при сборке topbar.
|
мелочь, решить при сборке topbar.
|
||||||
- **`refs/` и `refs-html/` — одноразовый источник, НЕ поддерживаемый.**
|
- **`refs/` и `refs-html/` — одноразовый источник, НЕ поддерживаемый.** После
|
||||||
После внедрения нового дизайна все правки дизайна делаются сразу в
|
внедрения нового дизайна все правки дизайна делаются сразу в продакшен-коде
|
||||||
продакшен-коде (`design2.css` + `kit/`), а не в `refs/`. Механизм
|
(`design2.css` + `kit/`), а не в `refs/`. Механизм синхронизации «ref → код»
|
||||||
синхронизации «ref → код» не нужен и не создаётся. `refs/` и `refs-html/`
|
не нужен и не создаётся. `refs/` и `refs-html/` можно удалить из репо на фазе
|
||||||
можно удалить из репо на фазе 5 (по желанию), они не являются частью
|
5 (по желанию), они не являются частью рантайма.
|
||||||
рантайма.
|
|
||||||
|
|
||||||
## 10. План исполнения (мелкие коммиты)
|
## 10. План исполнения (мелкие коммиты)
|
||||||
|
|
||||||
Ветка `feat/redesign`. Каждый шаг — отдельный коммит, цель < ~500 строк.
|
Ветка `feat/redesign`. Каждый шаг — отдельный коммит, цель < ~500 строк. Перед
|
||||||
Перед стартом: `test`/`build` зелёные на базе (якорь регрессии).
|
стартом: `test`/`build` зелёные на базе (якорь регрессии).
|
||||||
|
|
||||||
### Шаг 0. Изоляция layout (без дизайна)
|
### Шаг 0. Изоляция layout (без дизайна)
|
||||||
|
|
||||||
@@ -299,13 +295,15 @@ src/routes/preview/tools/[id]/+page.svelte
|
|||||||
`<div>{children}</div>`.
|
`<div>{children}</div>`.
|
||||||
- [x] **[C2]** Создать `routes/(old)/+layout.svelte` (импорт `app.css` + старая
|
- [x] **[C2]** Создать `routes/(old)/+layout.svelte` (импорт `app.css` + старая
|
||||||
шапка/подвал) и перенести старые страницы (`+page`, `demo`, `list-tools`,
|
шапка/подвал) и перенести старые страницы (`+page`, `demo`, `list-tools`,
|
||||||
`tools/[id]`) в `(old)/`. URL не меняются. Проверка: старый сайт идентичен.
|
`tools/[id]`) в `(old)/`. URL не меняются. Проверка: старый сайт
|
||||||
_(файлы — move, объём строк минимален)_
|
идентичен. _(файлы — move, объём строк минимален)_
|
||||||
|
|
||||||
### Шаг 1. Фундамент нового дизайна
|
### Шаг 1. Фундамент нового дизайна
|
||||||
|
|
||||||
- [x] **[C3]** Создать `src/lib/styles/design2.css` (токены `:root` + `[data-theme='dark']` по §2) и подключить `@fontsource/ibm-plex-sans` + `@fontsource/ibm-plex-mono` (self-host, offline-first).
|
- [x] **[C3]** Создать `src/lib/styles/design2.css` (токены `:root` +
|
||||||
подключить шрифты в `design2.css` (self-host, offline-first).
|
`[data-theme='dark']` по §2) и подключить `@fontsource/ibm-plex-sans` +
|
||||||
|
`@fontsource/ibm-plex-mono` (self-host, offline-first). подключить шрифты
|
||||||
|
в `design2.css` (self-host, offline-first).
|
||||||
- [x] **[C4]** _(слит в C3: шрифты нельзя подключить до создания `design2.css`)_
|
- [x] **[C4]** _(слит в C3: шрифты нельзя подключить до создания `design2.css`)_
|
||||||
`[data-theme='dark']` (таблица §2). Без утилитарных классов.
|
`[data-theme='dark']` (таблица §2). Без утилитарных классов.
|
||||||
- [x] **[C5]** Примитивы batch 1: `AppShell`, `BlueprintGrid`, `CheckerCanvas`,
|
- [x] **[C5]** Примитивы batch 1: `AppShell`, `BlueprintGrid`, `CheckerCanvas`,
|
||||||
@@ -320,87 +318,103 @@ src/routes/preview/tools/[id]/+page.svelte
|
|||||||
|
|
||||||
### Шаг 2. Витрина kit (проверка компонентов)
|
### Шаг 2. Витрина kit (проверка компонентов)
|
||||||
|
|
||||||
- [x] **[C8]** `preview/+layout.svelte` (импорт `design2.css` + `<AppShell>` + переключатель темы) и
|
- [x] **[C8]** `preview/+layout.svelte` (импорт `design2.css` + `<AppShell>` +
|
||||||
временная `preview/kit` страница со всеми состояниями (light/dark,
|
переключатель темы) и временная `preview/kit` страница со всеми
|
||||||
hover/disabled, варианты Button/Segmented/Badge и пр.)
|
состояниями (light/dark, hover/disabled, варианты Button/Segmented/Badge и
|
||||||
|
пр.)
|
||||||
|
|
||||||
### Шаг 3. Новые страницы
|
### Шаг 3. Новые страницы
|
||||||
|
|
||||||
- [x] **[C9]** `/preview` — воркспейс (eyebrow, h1, lede, поиск, «открыть последний»).
|
- [x] **[C9]** `/preview` — воркспейс (eyebrow, h1, lede, поиск, «открыть
|
||||||
Поиск фильтрует `TOOLS` из `$lib/registry`; карточки результата пока
|
последний»). Поиск фильтрует `TOOLS` из `$lib/registry`; карточки
|
||||||
ведут на `#` (реальные `/preview/tools/[id]` подключим на C12, иначе
|
результата пока ведут на `#` (реальные `/preview/tools/[id]` подключим на
|
||||||
prerender падает на 404).
|
C12, иначе prerender падает на 404).
|
||||||
- [x] **[C10]** `/preview/list-tools` — каталог карточек (группировка по
|
- [x] **[C10]** `/preview/list-tools` — каталог карточек (группировка по
|
||||||
`CATEGORIES`, `ToolCard`). Ссылки пока `#` (реальные `/preview/tools/[id]`
|
`CATEGORIES`, `ToolCard`). Ссылки пока `#` (реальные `/preview/tools/[id]`
|
||||||
подключим на C12).
|
подключим на C12).
|
||||||
- [x] **[C11]** `/preview/demo` — зеркало воркспейса (4 шага + панель превью), статичный приёмочный стенд.
|
- [x] **[C11]** `/preview/demo` — зеркало воркспейса (4 шага + панель превью),
|
||||||
- [x] **[C12]** `/preview/tools/[id]` — data-driven settings-panel + preview-panel (gradient live, остальные — плейсхолдер); `NumberField`, `entries()` для prerender, ToolCard-ссылки подключены.
|
статичный приёмочный стенд.
|
||||||
- [x] **[C13]** `/preview/tools/[id]` — comparison-grid для file-source инструментов (`Dropzone` + before/after `CheckerCanvas`, `result-meta`); охватывает background-remover и прочие.
|
- [x] **[C12]** `/preview/tools/[id]` — data-driven settings-panel +
|
||||||
- [x] **[C14]** `/preview/tools/[id]` — исполняемая цепочка: `StepCard`-шаги (параметры inline) + «Add step» (chainable), живой прогон пайплайна на исходнике, превью каждого шага + итог + Download.
|
preview-panel (gradient live, остальные — плейсхолдер); `NumberField`,
|
||||||
|
`entries()` для prerender, ToolCard-ссылки подключены.
|
||||||
|
- [x] **[C13]** `/preview/tools/[id]` — comparison-grid для file-source
|
||||||
|
инструментов (`Dropzone` + before/after `CheckerCanvas`, `result-meta`);
|
||||||
|
охватывает background-remover и прочие.
|
||||||
|
- [x] **[C14]** `/preview/tools/[id]` — исполняемая цепочка: `StepCard`-шаги
|
||||||
|
(параметры inline) + «Add step» (chainable), живой прогон пайплайна на
|
||||||
|
исходнике, превью каждого шага + итог + Download.
|
||||||
|
|
||||||
### Шаг 4. Переключатель и проверка
|
### Шаг 4. Переключатель и проверка
|
||||||
|
|
||||||
- [x] **[C15]** В старой шапке ссылка «Preview v2» → `/preview`, ставит `localStorage['easy-png-tools:preview-v2']='1'`.
|
- [x] **[C15]** В старой шапке ссылка «Preview v2» → `/preview`, ставит
|
||||||
- [x] **[C16]** База под дизайн: рабочий переключатель тем (persist в localStorage, применяется на `<html>`; проверено — фон `#eef1f4`↔`#11171d`), `TopBar` приведён к виду `demo.html` (бренд + синий хлебный крош `/* страница */`, безрамочные иконки, язык — active = `--foreground`/`--background`), страницы `/preview/*` на всю ширину (убран `max-width`). Точечное выравнивание к рефу отложено: реф-дизайн неконсистентен.
|
`localStorage['easy-png-tools:preview-v2']='1'`.
|
||||||
(доказательство отсутствия регресса). Фиксы по результатам — отдельными
|
- [x] **[C16]** База под дизайн: рабочий переключатель тем (persist в
|
||||||
мелкими коммитами.
|
localStorage, применяется на `<html>`; проверено — фон
|
||||||
|
`#eef1f4`↔`#11171d`), `TopBar` приведён к виду `demo.html` (бренд + синий
|
||||||
|
хлебный крош `/* страница */`, безрамочные иконки, язык — active =
|
||||||
|
`--foreground`/`--background`), страницы `/preview/*` на всю ширину (убран
|
||||||
|
`max-width`). Точечное выравнивание к рефу отложено: реф-дизайн
|
||||||
|
неконсистентен. (доказательство отсутствия регресса). Фиксы по результатам
|
||||||
|
— отдельными мелкими коммитами.
|
||||||
|
|
||||||
### Шаг 5. Сделать новый дизайн основным — СЛЕДУЮЩИЙ ЭТАП
|
### Шаг 5. Сделать новый дизайн основным — СЛЕДУЮЩИЙ ЭТАП
|
||||||
|
|
||||||
> Раньше планировалось после «верности к рефу» (Этапы 0–1
|
> Раньше планировалось после «верности к рефу» (Этапы 0–1
|
||||||
> `docs/archive/plan-design-fix.md`) — план закрыт, гейт снят (2026-09-07).
|
> `docs/archive/plan-design-fix.md`) — план закрыт, гейт снят (2026-09-07).
|
||||||
> Перед стартом C17 выполнить переезд старых файлов в папки `old/`
|
> Перед стартом C17 выполнить переезд старых файлов в папки `old/` (backlog:
|
||||||
> (backlog: «Переезд старых файлов в `old/`», шаг 37 закрытого
|
> «Переезд старых файлов в `old/`», шаг 37 закрытого
|
||||||
> `docs/archive/plan-composite-params.md`). Пока не трогаем `preview/*` и `(old)/`.
|
> `docs/archive/plan-composite-params.md`). Пока не трогаем `preview/*` и
|
||||||
|
> `(old)/`.
|
||||||
|
|
||||||
- [ ] **[C17]** Перенести `preview/*` → реальные маршруты (move), удалить
|
- [ ] **[C17]** Перенести `preview/*` → реальные маршруты (move), удалить
|
||||||
`(old)/`, корневой `+layout.svelte` импортирует `design2.css` + новый shell.
|
`(old)/`, корневой `+layout.svelte` импортирует `design2.css` + новый
|
||||||
Убрать флаг/кнопку. **И обновить `newCode` в `web/eslint.config.js`:** заменить
|
shell. Убрать флаг/кнопку. **И обновить `newCode` в
|
||||||
`src/routes/preview/**` на `src/routes/**` (после переезда все маршруты —
|
`web/eslint.config.js`:** заменить `src/routes/preview/**` на
|
||||||
новый дизайн), компонентный glob `src/lib/components/kit/**` пока оставить.
|
`src/routes/**` (после переезда все маршруты — новый дизайн), компонентный
|
||||||
|
glob `src/lib/components/kit/**` пока оставить.
|
||||||
- [ ] **[C18]** Правки импортов/редиректов после переноса, проверка билда.
|
- [ ] **[C18]** Правки импортов/редиректов после переноса, проверка билда.
|
||||||
|
|
||||||
### Шаг 6. Удаление старого
|
### Шаг 6. Удаление старого
|
||||||
|
|
||||||
- [ ] **[C19]** Удалить старое `ui/`, прочие старые компоненты, старый `app.css`,
|
- [ ] **[C19]** Удалить старое `ui/`, прочие старые компоненты, старый
|
||||||
неиспользуемые классы. Переименовать `kit/` → `ui/` (если решено на C1 —
|
`app.css`, неиспользуемые классы. Переименовать `kit/` → `ui/` (если
|
||||||
тогда в `eslint.config.js` обновить и компонентный glob).
|
решено на C1 — тогда в `eslint.config.js` обновить и компонентный glob).
|
||||||
**И раскрыть ESLint `recommended` на весь код** — убрать scoped-блок
|
**И раскрыть ESLint `recommended` на весь код** — убрать scoped-блок
|
||||||
`newCode` в `web/eslint.config.js` (см. AGENTS.md), прогнать `lint` и
|
`newCode` в `web/eslint.config.js` (см. AGENTS.md), прогнать `lint` и
|
||||||
починить всплывшие ошибки в оставшемся коде.
|
починить всплывшие ошибки в оставшемся коде. _Примечание: доменные модули
|
||||||
_Примечание: доменные модули (`lib/core`, `lib/registry`, тесты и т.п.) —
|
(`lib/core`, `lib/registry`, тесты и т.п.) — это не «старый дизайн», у них
|
||||||
это не «старый дизайн», у них свой линт-долг; расширение `recommended` на
|
свой линт-долг; расширение `recommended` на них может выдать много ошибок.
|
||||||
них может выдать много ошибок. Решить на C19: либо чиним сразу, либо
|
Решить на C19: либо чиним сразу, либо расширяем scoped только на
|
||||||
расширяем scoped только на `src/routes/**` + `src/lib/components/**`._
|
`src/routes/**` + `src/lib/components/**`._
|
||||||
- [ ] **[C20]** Финальный проход по брейкпоинтам 1200/1100/800/480; build без
|
- [ ] **[C20]** Финальный проход по брейкпоинтам 1200/1100/800/480; build без
|
||||||
предупреждений; `grep` по старым токенам/классам пуст.
|
предупреждений; `grep` по старым токенам/классам пуст.
|
||||||
- [ ] **[C21]** _(опционально)_ Удалить `refs/` и `refs-html/` из репо — они
|
- [ ] **[C21]** _(опционально)_ Удалить `refs/` и `refs-html/` из репо — они
|
||||||
больше не источник правды.
|
больше не источник правды.
|
||||||
|
|
||||||
> Если какой-то коммит тянет за собой > 500 строк (например, C6/C7),
|
> Если какой-то коммит тянет за собой > 500 строк (например, C6/C7), разбивать
|
||||||
> разбивать на под-коммиты по 2–4 компонента. Тесты/билд прогонять после
|
> на под-коммиты по 2–4 компонента. Тесты/билд прогонять после каждого коммита,
|
||||||
> каждого коммита, чтобы регрессия локализовалась одним шагом назад.
|
> чтобы регрессия локализовалась одним шагом назад.
|
||||||
>
|
>
|
||||||
> **Атомарность и точки «поломки» между коммитами.**
|
> **Атомарность и точки «поломки» между коммитами.**
|
||||||
>
|
>
|
||||||
> - C1+C2 уже сделаны вместе (связаны: нейтральный root без `(old)`-layout
|
> - C1+C2 уже сделаны вместе (связаны: нейтральный root без `(old)`-layout
|
||||||
> ломает старый сайт). Аналогично связанных пар больше нет.
|
> ломает старый сайт). Аналогично связанных пар больше нет.
|
||||||
> - C3 и C4 слиты: шрифты подключаются внутри `design2.css`, поэтому файл
|
> - C3 и C4 слиты: шрифты подключаются внутри `design2.css`, поэтому файл должен
|
||||||
> должен существовать в том же коммите.
|
> существовать в том же коммите.
|
||||||
> - Коммиты **C3→C16 не трогают старый сайт** `(old)/` — они только
|
> - Коммиты **C3→C16 не трогают старый сайт** `(old)/` — они только добавляют
|
||||||
> добавляют новые файлы/маршруты, сломать старый сайт на них нельзя.
|
> новые файлы/маршруты, сломать старый сайт на них нельзя.
|
||||||
> - **C17 — единственный атомарный переключатель**: перенос `preview/*` на
|
> - **C17 — единственный атомарный переключатель**: перенос `preview/*` на
|
||||||
> реальные пути + удаление `(old)/` + смена root-layout должны быть в
|
> реальные пути + удаление `(old)/` + смена root-layout должны быть в ОДНОМ
|
||||||
> ОДНОМ коммите (иначе конфликт маршрутов или пропажа страниц). Не дробить.
|
> коммите (иначе конфликт маршрутов или пропажа страниц). Не дробить.
|
||||||
> - **C19 должен идти строго после C17** (старый `app.css`/`ui/` удаляем
|
> - **C19 должен идти строго после C17** (старый `app.css`/`ui/` удаляем только
|
||||||
> только когда old-сайт уже выключен).
|
> когда old-сайт уже выключен).
|
||||||
> - Очерёдность зависимостей: `design2.css` (C3) и kit-компоненты (C5–C7)
|
> - Очерёдность зависимостей: `design2.css` (C3) и kit-компоненты (C5–C7) должны
|
||||||
> должны существовать до страниц, которые их импортируют (C8–C14).
|
> существовать до страниц, которые их импортируют (C8–C14).
|
||||||
|
|
||||||
## Бэклог полировки (после C9–C21, отдельным проходом)
|
## Бэклог полировки (после C9–C21, отдельным проходом)
|
||||||
|
|
||||||
Конкретные расхождения витрины `preview/kit` с `refs-html/`. Править
|
Конкретные расхождения витрины `preview/kit` с `refs-html/`. Править системно в
|
||||||
системно в конце, а не по ходу сборки базы.
|
конце, а не по ходу сборки базы.
|
||||||
|
|
||||||
- **Toggle** в дизайне прямоугольный, у нас — закруглённый. Сделать
|
- **Toggle** в дизайне прямоугольный, у нас — закруглённый. Сделать
|
||||||
прямоугольным (или уточнить радиус у свича отдельно от общих токенов).
|
прямоугольным (или уточнить радиус у свича отдельно от общих токенов).
|
||||||
@@ -408,11 +422,11 @@ src/routes/preview/tools/[id]/+page.svelte
|
|||||||
ненулевой `--radius` для всех бордеров. Проверить токен `--radius` против
|
ненулевой `--radius` для всех бордеров. Проверить токен `--radius` против
|
||||||
референсов; возможно он должен быть 0 (или близко), а скругления оставить
|
референсов; возможно он должен быть 0 (или близко), а скругления оставить
|
||||||
только там, где они реально есть в дизайне.
|
только там, где они реально есть в дизайне.
|
||||||
- **DownloadButton**: нет отступа между текстом и размером файла
|
- **DownloadButton**: нет отступа между текстом и размером файла (`size`-чип).
|
||||||
(`size`-чип). Добавить `gap`/отступ внутри кнопки.
|
Добавить `gap`/отступ внутри кнопки.
|
||||||
- **Поля ввода (controls)**: выглядят без паддингов (возможно артефакт самой
|
- **Поля ввода (controls)**: выглядят без паддингов (возможно артефакт самой
|
||||||
витрины, но визуально плохо). Проверить паддинги в `TextField`/`SelectField`/
|
витрины, но визуально плохо). Проверить паддинги в `TextField`/`SelectField`/
|
||||||
`ColorField`/`SliderField` против референса и поправить.
|
`ColorField`/`SliderField` против референса и поправить.
|
||||||
- **Segmented**: на витрине последняя кнопка растянута и нечитаемо, что это
|
- **Segmented**: на витрине последняя кнопка растянута и нечитаемо, что это
|
||||||
сегмент. Проверить, как `Segmented` выглядит в реальных раскладках
|
сегмент. Проверить, как `Segmented` выглядит в реальных раскладках (C9–C14);
|
||||||
(C9–C14); возможно нужен фиксированный/авто размер сегментов или перенос.
|
возможно нужен фиксированный/авто размер сегментов или перенос.
|
||||||
|
|||||||
+32
-32
@@ -1,12 +1,11 @@
|
|||||||
# План: закрытие техдолга перед переездом `old/` (C17)
|
# План: закрытие техдолга перед переездом `old/` (C17)
|
||||||
|
|
||||||
> Статус: план к выполнению. Ветка `feat/redesign`.
|
> Статус: план к выполнению. Ветка `feat/redesign`. Входит в гейт перед
|
||||||
> Входит в гейт перед переездом старых файлов в `old/` (backlog №14; см. также
|
> переездом старых файлов в `old/` (backlog №14; см. также
|
||||||
> `archive/plan-migrate-remaining.md` — техдолг «чинить по заведённым задачам,
|
> `archive/plan-migrate-remaining.md` — техдолг «чинить по заведённым задачам,
|
||||||
> не игнорировать правилами»).
|
> не игнорировать правилами»). Целевое состояние по завершении: `svelte-check` →
|
||||||
> Целевое состояние по завершении: `svelte-check` → 0 errors, `pnpm test` →
|
> 0 errors, `pnpm test` → зелёный, `lint:all` → без ошибок (допустим только
|
||||||
> зелёный, `lint:all` → без ошибок (допустим только задокументированный остаток),
|
> задокументированный остаток), дизайн-токены → единый словарь `preview.css`.
|
||||||
> дизайн-токены → единый словарь `preview.css`.
|
|
||||||
|
|
||||||
## Состав техдолга (на 2026-09-07)
|
## Состав техдолга (на 2026-09-07)
|
||||||
|
|
||||||
@@ -15,9 +14,9 @@
|
|||||||
svelte-check, висит на каждом прогоне.
|
svelte-check, висит на каждом прогоне.
|
||||||
- **фейл теста** — `src/lib/palette.test.ts`: `ENOENT src/app.css` (файл
|
- **фейл теста** — `src/lib/palette.test.ts`: `ENOENT src/app.css` (файл
|
||||||
разъехался на `old.css` + `design2.css` при редизайне). Тест проверял
|
разъехался на `old.css` + `design2.css` при редизайне). Тест проверял
|
||||||
контрасты палитры по токенам старого `app.css` (`text/surface/bg/link/
|
контрасты палитры по токенам старого `app.css`
|
||||||
accent/danger/border`). При гейтах `pnpm test` красный, хотя 607 тестов
|
(`text/surface/bg/link/ accent/danger/border`). При гейтах `pnpm test`
|
||||||
проходят.
|
красный, хотя 607 тестов проходят.
|
||||||
- **design-tokens в `kit/**`** — ~335 ошибок `lint:all` от локального плагина
|
- **design-tokens в `kit/**`** — ~335 ошибок `lint:all` от локального плагина
|
||||||
`eslint-plugins/design-tokens` (захардкоженные цвета/размеры/длительности/
|
`eslint-plugins/design-tokens` (захардкоженные цвета/размеры/длительности/
|
||||||
z-index/`color-mix()` в `<style>`-блоках svelte), плюс stylelint по CSS.
|
z-index/`color-mix()` в `<style>`-блоках svelte), плюс stylelint по CSS.
|
||||||
@@ -43,46 +42,47 @@
|
|||||||
|
|
||||||
### Этап 2 — миграция `kit/**` на design-токены (main объём)
|
### Этап 2 — миграция `kit/**` на design-токены (main объём)
|
||||||
|
|
||||||
Цель — `lint:all` без ошибок design-tokens (все примитивы/`color-mix()`
|
Цель — `lint:all` без ошибок design-tokens (все примитивы/`color-mix()` уходят в
|
||||||
уходят в токены `preview.css`).
|
токены `preview.css`).
|
||||||
|
|
||||||
**Вариант А (основной): авто-фикс одноразовым скриптом.**
|
**Вариант А (основной): авто-фикс одноразовым скриптом.** Писать скрипт ТОЛЬКО
|
||||||
Писать скрипт ТОЛЬКО если он гарантированно ничего не испортит. Гарантии:
|
если он гарантированно ничего не испортит. Гарантии:
|
||||||
|
|
||||||
- Скрипт — одноразовый (`web/scripts/fix-design-tokens.mjs`), после прогона
|
- Скрипт — одноразовый (`web/scripts/fix-design-tokens.mjs`), после прогона
|
||||||
удаляется либо помечается неактуальным.
|
удаляется либо помечается неактуальным.
|
||||||
- Строгая карта замен: примитив → токен **1:1, только если значение точно
|
- Строгая карта замен: примитив → токен **1:1, только если значение точно
|
||||||
совпадает с уже существующим токеном словаря** `preview.css`
|
совпадает с уже существующим токеном словаря** `preview.css` (цвет →
|
||||||
(цвет → `--color-*`/`--brand-*`, радиус → `--radius-*`, размер →
|
`--color-*`/`--brand-*`, радиус → `--radius-*`, размер →
|
||||||
`--space-*`/`--size-*`, длительность → `--duration-*`, z-index → `--z-*`).
|
`--space-*`/`--size-*`, длительность → `--duration-*`, z-index → `--z-*`).
|
||||||
Никаких новых токенов скрипт не генерирует.
|
Никаких новых токенов скрипт не генерирует.
|
||||||
- Замены — только примитивы, не содержащие `var()`/`calc()`/`color-mix()`;
|
- Замены — только примитивы, не содержащие `var()`/`calc()`/`color-mix()`;
|
||||||
исключения по правилам плагина (0, 0px, 1px, проценты, unitless) не
|
исключения по правилам плагина (0, 0px, 1px, проценты, unitless) не трогаются.
|
||||||
трогаются. `color-mix()` → выносится в `preview.css` как производный
|
`color-mix()` → выносится в `preview.css` как производный `--color-*` токен
|
||||||
`--color-*` токен (может требовать ручного решения имени).
|
(может требовать ручного решения имени).
|
||||||
- Скрипт обрабатывает только `src/lib/components/kit/**` и `src/preview.css`
|
- Скрипт обрабатывает только `src/lib/components/kit/**` и `src/preview.css`
|
||||||
(scoped-пути плагина) и только `<style>`-блоки/`.css` — логику/разметку не
|
(scoped-пути плагина) и только `<style>`-блоки/`.css` — логику/разметку не
|
||||||
меняет, только строки стилей.
|
меняет, только строки стилей.
|
||||||
- После прогона: обязательный дифф-ревью (все изменения — замена значений на
|
- После прогона: обязательный дифф-ревью (все изменения — замена значений на
|
||||||
`var(--x)`, ничего удалено/переставлено), затем полный `lint:all` +
|
`var(--x)`, ничего удалено/переставлено), затем полный `lint:all` +
|
||||||
`svelte-check` + `pnpm test`. Если хоть что-то вне «замена примитива на
|
`svelte-check` + `pnpm test`. Если хоть что-то вне «замена примитива на токен»
|
||||||
токен» — скрипт считается проваленным, откатываем и переходим на вариант Б.
|
— скрипт считается проваленным, откатываем и переходим на вариант Б.
|
||||||
|
|
||||||
**Вариант Б (страховка): руками, пакетами по компонентам.**
|
**Вариант Б (страховка): руками, пакетами по компонентам.** Если скрипт не
|
||||||
Если скрипт не удался — чистим/заводим токены вручную, пакетами по компонентам
|
удался — чистим/заводим токены вручную, пакетами по компонентам (1–2 на задачу,
|
||||||
(1–2 на задачу, диф < ~500 строк). Отсутствующие токены (например
|
диф < ~500 строк). Отсутствующие токены (например `--space-2xl`/`--space-panel`,
|
||||||
`--space-2xl`/`--space-panel`, уже дважды обходились в SchemaTextSource)
|
уже дважды обходились в SchemaTextSource) добавляем в словарь `preview.css`
|
||||||
добавляем в словарь `preview.css` осознанно.
|
осознанно.
|
||||||
|
|
||||||
Гейт этапа: `pnpm --dir web lint:all` — только допустимый остаток (неиспользуемые
|
Гейт этапа: `pnpm --dir web lint:all` — только допустимый остаток
|
||||||
токены — варнинг), плюс регресс-прогон тестов и svelte-check.
|
(неиспользуемые токены — варнинг), плюс регресс-прогон тестов и svelte-check.
|
||||||
|
|
||||||
### Этап 3 — чистота словаря `preview.css`
|
### Этап 3 — чистота словаря `preview.css`
|
||||||
|
|
||||||
- Разобрать варнинги неиспользуемых токенов (check-tokens): либо использовать,
|
- Разобрать варнинги неиспользуемых токенов (check-tokens): либо использовать,
|
||||||
либо удалить из словаря.
|
либо удалить из словаря.
|
||||||
- После этапа 2 — повторный аудит токенов (`pnpm --dir web exec node
|
- После этапа 2 — повторный аудит токенов
|
||||||
scripts/check-tokens.mjs`): parity light/dark, hct-only, без мёртвых токенов.
|
(`pnpm --dir web exec node scripts/check-tokens.mjs`): parity light/dark,
|
||||||
|
hct-only, без мёртвых токенов.
|
||||||
|
|
||||||
### Этап 4 — финальные гейты (гейт переезда `old/`)
|
### Этап 4 — финальные гейты (гейт переезда `old/`)
|
||||||
|
|
||||||
@@ -92,12 +92,12 @@
|
|||||||
4. `pnpm --dir web lint:all` → 0 (или только задокументированный остаток)
|
4. `pnpm --dir web lint:all` → 0 (или только задокументированный остаток)
|
||||||
5. Обе ветки открываются: старый UI `/`, новый `/preview`
|
5. Обе ветки открываются: старый UI `/`, новый `/preview`
|
||||||
|
|
||||||
После зеленых гейтов — закрытие задач и переход к `backlog` №14
|
После зеленых гейтов — закрытие задач и переход к `backlog` №14 (переезд старых
|
||||||
(переезд старых файлов в `old/`).
|
файлов в `old/`).
|
||||||
|
|
||||||
## Порядок и коммиты
|
## Порядок и коммиты
|
||||||
|
|
||||||
- Коммиты атомарные, < ~500 строк (AGENTS). По завершении каждой мини-задачи —
|
- Коммиты атомарные, < ~500 строк (AGENTS). По завершении каждой мини-задачи —
|
||||||
ревью (агент не коммитит).
|
ревью (агент не коммитит).
|
||||||
- Техдолг чинится по заведённым задачам, правила линтеров не отключаются и не
|
- Техдолг чинится по заведённым задачам, правила линтеров не отключаются и не
|
||||||
игнорируются.
|
игнорируются.
|
||||||
|
|||||||
+83
-37
@@ -1,23 +1,39 @@
|
|||||||
# План разработки: easy-png-tools
|
# План разработки: easy-png-tools
|
||||||
|
|
||||||
> **Статус (2026-09-07):** Фаза 1 (полноценный TS-сайт) в основном выполнена —
|
> **Статус (2026-09-07):** Фаза 1 (полноценный TS-сайт) в основном выполнена —
|
||||||
> сайт живёт, каталог переведён в типизированный `registry-new` (121/125),
|
> сайт живёт, каталог переведён в типизированный `registry-new` (121/125), идёт
|
||||||
> идёт редизайн на `preview/*` по `plan-redesign.md` (параллельная ветка,
|
> редизайн на `preview/*` по `plan-redesign.md` (параллельная ветка, старый UI
|
||||||
> старый UI на `(old)/`). Фазы 2–8 (эталоны, Rust/wasm, CLI, harness) —
|
> на `(old)/`). Фазы 2–8 (эталоны, Rust/wasm, CLI, harness) — будущие, разделам
|
||||||
> будущие, разделам ниже не запущены.
|
> ниже не запущены.
|
||||||
|
|
||||||
## 0. Решения
|
## 0. Решения
|
||||||
|
|
||||||
- **Набор PNG-утилит, всё в браузере** (SvelteKit + `adapter-static`), данные не покидают машину.
|
- **Набор PNG-утилит, всё в браузере** (SvelteKit + `adapter-static`), данные не
|
||||||
- **Первый и главный приоритет — полностью рабочий TS-сайт**: максимум простых инструментов уровня EASY, UX/UI, пайплайны (несколько инструментов подряд), описания инструментов. Сайтом можно пользоваться уже после этой фазы.
|
покидают машину.
|
||||||
- **Никакого wasm/Rust на этом этапе.** TS-реализация на canvas + `ImageData` — эталон алгоритмов, из которого позже генерируются эталонные файлы для проверки Rust-порта. Rust-ядро, WASM и CLI — отдельные фазы позже, когда сайт живёт.
|
- **Первый и главный приоритет — полностью рабочий TS-сайт**: максимум простых
|
||||||
- **Пайплайн = JSON-список шагов** `{tool, params}`. Формат закладывается сейчас и потом станет общим для браузера, wasm и CLI.
|
инструментов уровня EASY, UX/UI, пайплайны (несколько инструментов подряд),
|
||||||
- **Реестр инструментов** (id, title, description, схема параметров, `run`) — источник истины: из него генерируются страницы, формы, пайплайны. Позже — общий формат для Rust-парсера.
|
описания инструментов. Сайтом можно пользоваться уже после этой фазы.
|
||||||
- Валидация будущего Rust-порта — эталонные файлы, сгенерированные из TS-реализации. Сравнение: **бит-в-бит** для чистых пиксельных операций, **perceptual diff** для canvas-зависимых.
|
- **Никакого wasm/Rust на этом этапе.** TS-реализация на canvas + `ImageData` —
|
||||||
- **Единая точка исполнения инструментов.** Страницы вызывают операции только через `web/src/lib/tools/executor.ts::executeStep` — нигде напрямую `tool.run`. Контракт уже асинхронный (`Promise<PixelImage>`), поэтому:
|
эталон алгоритмов, из которого позже генерируются эталонные файлы для проверки
|
||||||
- когда появятся тяжёлые MEDIUM-операции (свёртки, квантование), внутренности `executeStep` переезжают в Web Worker — воркер импортирует тот же чистый core как TS-фолбэк, алгоритмы не дублируются, вызывающий код не меняется;
|
Rust-порта. Rust-ядро, WASM и CLI — отдельные фазы позже, когда сайт живёт.
|
||||||
- в WASM-фазе тот же воркер принимает wasm-модуль и диспетчеризует по `toolId` — свитч A/B из фазы 5 сводится к замене реализации внутри исполнителя;
|
- **Пайплайн = JSON-список шагов** `{tool, params}`. Формат закладывается сейчас
|
||||||
- до появления MEDIUM-операций воркер не вводится: EASY-операции быстры и не блокируют UI.
|
и потом станет общим для браузера, wasm и CLI.
|
||||||
|
- **Реестр инструментов** (id, title, description, схема параметров, `run`) —
|
||||||
|
источник истины: из него генерируются страницы, формы, пайплайны. Позже —
|
||||||
|
общий формат для Rust-парсера.
|
||||||
|
- Валидация будущего Rust-порта — эталонные файлы, сгенерированные из
|
||||||
|
TS-реализации. Сравнение: **бит-в-бит** для чистых пиксельных операций,
|
||||||
|
**perceptual diff** для canvas-зависимых.
|
||||||
|
- **Единая точка исполнения инструментов.** Страницы вызывают операции только
|
||||||
|
через `web/src/lib/tools/executor.ts::executeStep` — нигде напрямую
|
||||||
|
`tool.run`. Контракт уже асинхронный (`Promise<PixelImage>`), поэтому:
|
||||||
|
- когда появятся тяжёлые MEDIUM-операции (свёртки, квантование), внутренности
|
||||||
|
`executeStep` переезжают в Web Worker — воркер импортирует тот же чистый
|
||||||
|
core как TS-фолбэк, алгоритмы не дублируются, вызывающий код не меняется;
|
||||||
|
- в WASM-фазе тот же воркер принимает wasm-модуль и диспетчеризует по `toolId`
|
||||||
|
— свитч A/B из фазы 5 сводится к замене реализации внутри исполнителя;
|
||||||
|
- до появления MEDIUM-операций воркер не вводится: EASY-операции быстры и не
|
||||||
|
блокируют UI.
|
||||||
|
|
||||||
## 1. Структура репозитория (этап TS)
|
## 1. Структура репозитория (этап TS)
|
||||||
|
|
||||||
@@ -39,15 +55,28 @@ easy-png-tools/
|
|||||||
|
|
||||||
### Фаза 1 — Полноценный TS-сайт (единственный приоритет)
|
### Фаза 1 — Полноценный TS-сайт (единственный приоритет)
|
||||||
|
|
||||||
**Цель: сайт, которым можно пользоваться.** Без wasm, без Rust, без clamp-семантики (в TS это делает `Uint8ClampedArray` сам).
|
**Цель: сайт, которым можно пользоваться.** Без wasm, без Rust, без
|
||||||
|
clamp-семантики (в TS это делает `Uint8ClampedArray` сам).
|
||||||
|
|
||||||
1.1 **Каркас и UX/UI.** SvelteKit + static adapter, дизайн-система (цвета, типографика, компоненты), общий лейаут, шапка с навигацией по категориям.
|
1.1 **Каркас и UX/UI.** SvelteKit + static adapter, дизайн-система (цвета,
|
||||||
|
типографика, компоненты), общий лейаут, шапка с навигацией по категориям.
|
||||||
|
|
||||||
1.2 **Ядро.** Тип `Image` (обёртка над `ImageData`), загрузка/декодирование файла, кодирование и скачивание, библиотека операций (color/alpha/geometry/format/text/analyze). Всё без DOM-зависимостей внутри `core`.
|
1.2 **Ядро.** Тип `Image` (обёртка над `ImageData`), загрузка/декодирование
|
||||||
|
файла, кодирование и скачивание, библиотека операций
|
||||||
|
(color/alpha/geometry/format/text/analyze). Всё без DOM-зависимостей внутри
|
||||||
|
`core`.
|
||||||
|
|
||||||
1.3 **Реестр инструментов.** Одна запись = `{ id, title, description, category, params: ParamDef[], run }`. Универсальный рендерер: страница инструмента и форма параметров строятся из записи реестра автоматически; маршруты-утилиты генерируются из реестра на билде. Новый инструмент = новая запись + функция `run`.
|
1.3 **Реестр инструментов.** Одна запись =
|
||||||
|
`{ id, title, description, category, params: ParamDef[], run }`. Универсальный
|
||||||
|
рендерер: страница инструмента и форма параметров строятся из записи реестра
|
||||||
|
автоматически; маршруты-утилиты генерируются из реестра на билде. Новый
|
||||||
|
инструмент = новая запись + функция `run`.
|
||||||
|
|
||||||
1.4 **Пайплайн-workspace** (`/workspace`): загрузил изображение → список шагов (инструмент + его параметры) → последовательное применение с превью каждого шага → скачивание финального результата. Добавление/удаление/перестановка шагов. Сохранение пайплайнов в localStorage, экспорт/импорт JSON. Общие компоненты: DropZone, Preview, ParamForm, Download, PipelineSteps.
|
1.4 **Пайплайн-workspace** (`/workspace`): загрузил изображение → список шагов
|
||||||
|
(инструмент + его параметры) → последовательное применение с превью каждого шага
|
||||||
|
→ скачивание финального результата. Добавление/удаление/перестановка шагов.
|
||||||
|
Сохранение пайплайнов в localStorage, экспорт/импорт JSON. Общие компоненты:
|
||||||
|
DropZone, Preview, ParamForm, Download, PipelineSteps.
|
||||||
|
|
||||||
1.5 **Массовая реализация EASY-инструментов** по категориям:
|
1.5 **Массовая реализация EASY-инструментов** по категориям:
|
||||||
|
|
||||||
@@ -56,29 +85,36 @@ easy-png-tools/
|
|||||||
- цвет: замена, тон, каналы, оттенки серого, инверсии;
|
- цвет: замена, тон, каналы, оттенки серого, инверсии;
|
||||||
- геометрия и холст: resize, crop, rotate, flip, border, padding, background;
|
- геометрия и холст: resize, crop, rotate, flip, border, padding, background;
|
||||||
- текст и простые эффекты: watermark, add-text, рамки, шум, pixelate;
|
- текст и простые эффекты: watermark, add-text, рамки, шум, pixelate;
|
||||||
- анализ: размеры, палитра, проверки, просмотр.
|
- анализ: размеры, палитра, проверки, просмотр. Приоритет — ширина, не глубина:
|
||||||
Приоритет — ширина, не глубина: как можно больше простых инструментов.
|
как можно больше простых инструментов.
|
||||||
|
|
||||||
1.6 **Полировка.** Описания и подсказки для всех инструментов, состояния загрузки/ошибок, доступность, пустые состояния.
|
1.6 **Полировка.** Описания и подсказки для всех инструментов, состояния
|
||||||
|
загрузки/ошибок, доступность, пустые состояния.
|
||||||
|
|
||||||
**Checkpoint:** загрузил PNG → применил пайплайн из N шагов → сохранил и вернул пайплайн → скачал результат. Все инструменты 1.5 работают на сайте. Ни одного wasm.
|
**Checkpoint:** загрузил PNG → применил пайплайн из N шагов → сохранил и вернул
|
||||||
|
пайплайн → скачал результат. Все инструменты 1.5 работают на сайте. Ни одного
|
||||||
|
wasm.
|
||||||
|
|
||||||
### Фаза 2 — Тестовые PNG и генератор эталонов
|
### Фаза 2 — Тестовые PNG и генератор эталонов
|
||||||
|
|
||||||
- Фикстурные входы: градиенты/паттерны/шум + пара «настоящих» PNG.
|
- Фикстурные входы: градиенты/паттерны/шум + пара «настоящих» PNG.
|
||||||
- Правило: сравниваем **распакованные пиксели**, не байты файла.
|
- Правило: сравниваем **распакованные пиксели**, не байты файла.
|
||||||
- **Checkpoint:** в `tests/reference/<op>/` лежат `input-*.png` + `expected-*.png`.
|
- **Checkpoint:** в `tests/reference/<op>/` лежат `input-*.png` +
|
||||||
|
`expected-*.png`.
|
||||||
|
|
||||||
### Фаза 3 — Playwright-тесты
|
### Фаза 3 — Playwright-тесты
|
||||||
|
|
||||||
- `tools/gen-references.ts`: грузит страницу, гоняет TS-эталон, сохраняет `expected-*.png`.
|
- `tools/gen-references.ts`: грузит страницу, гоняет TS-эталон, сохраняет
|
||||||
|
`expected-*.png`.
|
||||||
- Регресс: изменение TS-ядра ловится тестами.
|
- Регресс: изменение TS-ядра ловится тестами.
|
||||||
- **Checkpoint:** регресс-тесты падают при изменении выхода эталона.
|
- **Checkpoint:** регресс-тесты падают при изменении выхода эталона.
|
||||||
|
|
||||||
### Фаза 4 — Rust-ядро
|
### Фаза 4 — Rust-ядро
|
||||||
|
|
||||||
- Порт 1:1 чистых операций (первым делом те, что покрыты эталонными файлами) с `Image` ↔ TS `ImageData`.
|
- Порт 1:1 чистых операций (первым делом те, что покрыты эталонными файлами) с
|
||||||
- `clamp_to_u8` с семантикой `ToUint8Clamp` (round-half-to-even, ≥255 → 255) + юнит-тесты на краях.
|
`Image` ↔ TS `ImageData`.
|
||||||
|
- `clamp_to_u8` с семантикой `ToUint8Clamp` (round-half-to-even, ≥255 → 255) +
|
||||||
|
юнит-тесты на краях.
|
||||||
- `cargo test` читает эталонные файлы напрямую, **без браузера**.
|
- `cargo test` читает эталонные файлы напрямую, **без браузера**.
|
||||||
- **Checkpoint:** `cargo test` зелёный на всех эталонных файлах.
|
- **Checkpoint:** `cargo test` зелёный на всех эталонных файлах.
|
||||||
|
|
||||||
@@ -97,14 +133,18 @@ easy-png-tools/
|
|||||||
|
|
||||||
### Фаза 7 — Сквозная проверка идентичности
|
### Фаза 7 — Сквозная проверка идентичности
|
||||||
|
|
||||||
- Матрица: **эталонные файлы (из TS) ↔ Rust-native ↔ Rust-wasm**, одна команда `harness compare`.
|
- Матрица: **эталонные файлы (из TS) ↔ Rust-native ↔ Rust-wasm**, одна команда
|
||||||
|
`harness compare`.
|
||||||
- **Checkpoint:** сходимость по всем покрытым операциям во всех трёх рантаймах.
|
- **Checkpoint:** сходимость по всем покрытым операциям во всех трёх рантаймах.
|
||||||
|
|
||||||
### Фаза 8 — Масштабирование и продвинутое
|
### Фаза 8 — Масштабирование и продвинутое
|
||||||
|
|
||||||
- Порт оставшихся EASY → MEDIUM (свёртки, морфология, квантование; кодеки через крейт `image`).
|
- Порт оставшихся EASY → MEDIUM (свёртки, морфология, квантование; кодеки через
|
||||||
- Canvas-зависимые операции (текст, градиенты, штампы, `ctx.filter`): остаются в TS с perceptual-классом либо реимплементация в Rust.
|
крейт `image`).
|
||||||
- batch-страница: дроп папки → wasm по всем файлам → zip. localStorage-пресеты (тот же JSON, что у CLI).
|
- Canvas-зависимые операции (текст, градиенты, штампы, `ctx.filter`): остаются в
|
||||||
|
TS с perceptual-классом либо реимплементация в Rust.
|
||||||
|
- batch-страница: дроп папки → wasm по всем файлам → zip. localStorage-пресеты
|
||||||
|
(тот же JSON, что у CLI).
|
||||||
- HARD: compress/optimize через oxipng/zopfli (в Rust — нативно).
|
- HARD: compress/optimize через oxipng/zopfli (в Rust — нативно).
|
||||||
|
|
||||||
## 3. Правила сравнения (для фаз 4–7)
|
## 3. Правила сравнения (для фаз 4–7)
|
||||||
@@ -120,12 +160,18 @@ easy-png-tools/
|
|||||||
|
|
||||||
## 4. Риски и контрмеры
|
## 4. Риски и контрмеры
|
||||||
|
|
||||||
- **Реестр разрастается, страницы дублируются** — один универсальный рендерер страниц/форм из записей реестра; инструмент = данные + `run`.
|
- **Реестр разрастается, страницы дублируются** — один универсальный рендерер
|
||||||
- **Семантика пайплайна** — зафиксировать: каждый шаг применяется к результату предыдущего; единый тип `Image` на всём пути; схема параметров на шаге.
|
страниц/форм из записей реестра; инструмент = данные + `run`.
|
||||||
- **Canvas-операции не бит-в-бит** — класс perceptual закладывается на Фазе 2–3, а не в конце.
|
- **Семантика пайплайна** — зафиксировать: каждый шаг применяется к результату
|
||||||
- **WASM + Vite** — спайк на Фазе 5 до массовой интеграции (риск отложен сознательно: сайт уже живёт на TS).
|
предыдущего; единый тип `Image` на всём пути; схема параметров на шаге.
|
||||||
- **Расползание реестра при будущем Rust-порте** — схема параметров в JSON, её можно парсить и из Rust.
|
- **Canvas-операции не бит-в-бит** — класс perceptual закладывается на Фазе 2–3,
|
||||||
- **Разные декодеры PNG** — фикстурные PNG генерировать самим, чтобы не тащить чужие артефакты.
|
а не в конце.
|
||||||
|
- **WASM + Vite** — спайк на Фазе 5 до массовой интеграции (риск отложен
|
||||||
|
сознательно: сайт уже живёт на TS).
|
||||||
|
- **Расползание реестра при будущем Rust-порте** — схема параметров в JSON, её
|
||||||
|
можно парсить и из Rust.
|
||||||
|
- **Разные декодеры PNG** — фикстурные PNG генерировать самим, чтобы не тащить
|
||||||
|
чужие артефакты.
|
||||||
|
|
||||||
## 5. Команды верификации
|
## 5. Команды верификации
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
# Хранить данные для сортировки по популярности инструментов
|
||||||
|
|
||||||
|
Идея выложить на Vercel и использовать Vercel KV (на базе Upstash Redis) — это
|
||||||
|
отличный, рабочий и очень быстрый в реализации вариант. Для 150 инструментов
|
||||||
|
объем данных будет крошечным, но здесь есть пара критически важных нюансов по
|
||||||
|
лимитам, о которых нужно знать на старте, чтобы бесплатный тариф не отключился в
|
||||||
|
первый же месяц. [1]
|
||||||
|
|
||||||
|
## ⚠️ Подводный камень: Лимиты Vercel KV на Free-тарифе
|
||||||
|
|
||||||
|
На бесплатном тарифе (Hobby) для Vercel KV действуют следующие ограничения: [2]
|
||||||
|
|
||||||
|
- 30 000 запросов в месяц (суммарно чтение + запись).
|
||||||
|
- Ограничение около 3 000 запросов в день. [2, 3]
|
||||||
|
|
||||||
|
Если делать запрос к базе на каждый просмотр страницы («посещение»), лимит в 30
|
||||||
|
тысяч улетит мгновенно при первой же паре сотен посетителей в день.
|
||||||
|
|
||||||
|
## 💡 Как сделать это правильно и бесплатно
|
||||||
|
|
||||||
|
Чтобы не выйти за рамки лимитов, архитектуру нужно построить экономно:
|
||||||
|
|
||||||
|
## 1. Чтение: Сортировка на этапе сборки (ISR / SSG)
|
||||||
|
|
||||||
|
Не нужно запрашивать KV из Edge-функции при каждом открытии сайта пользователем.
|
||||||
|
|
||||||
|
- Используйте Incremental Static Regeneration (ISR) (если вы делаете на Next.js)
|
||||||
|
или раз в сутки запускайте Cron-сид (Vercel Cron).
|
||||||
|
- Сайт раз в несколько часов запрашивает весь список из KV (всего 1 запрос на
|
||||||
|
получение общей структуры), сортирует инструменты и кеширует эту страницу как
|
||||||
|
статическую.
|
||||||
|
- Результат: 100 000 пользователей увидят отсортированный список, а вы потратите
|
||||||
|
всего пару запросов к KV в день.
|
||||||
|
|
||||||
|
## 2. Запись: Защита от накрутки и экономия («Добавление в избранное»)
|
||||||
|
|
||||||
|
«Избранное» — идеальная метрика для KV, так как её нажимают редко. А вот
|
||||||
|
«Посещения» лучше отправлять не на каждый клик, а пачками (батчингом) или
|
||||||
|
использовать для просмотров бесплатную Vercel Web Analytics (там лимит 25 000
|
||||||
|
событий в месяц обновляется отдельно).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💻 Оптимальная структура данных в Redis (KV)
|
||||||
|
|
||||||
|
Вместо 150 отдельных ключей, храните всё в одной хэш-таблице (Hash) или
|
||||||
|
сортированном множестве (Sorted Set). Это сэкономит количество запросов. Вариант
|
||||||
|
структуры через Hash:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// Запись / инкремент (в API роуте Vercel)
|
||||||
|
import { kv } from "@vercel/kv";
|
||||||
|
//Когда пользователь зашел на инструмент 'crop-png'
|
||||||
|
await kv.hincrby("tools:visits", "crop-png", 1);
|
||||||
|
// Когда добавил в избранное
|
||||||
|
await kv.hincrby("tools:favorites", "crop-png", 1);
|
||||||
|
```
|
||||||
|
|
||||||
|
Как забрать всё одним запросом для сортировки:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// Получаем сразу все просмотры (вернет объект вида { 'crop-png': '120', 'resize-png': '85' })
|
||||||
|
const allVisits = await kv.hgetall("tools:visits");
|
||||||
|
const allFavorites = await kv.hgetall("tools:favorites");
|
||||||
|
// Дальше соединяем в массив на клиенте/сервере и сортируем:
|
||||||
|
// Популярность = Просмотры + (Избранное * 5) <- так лайки весят больше
|
||||||
|
```
|
||||||
|
|
||||||
|
## ⚡ Альтернатива: Cloudflare Workers + KV
|
||||||
|
|
||||||
|
Если вы боитесь упереться в 30 000 запросов на Vercel, рассмотрите связку
|
||||||
|
Cloudflare Workers + Workers KV. Там на бесплатном тарифе дают: [4]
|
||||||
|
|
||||||
|
- 100 000 запросов на запись в сутки.
|
||||||
|
- 10 000 000 (10 млн) запросов на чтение в сутки.
|
||||||
|
- Для пет-проекта этого хватит навсегда, даже если вы будете делать запросы
|
||||||
|
напрямую с фронтенда без кеширования.
|
||||||
+48
-22
@@ -1,7 +1,9 @@
|
|||||||
# Карта инструментов: реализовано · добавить · идеи
|
# Карта инструментов: реализовано · добавить · идеи
|
||||||
|
|
||||||
> Живой документ для сверки с каталогом. Формат записи: `id / название — параметры через запятую`.
|
> Живой документ для сверки с каталогом. Формат записи:
|
||||||
> Источник сравнения: onlinepngtools.com (311 уникальных; 107 из них — нишевые клоны серий Logo*/Icon*/Stamp*/Signature* поверх обычных операций).
|
> `id / название — параметры через запятую`. Источник сравнения:
|
||||||
|
> onlinepngtools.com (311 уникальных; 107 из них — нишевые клоны серий
|
||||||
|
> Logo*/Icon*/Stamp*/Signature* поверх обычных операций).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -21,8 +23,10 @@
|
|||||||
- png-to-base64 / base64-to-png — строка
|
- png-to-base64 / base64-to-png — строка
|
||||||
- png-to-data-uri / data-uri-to-png — строка
|
- png-to-data-uri / data-uri-to-png — строка
|
||||||
- png-to-hex — rrggbbaa по строкам / hex-to-png — tokens + width
|
- png-to-hex — rrggbbaa по строкам / hex-to-png — tokens + width
|
||||||
- png-to-bytes — десятичные RGBA-байты по строкам / bytes-to-png — tokens + width
|
- png-to-bytes — десятичные RGBA-байты по строкам / bytes-to-png — tokens +
|
||||||
- png-to-rgb-values — rgba(r,g,b,a) по пикселям / rgb-values-to-png — числа + width
|
width
|
||||||
|
- png-to-rgb-values — rgba(r,g,b,a) по пикселям / rgb-values-to-png — числа +
|
||||||
|
width
|
||||||
- svg-to-png — width результата
|
- svg-to-png — width результата
|
||||||
- verify-is-png — текстовый источник (base64/data-uri), вердикт по сигнатуре
|
- verify-is-png — текстовый источник (base64/data-uri), вердикт по сигнатуре
|
||||||
|
|
||||||
@@ -41,7 +45,8 @@
|
|||||||
- harden-alpha-png — threshold
|
- harden-alpha-png — threshold
|
||||||
- feather-edges-png — radius (размытие только альфы)
|
- feather-edges-png — radius (размытие только альфы)
|
||||||
- clean-edges-png — radius (defringe: RGB от ближайшего непрозрачного)
|
- clean-edges-png — radius (defringe: RGB от ближайшего непрозрачного)
|
||||||
- watermark-image-png — вторая картинка-знак (загружается на странице), scale, opacity, position, margin
|
- watermark-image-png — вторая картинка-знак (загружается на странице), scale,
|
||||||
|
opacity, position, margin
|
||||||
- despeckle-alpha-png / close-holes-png — radius
|
- despeckle-alpha-png / close-holes-png — radius
|
||||||
- center-by-alpha-png — без параметров
|
- center-by-alpha-png — без параметров
|
||||||
- round-corners-png — radius
|
- round-corners-png — radius
|
||||||
@@ -52,7 +57,8 @@
|
|||||||
|
|
||||||
### Цвет
|
### Цвет
|
||||||
|
|
||||||
- grayscale-png / invert-colors-png / sepia-png / auto-contrast-png — без параметров
|
- grayscale-png / invert-colors-png / sepia-png / auto-contrast-png — без
|
||||||
|
параметров
|
||||||
- adjust-brightness-contrast-png — brightness, contrast
|
- adjust-brightness-contrast-png — brightness, contrast
|
||||||
- change-png-hue — degrees
|
- change-png-hue — degrees
|
||||||
- extract-channel-png — channel (r/g/b)
|
- extract-channel-png — channel (r/g/b)
|
||||||
@@ -73,7 +79,8 @@
|
|||||||
|
|
||||||
### Разложение каналов
|
### Разложение каналов
|
||||||
|
|
||||||
- png-to-hsl / png-to-hsv / png-to-hsi — component (h/s/l и т.п.), display (gray | space-as-rgb)
|
- png-to-hsl / png-to-hsv / png-to-hsi — component (h/s/l и т.п.), display (gray
|
||||||
|
| space-as-rgb)
|
||||||
- png-to-cmyk — component (c/m/y/k), display
|
- png-to-cmyk — component (c/m/y/k), display
|
||||||
- png-to-ycbcr — component (y/cb/cr), display
|
- png-to-ycbcr — component (y/cb/cr), display
|
||||||
- png-to-lab — component (l/a/b), display
|
- png-to-lab — component (l/a/b), display
|
||||||
@@ -117,12 +124,15 @@
|
|||||||
### Маски по свойствам пикселей
|
### Маски по свойствам пикселей
|
||||||
|
|
||||||
- show-transparent-png — color, opacity (подсветка прозрачных/полупрозрачных)
|
- show-transparent-png — color, opacity (подсветка прозрачных/полупрозрачных)
|
||||||
- show-grayscale-pixels-png — tolerance, mode (binary/highlight), highlightColor, highlightOpacity
|
- show-grayscale-pixels-png — tolerance, mode (binary/highlight),
|
||||||
|
highlightColor, highlightOpacity
|
||||||
- show-color-pixels-png — tolerance, mode, highlightColor, highlightOpacity
|
- show-color-pixels-png — tolerance, mode, highlightColor, highlightOpacity
|
||||||
- light-pixel-mask-png — threshold, mode, highlightColor, highlightOpacity
|
- light-pixel-mask-png — threshold, mode, highlightColor, highlightOpacity
|
||||||
- dark-pixel-mask-png — threshold, mode, highlightColor, highlightOpacity
|
- dark-pixel-mask-png — threshold, mode, highlightColor, highlightOpacity
|
||||||
- unique-color-mask-png — rarity (макс. повторов), mode, highlightColor, highlightOpacity
|
- unique-color-mask-png — rarity (макс. повторов), mode, highlightColor,
|
||||||
- extract-color-from-png — color, tolerance (оставить близкие, остальное прозрачным)
|
highlightOpacity
|
||||||
|
- extract-color-from-png — color, tolerance (оставить близкие, остальное
|
||||||
|
прозрачным)
|
||||||
|
|
||||||
### Генерация
|
### Генерация
|
||||||
|
|
||||||
@@ -130,7 +140,8 @@
|
|||||||
- single-color-png — width, height, color
|
- single-color-png — width, height, color
|
||||||
- random-noise-png — width, height, seed
|
- random-noise-png — width, height, seed
|
||||||
- linear-gradient-png — width, height, fromColor, toColor, direction
|
- linear-gradient-png — width, height, fromColor, toColor, direction
|
||||||
- text-to-png — text, fontSize, font, bold, color, transparentBg, backgroundColor, padding
|
- text-to-png — text, fontSize, font, bold, color, transparentBg,
|
||||||
|
backgroundColor, padding
|
||||||
- emoji-to-png — emoji, size
|
- emoji-to-png — emoji, size
|
||||||
- placeholder-png — width, height, backgroundColor, color, showText
|
- placeholder-png — width, height, backgroundColor, color, showText
|
||||||
- color-spectrum-png — width, height, direction, saturation, lightness
|
- color-spectrum-png — width, height, direction, saturation, lightness
|
||||||
@@ -139,13 +150,17 @@
|
|||||||
|
|
||||||
### Текст
|
### Текст
|
||||||
|
|
||||||
- add-text-png — text, fontSize, color, font, bold, position (3×3), margin, plate, plateColor, plateOpacity
|
- add-text-png — text, fontSize, color, font, bold, position (3×3), margin,
|
||||||
- date-stamp-png — format, fontSize, color, font, bold, position, margin, plate, plateColor, plateOpacity
|
plate, plateColor, plateOpacity
|
||||||
- watermark-tile-png — text, fontSize, color, opacity, angle, stepX, stepY, font, bold
|
- date-stamp-png — format, fontSize, color, font, bold, position, margin, plate,
|
||||||
|
plateColor, plateOpacity
|
||||||
|
- watermark-tile-png — text, fontSize, color, opacity, angle, stepX, stepY,
|
||||||
|
font, bold
|
||||||
|
|
||||||
### Палитры и цветовые утилиты
|
### Палитры и цветовые утилиты
|
||||||
|
|
||||||
- color-wheel-generator — size, кольца/сектора, показ hex при клике (у нас — статичный свотч-полотно)
|
- color-wheel-generator — size, кольца/сектора, показ hex при клике (у нас —
|
||||||
|
статичный свотч-полотно)
|
||||||
- complementary-palette — baseColor
|
- complementary-palette — baseColor
|
||||||
- monochromatic-palette — baseColor, count
|
- monochromatic-palette — baseColor, count
|
||||||
- analogous-palette — baseColor, spread
|
- analogous-palette — baseColor, spread
|
||||||
@@ -154,7 +169,8 @@
|
|||||||
- similar-shades-palette — baseColor, count, range
|
- similar-shades-palette — baseColor, count, range
|
||||||
- sort-colors — источник (палитра-картинка или список), порядок (hsl/luma)
|
- sort-colors — источник (палитра-картинка или список), порядок (hsl/luma)
|
||||||
- mix-colors — colors[], веса?
|
- mix-colors — colors[], веса?
|
||||||
- average-color — colors[]; blend-two — a, b, steps; step-between — a, b, steps (три частных случая одного движка)
|
- average-color — colors[]; blend-two — a, b, steps; step-between — a, b, steps
|
||||||
|
(три частных случая одного движка)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -193,7 +209,8 @@
|
|||||||
|
|
||||||
### Прочее единичное
|
### Прочее единичное
|
||||||
|
|
||||||
- pick-a-color — пипетка уже есть в превью; отдельная страница не планируется (покрыто)
|
- pick-a-color — пипетка уже есть в превью; отдельная страница не планируется
|
||||||
|
(покрыто)
|
||||||
- preview/test на цветных фонах — фича превью, не инструмент (решить позже)
|
- preview/test на цветных фонах — фича превью, не инструмент (решить позже)
|
||||||
- extract-barcode — HARD, вне планов
|
- extract-barcode — HARD, вне планов
|
||||||
|
|
||||||
@@ -201,8 +218,17 @@
|
|||||||
|
|
||||||
## 3. Идеи на рассмотрение (нужна архитектура или спорная ценность)
|
## 3. Идеи на рассмотрение (нужна архитектура или спорная ценность)
|
||||||
|
|
||||||
- **Region-инструменты** — требуется UI выделения прямоугольника/ластика на превью: censor-region, erase-region, pixelate-area, blur-area, sharpen-area, reverse-colors-area. Один раз делаем selection-компонент — получаем сразу шесть инструментов.
|
- **Region-инструменты** — требуется UI выделения прямоугольника/ластика на
|
||||||
- **Мультифайловый вывод** — сейчас инструмент отдаёт одну картинку: split-into-parts, gif-to-frames, separate-colors, multiply-grid-as-files. Нужен механизм «результат = набор файлов» (zip?).
|
превью: censor-region, erase-region, pixelate-area, blur-area, sharpen-area,
|
||||||
- **Анимационные эффекты** — slow-reveal, fade-in/out, disappearing, scrolling: это видео/GIF на выходе, а не PNG. Отдельное решение о формате результата.
|
reverse-colors-area. Один раз делаем selection-компонент — получаем сразу
|
||||||
- **HARD-хвост** — glitch-art, extract-signature, handwritten→digital, extract-barcode.
|
шесть инструментов.
|
||||||
- **Нишевые серии** (logo/icon/stamp/signature — 107 клонов у источника) — сознательно не копируем: это обычные операции над конкретным контентом, у нас они доступны через базовые инструменты + цепочки.
|
- **Мультифайловый вывод** — сейчас инструмент отдаёт одну картинку:
|
||||||
|
split-into-parts, gif-to-frames, separate-colors, multiply-grid-as-files.
|
||||||
|
Нужен механизм «результат = набор файлов» (zip?).
|
||||||
|
- **Анимационные эффекты** — slow-reveal, fade-in/out, disappearing, scrolling:
|
||||||
|
это видео/GIF на выходе, а не PNG. Отдельное решение о формате результата.
|
||||||
|
- **HARD-хвост** — glitch-art, extract-signature, handwritten→digital,
|
||||||
|
extract-barcode.
|
||||||
|
- **Нишевые серии** (logo/icon/stamp/signature — 107 клонов у источника) —
|
||||||
|
сознательно не копируем: это обычные операции над конкретным контентом, у нас
|
||||||
|
они доступны через базовые инструменты + цепочки.
|
||||||
|
|||||||
+7
-2
@@ -7,7 +7,9 @@
|
|||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
"dev": "pnpm --dir web dev",
|
"dev": "pnpm --dir web dev",
|
||||||
"build": "pnpm --dir web build",
|
"build": "pnpm --dir web build",
|
||||||
"format": "pnpm --dir web format",
|
"format": "pnpm format:docs && pnpm --dir web format",
|
||||||
|
"format:docs": "prettier --write docs --log-level warn",
|
||||||
|
"check:docs": "prettier --check docs",
|
||||||
"html": "pnpm --dir refs build && node scripts/extract-static.mjs refs/.next/server/app refs-html",
|
"html": "pnpm --dir refs build && node scripts/extract-static.mjs refs/.next/server/app refs-html",
|
||||||
"cdp-audit": "pnpm --dir web refs-cdp-audit:large"
|
"cdp-audit": "pnpm --dir web refs-cdp-audit:large"
|
||||||
},
|
},
|
||||||
@@ -21,5 +23,8 @@
|
|||||||
"onFail": "download"
|
"onFail": "download"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"type": "module"
|
"type": "module",
|
||||||
|
"devDependencies": {
|
||||||
|
"prettier": "^3.9.6"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+16
-1
@@ -205,4 +205,19 @@ settings:
|
|||||||
|
|
||||||
importers:
|
importers:
|
||||||
|
|
||||||
.: {}
|
.:
|
||||||
|
devDependencies:
|
||||||
|
prettier:
|
||||||
|
specifier: ^3.9.6
|
||||||
|
version: 3.9.6
|
||||||
|
|
||||||
|
packages:
|
||||||
|
|
||||||
|
prettier@3.9.6:
|
||||||
|
resolution: {integrity: sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==}
|
||||||
|
engines: {node: '>=14'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
snapshots:
|
||||||
|
|
||||||
|
prettier@3.9.6: {}
|
||||||
|
|||||||
Reference in New Issue
Block a user