diff --git a/docs/plan-composite-params.md b/docs/plan-composite-params.md new file mode 100644 index 0000000..b3b06cf --- /dev/null +++ b/docs/plan-composite-params.md @@ -0,0 +1,378 @@ +# План: составные типы параметров и полная типизация pipeline + +> Статус: **обсуждение завершено, решения приняты. Реализация пока НЕ начата** +> (ждут готовности UI). Док отражает итоговую целевую архитектуру. +> +> Файлы: `web/src/lib/registry.ts` (3918 строк), `web/src/lib/preview/tool-views.ts` (193 строки). + +## Ключевая стратегия: параллельная сборка, старый UI не трогаем + +Старый UI (группа `(old)/`, `ParamForm.svelte` + старый pipeline) **продолжает +работать как сейчас, без рефакторинга**. Новый `ToolSchema
` строится +рядом и служит источником для **нового UI**. Инструменты переписываются под +новый registry по мере нужды — для нового UI. + +Это та же логика, что в `plan-redesign.md` применена к дизайну: параллельная +сборка, изоляция от старого, затем новый становится основным и старый +удаляется вместе со старым дизайном. + +**Разделение схем: две независимые схемы.** + +| Схема | Источник | Использование | +| --------------------- | ------------------------------------------------- | ---------------------------------------------------------------- | +| Старый `ParamDef[]` | В `ToolEntry.params` — **остаётся без изменений** | Старый UI, старый pipeline (`ParamForm.svelte`, executor, chain) | +| Новый `ToolSchema
` | Новое поле `ToolEntry.schema` — добавляется рядом | Новый UI (kit/`ParamControl`, новый pipeline) |
+
+- Старая и новая схемы **не выводятся друг из друга** — это два независимых
+ описания инструмента.
+- Пока новый UI не готов — `schema` просто отсутствует у большинства
+ инструментов, старый UI ничего не замечает.
+- Удаление старого UI (после перехода) попутно удаляет и старый `ParamDef[]`
+
+## Проблема (текущая)
+
+- `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 продолжает работать без регресса.
+
+### Фаза 0 — фундамент (без инструментов, ещё не мигрируем)
+
+1. **Typed field builders** (`field.slider`, `field.color`, …) + тип `Field ()`** + тип `ToolSchema ` — проверка соответствия схемы
+ полям Params. Diff: добавление функции в тот же файл. Без изменений registry.
+3. **`ToolEntry ` generic + поле `schema`** (рядом с `params`). Diff: правка
+ типа `ToolEntry` в registry.ts (~10 строк). Компилируется, поведения не
+ меняет — `schema` опционален.
+4. **Новые `defaultParams`/`sanitizeParams`** для схемы (старые не трогаем).
+ Diff: добавление функций рядом. Никто ещё не вызывает — просто присутствуют.
+
+Результат фазы 0: инфраструктура готова, registry работает как раньше, ни один
+инструмент не изменён. Старый UI/тесты не затронуты.
+
+### Фаза 1 — эталонный инструмент (вертикальный срез)
+
+5. **Инструмент «добавить рамку» (`add-border-png`)** — первый пилот:
+ - добавить `interface AddBorderParams { thickness: number; color: string }`
+ - добавить `const addBorderSchema = toolSchema `; собрать макет
+ на пилоте add-border-png из Фазы 1.
+32. Составные виджеты (dimension, color-pair, offset, position9, font-style,
+ plate, gradient) — по одному, каждый с ревью.
+33. Пер-инструмент layout (`schema.layout`): группировка полей рамки и т.п.
+34. Для каждого переведённого инструмента — UI-макет, принимается отдельно.
+
+### Фаза 5 — зачистка
+
+35. Поглощение `tool-views.ts` (preview/lede/layout → meta инструмента).
+36. После перехода на новый дизайн — удаление старого 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`.
+- После принятия макета — можно делать UI инструмента, не блокируя остальных.
+
+## Поток данных (runtime)
+
+### Старый pipeline (не трогаем)
+
+Всё как сейчас: старое `defaultParams`/`sanitizeParams`/executor/chain работают
+на `ParamDef[]` и `Record `.
+- **defaultParams / sanitizeParams (новые)** — выводятся из `ToolSchema `,
+ дефолты лежат в схеме. Старые функции не трогаем, добавляем новые рядом.
+- **Executor/worker (новые)** — получает `Record `; поддержка составных типов и пер-инструмент layout
+ (`schema.layout`).
+- `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** — эталонный пилот «добавить рамку» (вертикальный срез), ревью.
+3. **Фаза 2** — простые/одиночные инструменты, 1 инструмент = 1 диф.
+4. **Фаза 3** — инструменты с составными типами (dimension → color-pair →
+ offset → position9 → font-style → plate → gradient), пилот каждого типа
+ отдельно.
+5. **Фаза 4** — связка с новым UI: рендер `ToolSchema `, составные виджеты,
+ пер-инструмент 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`).
diff --git a/web/src/lib/components/kit/ParamControl.svelte b/web/src/lib/components/kit/ParamControl.svelte
index e005659..9e5062b 100644
--- a/web/src/lib/components/kit/ParamControl.svelte
+++ b/web/src/lib/components/kit/ParamControl.svelte
@@ -16,6 +16,8 @@
}
let { param, value, onInput }: Props = $props();
+
+ // TODO: investigate - may be better move param fields switch to field component?
{#if param.type === "color"}