# План: составные типы параметров и полная типизация pipeline > Статус: **в реализации.** Фаза 0 (фундамент) ✔, Фаза 1 (рабочий инструмент > в preview) ✔, Фаза 2 (простые инструменты без составных типов) — переведены > все одиночные инструменты (18 шт). Фаза 3 — **составной тип `dimension` > полностью переведён** (12 инструментов: create-empty, single-color, > random-noise, linear-gradient, color-spectrum, random-colors, draw-grid, > placeholder, fit-on-background, change-canvas-size, resize, crop); preview > научен применять **генераторы** (`executeGenerate`, кнопка Generate) и > рендерить все kinds схемы (slider/number/color/select/checkbox/dimension). > Следующее: `color-pair` (пилот blend-two-png → two-colors → step-colors). > > Ключевые файлы нового registry: `web/src/lib/registry-new/{types,index,*}.ts` > (по файлу на категорию: geometry/alpha/convert/analyze/filters/color/generate) > > - `web/src/lib/preview/categories.ts`, > `web/src/lib/registry-schema.ts` (kind `dimension`), `kit/fields/DimensionField.svelte`, > `preview/executor/index.ts` (`executeGenerate`), `SchemaFields.svelte` > (полный рендер kinds). Старый `web/src/lib/registry.ts` разбит по категориям > в `web/src/lib/registry/` (см. `registry.ts` — тонкий баррель). ## Ключевая стратегия: параллельная сборка, старый UI не трогаем Старый UI (группа `(old)/`, `ParamForm.svelte` + старый pipeline) **продолжает работать как сейчас, без рефакторинга**. Новый `ToolSchema
` строится рядом и служит источником для **нового UI**. Инструменты переписываются под новый registry по мере нужды — для нового UI. Это та же логика, что в `plan-redesign.md` применена к дизайну: параллельная сборка, изоляция от старого, затем новый становится основным и старый удаляется вместе со старым дизайном. **Правило копий (важно!):** если для нового UI/registry нужно внести изменения в файл, который **прямо или косвенно** уже используется `(old)` веткой, — этот файл **НЕ трогаем**. Вместо этого делаем **копию** в новом месте (например, в `lib/preview/`) и правим копию. Это приводит к дублированию, но **гарантированно не задевает старую ветку сайта**. Пример: категории — новый `lib/preview/categories.ts` (object as const) копирует и заменяет собой `../categories` для нового кода, старый `categories.ts` обслуживает `(old)` и остаётся без изменений. После перехода (Фаза 5) копия становится основной, а исходник удаляется вместе со старым UI. **Разделение схем: две независимые схемы.** | Схема | Источник | Использование | | --------------------- | ------------------------------------------------- | ---------------------------------------------------------------- | | Старый `ParamDef[]` | В `ToolEntry.params` — **остаётся без изменений** | Старый UI, старый pipeline (`ParamForm.svelte`, executor, chain) | | Новый `ToolSchema
` | Новое поле `ToolEntry.schema` — добавляется рядом | Новый UI (kit/`ParamControl`, новый pipeline) | - Старая и новая схемы **не выводятся друг из друга** — это два независимых описания инструмента. - Пока новый UI не готов — `schema` просто отсутствует у большинства инструментов, старый UI ничего не замечает. - Удаление старого UI (после перехода) попутно удаляет и старый `ParamDef[]` ### Разделение registry по UI (новое решение, Фаза 2+) Помимо двух схем, registry тоже разделён по UI (по факту миграции): - **Старый registry** (`web/src/lib/registry.ts` + `registry/` + `registry-helpers.ts`) — работает на старом UI, использует `ParamDef[]` и `tool-views.ts`. **Не трогаем**; идёт под удаление вместе со старым UI. - **Новый registry** (`web/src/lib/registry-new/`) — строится **с нуля «как надо»**: `ToolEntry
` с обязательным `schema`, типизированный `run`, **без** `ParamDef[]`
и **без** связи со старым. Импортирует core-функции (`expandCanvas`,
`strokeImage`, …) напрямую. Наполняется **по-инструментно** по мере миграции
(не разовым переводом всех 130). Preview показывает только переведённые
инструменты.
Preview (`catalog.ts`, маршруты `preview/**`, `SchemaToolView`, `SchemaFields`)
импортируют из `$lib/registry-new`. После удаления старого UI `registry-new/`
переименуется в `registry`.
**Executor/worker для нового UI (осознанное дублирование):** общий
`executor.worker.ts` резолвит инструменты по `id` в **старом** registry — для
новых инструментов их там нет. Поэтому `executor.ts` + `executor.worker.ts`
**скопированы** в `web/src/lib/preview/` и переписаны под `registry-new` +
`sanitizeSchemaParams`. Старый executor/worker под старый UI остаются
нетронутыми; `SchemaToolView` использует `$lib/preview/executor`. Это
дублирование необходимо до ухода старого UI (после — preview-executor заменяет
общий).
## Проблема (текущая)
- `ParamDef` — плоский union из 6 примитивов (`number | slider | select |
checkbox | color | text`). Параметры инструмента — отдельные записи в плоском
массиве.
- `run()`/`generate()` получают `Record `
строится рядом для нового UI (см. «Ключевая стратегия» выше).
## Целевая архитектура (на примере «добавить рамку»)
Цель: «добавить рамку: ширина и цвет» — **один интерфейс**, а не два
слабо связанных поля.
```ts
// 1. Runtime-тип — что получает run/generate
interface AddBorderParams {
thickness: number; // рамка: ширина
color: string; // рамка: цвет
}
// 2. Типизированная UI-схема, проверяемая компилятором против Params
// Ключи схемы обязаны совпадать с полями Params; default совместим с типом.
const addBorderSchema = toolSchema : проверяет, что ключи схемы === полям P и типы сходятся ───
type FieldsOf (fields: FieldsOf ): ToolSchema { ... }
// TS выдаст ошибку, если schema содержит ключ, отсутствующий в Params, или
// если type поля не совпадает с типом Params[K].
```
Ключевая идея связки: **builder `field.x ` заставляет ключи схемы
совпадать с ключами `P`. Это и есть защита от рассинхрона.
## Составные типы (для переиспользования и группировки)
| Тип | Под-поля | Инструментов |
| ------------ | -------------------------- | ------------ |
| `dimension` | width + height | 12 |
| `color-pair` | from + to | 4 |
| `offset` | x + y | 4 |
| `gradient` | from + to + dir (+ type?) | 1 |
| `position9` | position (3×3 grid) | 3 |
| `font-style` | font + size + bold + color | 3-4 |
| `plate` | enabled + color + opacity | 2-3 |
Составной тип = группы полей, которые в интерфейсе `Params` могут разворачиваться
во вложенный объект или плоские ключи — решается на этапе реализации.
Пример для `gradient`:
```ts
interface GradientParams {
from: string;
to: string;
dir: string;
}
// схема: field.color(from), field.color(to), field.select(dir)
// плюс annotation в схеме: { group: "gradient" } → UI рисует как цветовой переход
```
## Инвентарь: какие инструменты мигрировать
### dimension (12)
resize-png*, crop-png*, fit-on-background-png, create-empty-png,
single-color-png, random-noise-png, linear-gradient-png, color-spectrum-png,
random-colors-png, draw-grid-png, placeholder-png, change-canvas-size-png
(\* у resize/crop — не просто dimension, есть специфика: keepAspect, x/y и т.д.)
### color-pair (4)
blend-two-png, step-colors-png, linear-gradient-png, two-colors-png
### offset (4)
circle-mask-png, square-mask-png, star-mask-png, wavy-mask-png
### position9 (3)
add-text-png, date-stamp-png, watermark-image-png
### font-style (3-4)
text-to-png, add-text-png, date-stamp-png
### plate (2-3)
add-text-png, date-stamp-png
## Пошаговый план перевода инструментов
> Каждый шаг — **атомарное, маленькое изменение** (в идеале 1 инструмент =
> 1 diff, укладывается в правило «коммиты < ~500 строк»). После каждого шага —
> ревью; параллельно можно делать UI нового инструмента.
>
> **Принцип малых шагов:** перевод инструмента не меняет поведение старой
> схемы (`params` остаётся), не ломает старый UI. Инструмент получает ДОПОЛНИТЕЛЬНО
> новое поле `schema` + `interface Params`, старое `run`/`params` не трогаем.
> Это гарантирует, что старый UI продолжает работать без регресса.
>
> **Новый подход (решение пользователя):** сначала делаем **один полноценный
> рабочий инструмент в preview** — не эталон «на бумаге», а реально юзабельный,
> чтобы руками оценить, насколько это рабочее решение и какие возникнут сложности.
> Оцениваем, при необходимости правим инфраструктуру, и только потом переводим
> остальные инструменты тем же проверенным паттерном.
### Фаза 0 — фундамент ✔ (сделано)
Typed field builders + `Field ()` + `ToolSchema ` —
в `web/src/lib/registry-schema.ts`. `ToolEntry ` generic + опциональное
поле `schema`. Новые `defaultSchemaParams`/`sanitizeSchemaParams`.
Старый `ParamDef[]`/pipeline не тронуты. Проверено: compile-time-защита
(лишний/неверный/отсутствующий ключ ловит TS), тесты, svelte-check без новых
ошибок.
### Фаза 1 — ОДИН полноценный рабочий инструмент в preview (срез) ✔ (сделано)
Инструмент переехал в `registry-new` и рендерится через `SchemaToolView`
(`SchemaFields`/`SchemaPreview`); применение реально работает end-to-end до
картинки. Паттерн подтверждён. `find-contour-png` — следующий в этой фазе-2.
5. **Инструмент «добавить рамку» (`add-border-png`)** — сделать полностью
рабочим в preview:
- `interface AddBorderParams { thickness: number; color: string }`
- `addBorderSchema = toolSchema `, рисует поля рамки, применение
реально работает (end-to-end до картинки).
- Дефолты/валидация/значения — из схемы (`defaultSchemaParams`,
`sanitizeSchemaParams`).
- **Ревью-оценка:** удобно ли это, что сломалось/усложнилось, что поправить
в инфраструктуре до масштабирования. Принимается макет/решение.
Результат: один реально рабочий инструмент на новой системе = доказываем паттерн,
прежде чем переводить остальных.
### Фаза 2 — простые инструменты без составных типов (1 инструмент = 1 шаг)
Одиночные/небольшие инструменты, где `interface Params` + `schema` не требуют
составных типов. Каждый — отдельный маленький diff (~15-30 строк), тем самым
проверенным в Фазе 1 паттерном. **Переезжают в `registry-new`.**
Переведено: `add-border-png` (Фаза 1), `add-stroke-png`, `find-contour-png`, плюс
весь блок простых инструментов ниже (пункты 8-22). Следующее — Фаза 3.
6. ~~add-stroke-png (color + slider)~~ → переведён в `registry-new` ✔
7. ~~find-contour-png (color + slider)~~ → переведён в `registry-new` ✔
8. ~~convert-png-to-jpg (color + slider)~~ → переведён в `registry-new` ✔
9. ~~convert-png-to-webp (slider)~~ → переведён в `registry-new` ✔
10. ~~remove-color-from-png (color + slider)~~ → переведён в `registry-new` ✔
11. ~~extract-color-from-png (color + slider)~~ → переведён в `registry-new` ✔
12. ~~add-noise-png (slider + select + number)~~ → переведён в `registry-new` ✔
13. ~~randomize-pixels-png (slider + number)~~ → переведён в `registry-new` ✔
14. ~~pixelate-png (slider)~~ → переведён в `registry-new` ✔
15. ~~vignette-png (slider)~~ → переведён в `registry-new` ✔
16. ~~gamma-png (slider)~~ → переведён в `registry-new` ✔
17. ~~temperature-png (slider)~~ → переведён в `registry-new` ✔
18. ~~tint-png (color + slider)~~ → переведён в `registry-new` ✔
19. ~~quantize-png (slider)~~ → переведён в `registry-new` ✔
20. ~~custom-palette-png (text)~~ → переведён в `registry-new` ✔
21. ~~dithering-png (slider + select)~~ → переведён в `registry-new` ✔
22. ~~jpeg-artifacts-png (slider)~~ → переведён в `registry-new` ✔
> **Замечание (конвертеры):** старые `convert-png-to-jpg`/`convert-png-to-webp`
> несли `output`-метаданные (`mime`/`ext`/`qualityParamId`) для выбора формата
> вывода. В новом `ToolEntry ` поля `output` пока нет, поэтому при переводе
> эти метаданные не перенесены — preview пока отдаёт результат как PNG.
> Механика выбора формата/качества в новом UI — отдельный шаг (не блокирует Фазу 2).
### Фаза 3 — инструменты с составными типами (по типу, затем по инструментам)
24. `dimension` — **переведены все 12 инструментов** ✔ (`create-empty-png`,
`single-color-png`, `random-noise-png`, `linear-gradient-png`,
`color-spectrum-png`, `random-colors-png`, `draw-grid-png`,
`placeholder-png` — генераторы в `registry-new/generate.ts`;
`fit-on-background-png`, `change-canvas-size-png`, `resize-png`,
`crop-png` — в `registry-new/geometry.ts`). Составной тип во всех видах:
вложенный объект `size: { width, height }` в Params + `field.dimension`,
виджет `kit/fields/DimensionField.svelte`.
Preview: генераторы применяются через `executeGenerate` (кнопка Generate),
`SchemaFields` рендерит все kinds схемы (slider/number/color/select/
checkbox/dimension). Тесты: 592 passed.
25. `color-pair` — пилот (blend-two-png), затем two-colors → step-colors →
linear-gradient (уже переведён в dimension, здесь добавляется к нему)
26. `offset` — пилот (circle-mask-png), затем square-mask → star-mask →
wavy-mask
27. `position9` — перевести add-text → date-stamp → watermark-image
28. `font-style` — перевести text-to-png → add-text (если ещё не) →
date-stamp
29. `plate` — перевести add-text → date-stamp
30. `gradient` — собрать из dimension + color-pair + direction на
linear-gradient (зависит от решения по gradient, см. план)
### Фаза 4 — масштаб UI на остальные инструменты
31. По мере перевода инструментов в Фазе 2-3 — рендер схемы (из Фазы 1)
покрывает их автоматически; составные виджеты (dimension, color-pair,
offset, position9, font-style, plate, gradient) — по одному, каждый с ревью.
32. Пер-инструмент layout (`schema.layout`): группировка полей рамки и т.п.
33. Для каждого переведённого инструмента — UI-макет, принимается отдельно.
### Фаза 5 — зачистка
34. Поглощение `tool-views.ts` (preview/lede/layout → meta инструмента).
35. После перехода на новый дизайн — удаление старого UI и старого `ParamDef[]`.
### Как ревьюить каждый шаг
- Diff **только один инструмент** (или фундамент без инструментов).
- Старый `params`/`run`/`defaultParams`/`sanitizeParams` — **не изменены** (кроме
добаление `schema`, если это пилот).
- Старый UI и старые тесты продолжают работать — запустить
`pnpm --dir web test` и `pnpm --dir web exec svelte-check`.
- Порядок полей в `interface Params` и в `schema` совпадает; дефолты в сeme
равны старым `default` из `params`.
- Пилот из Фазы 1 проверяется **руками в preview**: применяется рамка,
дефолты/валидация/смена значений работают end-to-end.
## Поток данных (runtime)
### Старый pipeline (не трогаем)
Всё как сейчас: старое `defaultParams`/`sanitizeParams`/executor/chain работают
на `ParamDef[]` и `Record `.
- **defaultParams / sanitizeParams (новые)** — выводятся из `ToolSchema `,
дефолты лежат в схеме. Старые функции не трогаем, добавляем новые рядом.
- **Executor/worker (новые)** — получает `Record `) — сейчас рендерит поля по схеме (number/slider/color).
- `tool-views.ts` — со временем поглощается registry (preview/lede/layout →
meta инструмента). Отдельный шаг, НЕ блокирует типизацию params.
## Оценка трудозатрат
| Часть | Сложность | Время |
| ----------------------------------------------------- | ------------------------ | ---------------------- |
| Typed field builders + toolSchema\ | Средняя | ~2ч |
| Новые defaultParams/sanitizeParams под схему | Средняя | ~2-3ч |
| ToolEntry\ generic + поле schema (рядом с params) | Низкая-Средняя | ~1-2ч |
| Новый executor/worker/pipeline под схему | Средняя | ~2-3ч |
| Миграция инструментов на interface Params + схемы | Механическая, но крупная | ~8-12ч |
| Новый рендер (kit/ParamControl) + составные виджеты | Средняя | ~4-5ч |
| Поглощение tool-views.ts | Средняя | ~1-2ч |
| **Итого** | | **~20-29ч** (поэтапно) |
> Оценка выросла по сравнению с ранней версией плана, потому что принят путь
> «явный interface Params + схема + общий рендер с пер-инструмент layout» —
> это полный рефакторинг pipeline, а не только добавление составных типов.
>
> Старый UI/`ParamDef[]`/старый pipeline в смету **не входят** — они не
> рефакторятся, а продолжают работать до перехода (затем удаляются вместе со
> старым дизайном).
## Порядок реализации (кратко)
Детальный пошаговый план с атомарными изменениями и порядком ревью — в разделе
«Пошаговый план перевода инструментов» выше. Здесь краткая сводка фаз:
1. **Фаза 0** — фундамент: builders + `toolSchema ` + `ToolEntry .schema` +
новые default/sanitize. Ни одного инструмента не изменено. ✔
2. **Фаза 1** — **один полноценный рабочий инструмент в preview**
(«добавить рамку»): schema + рендер + end-to-end применение. Ручная оценка
работоспособности решения/сложностей.
3. **Фаза 2** — простые/одиночные инструменты (проверенный паттерн Фазы 1),
1 инструмент = 1 диф.
4. **Фаза 3** — инструменты с составными типами (dimension → color-pair →
offset → position9 → font-style → plate → gradient), пилот каждого типа
отдельно.
5. **Фаза 4** — масштаб UI на остальные: составные виджеты, пер-инструмент
layout (для каждого инструмента — UI-макет).
6. **Фаза 5** — поглощение `tool-views.ts`, затем удаление старого UI/`ParamDef[]`
## Зависимости
- **UI сперва доделать** — текущая активная работа; типизация params ожидает
готовности нового дизайна.
- Ветка redesign (`plan-redesign.md`) — параллельная, учесть при миграции
kit/`ParamControl.svelte` и `overlay-store`.
- Старый pipeline остаётся рабочим и шippable на всём протяжении перехода
(аналогично стратегии `plan-redesign.md`).