# План: исправления по аудиту UX/UI > Статус: **draft** — на ревью. > > Находки — `docs/tools-audit.md` (что плохо, с датой прохода). Этот документ > задаёт **порядок правок** полей и раскладки волнами; сами задачи — в > `docs/backlog.md`. Проверки и гейты — `docs/quality-gates.md`; точки касания > при добавлении вида поля — `docs/architecture.md`, раздел 4. > > **Идентификаторы шагов — `FA-*` (fields-audit).** Буквы `C*`/`Q*` в других > документах относятся к архивным волнам редизайна и здесь не используются. > > **Как читать волны.** Шаг с пометкой «реализовано» — закрытый, его правку > можно не повторять; шаг без пометки — работа. Пометка стоит в первом абзаце > шага, а не в заголовке, чтобы не потерять её при чтении оглавления. Задачи, > которые в бэклоге, здесь не дублируются: этот документ задаёт **порядок**, > backlog — **учёт** (`docs/backlog.md`). > > **В этом документе инструменты названы по `id`** (`registry/tools/`), а не по > адресу страницы: поля и раскладка принадлежат схеме инструмента, а страница > только ссылается на список шагов (`Page.steps`), где сейчас ровно один > инструмент, а в будущем может быть несколько. Поэтому «у страницы `crop-png` > есть поля x/y» неверно — поля есть у инструмента `crop`, а страница лишь > показывает его. `id` не несёт png-интента: страница `crop-png` вызывает > `id: "crop"`, страница `png-to-hsl` — `id: "to-hsl"` (модель — > `docs/architecture.md`, раздел 3). > > **Как переносить пункты аудита.** `docs/tools-audit.md` назван по страницам, > потому что это то, что видит пользователь. При переносе пункта в шаг мапить > slug → `id` через `page.steps[0].id` в `registry/pages/`. Названия аудита вида > `color-wheel-generator` и `webp-to-png` в реестре отсутствуют (реальная > страница — `color-wheel-png`, инструмент — `color-wheel`). ## 0. Ключевое решение: select → buttons Существующий `Segmented.svelte` — компонент с группированными кнопками (общая рамка, разделители между сегментами). Для параметров инструментов он не подходит: при 3+ опциях сегменты сжимаются, текст не читается. **Решение:** новый компонент `ButtonsControl` — раздельные outline-кнопки с отступами. Каждая опция — отдельная ` {/each} ``` ```css .buttons { display: flex; flex-wrap: wrap; gap: var(--space-m); } .btn { padding: var(--space-m) var(--space-l); border: var(--size-border) solid var(--color-border); border-radius: var(--radius-s); font: var(--font-size-s) var(--font-mono); color: var(--color-text-muted); background: transparent; cursor: pointer; } .btn.selected { background: var(--color-main); color: var(--color-background); border-color: var(--color-main); } ``` ### Схема: `field.buttons()` Новый тип в `registry-schema.ts`: ```ts export interface ButtonsSpec extends FieldSpecBase { kind: "buttons"; default: V; options: { value: V; label: string }[]; } ``` Фабрика: ```ts buttons: (s: Omit, "kind">): Field => ({ spec: { kind: "buttons", ...s }, }), ``` Регистрация: добавить `"buttons": {} as ButtonsSpec` в `fieldSpecs`, добавить `ButtonsControl` в `FIELDS` в `SchemaFields.svelte`, добавить дефолт/санитайз в `sanitizeSchemaParams` и `defaultSchemaParams`. --- ## 1. Волна A — инфраструктура (1 коммит) ### FA-A1. Новый тип `buttons` в схеме Файлы: - `web/src/lib/registry-schema.ts` — `ButtonsSpec`, фабрика `field.buttons()`, кейс в `sanitizeSchemaParams`, кейс в `defaultSchemaParams` - `web/src/lib/components/fields/schema/ButtonsControl.svelte` — новый компонент - `web/src/lib/components/SchemaFields.svelte` — импорт + запись в `FIELDS` ### FA-A2. Seed → input + randomize Для `field.number()` с `kind: "seed"` (или новый подтип `field.seed()`) — рендерить поле ввода + кнопку «Random» вместо range slider. Решение: новый вид `field.seed()` (наследует от number, но рендерится иначе). Или проще: опциональное поле `variant?: "seed"` в `NumberSpec` — тогда `RangeControl` покажет input + кнопку. Решение: **`variant: "seed"` в `NumberSpec`** — меньше новых типов. Файлы: - `web/src/lib/registry-schema.ts` — `NumberSpec.variant?: "seed"` - `web/src/lib/components/fields/schema/RangeControl.svelte` — условный рендер - `web/src/lib/registry/tools/filters.ts` — `randomizePixels`, `addNoiseTool`: поле seed → `{ ...field.number(...), spec: { ...spec, variant: "seed" } }` (или пересоздать через `field.seed()`) --- ## 2. Волна B — select → buttons (по файлам) Каждый шаг — один коммит, < 500 строк. Меняем `field.select()` → `field.buttons()` в перечисленных ниже схемах. ### FA-B1. geometry.ts | Инструмент | Поле | Опции | | ------------------- | -------- | ----------------------------------------- | | rotate | angle | 90° / 180° / 270° | | flip | axis | horizontal / vertical | | swap-orientation | target | portrait / landscape | | symmetric-copy | axis | vertical / horizontal | | symmetric-copy | keepSide | left / right / top / bottom | | change-aspect-ratio | ratio | 1:1 / 4:3 / 3:4 / 3:2 / 2:3 / 16:9 / 9:16 | | change-aspect-ratio | mode | crop / pad | **Исключение:** `change-canvas-size` (anchor, 9 опций) — оставить `field.select()`, т.к. 9 кнопок в ряд не поместятся. Аналогично `position9` (уже отдельный компонент `PositionControl`). ### FA-B2. color.ts | Инструмент | Поле | Опции | | -------------------- | --------- | ---------------------------------------------------- | | dithering | pattern | floyd-steinberg / bayer | | extract-channel | channel | red / green / blue | | swap-channels | pair | r-g / r-b / g-b | | decrease-color-count | maxColors | 2 / 4 / 8 / 16 / 32 / 44 / 64 / 96 / 128 / 192 / 256 | **Исключение:** инструменты разложения каналов (`to-hsl`, `to-hsv`, `to-hsi`, `to-cmyk`, `to-ycbcr`, `to-lab`, поле `component`) — 3-6 опций, но это динамические инструменты, оставить `select`. ### FA-B3. analyze.ts | Инструмент | Поле | Опции | | --------------------- | ---- | ------------------ | | show-transparent | mode | binary / highlight | | show-grayscale-pixels | mode | (тот же) | | show-color-pixels | mode | (тот же) | | light-pixel-mask | mode | (тот же) | | dark-pixel-mask | mode | (тот же) | | unique-color-mask | mode | (тот же) | Пять инструментов из шести берут `mode` из общего `maskBaseFields` — правка в одном месте. Исключение — `show-transparent`: он переопределяет `mode` своими опциями и дефолтом `highlight`, поэтому его `mode` правится отдельно. ### FA-B4. filters.ts | Инструмент | Поле | Опции | | ---------- | ---- | ------------ | | add-noise | mode | mono / color | ### FA-B5. generate.ts | Инструмент | Поле | Опции | | -------------- | ------------ | --------------------- | | color-spectrum | direction | horizontal / vertical | | step-colors | layout | grid / strip | | complementary | layout | grid / strip | | triadic | layout | grid / strip | | tetradic | layout | grid / strip | | analogous | layout | grid / strip | | monochromatic | layout | grid / strip | | shades | layout | grid / strip | | sort-colors | layout | grid / strip | | mix-colors | (нет select) | — | --- ## 3. Волна C — обязательные фиксы (баги + дефолты) ### FA-C1. Source-aware defaults для dimension-полей — реализовано Source-aware defaults реализованы: общий resolver в `registry-schema` (`applySourceDefaults`). Для `resize` и `crop` дефолт после загрузки равен размерам текущего source; до загрузки используется положительный fallback `1×1`. Один-sided `0` у resize сохраняет режим auto. Проверено тестом `registry.test.ts` («resize и crop получают размеры текущего source»). ### FA-C2. symmetric-copy: "keep side" не работает Баг в `run()`: `keepSide` не учитывает `axis`. При `axis: "vertical"` (удвоение ширины) работают только `left`/`right`, `top`/`bottom` не имеют смысла. Нужно: либо фильтровать опции `keepSide` в зависимости от `axis` (reactive schema), либо нормализовать в `run()`. **Решение:** reactive — при смене `axis` сбрасывать `keepSide` на допустимое значение. Пока достаточно в `run()`: если `axis === "vertical"` и `keepSide` ∈ {top, bottom} → заменить на `left`. ### FA-C3. Source-aware bounds (отдельный следующий шаг) — не начат Source-aware **defaults** реализованы через `SchemaContext` и `applySourceDefaults`. Динамические min/max для crop x/y, shift и resize не реализованы и остаются отдельной задачей: bounds не следует смешивать с default resolver. **Что нужно спроектировать и завести:** 1. **Где берём размер.** `SchemaToolView` уже знает размер после `decodeFile()` → `PixelImage` (width/height); для bounds понадобится передать этот context в `SchemaFields` и контролы. 2. **Как схеме описать зависимость.** В спеках добавить ссылку на размер источника вместо жёстких чисел. Кандидат: ```ts x: field.slider({ label: "fields.x", bound: "sourceWidth", // min/max = ±sourceWidth default: 0, }), ``` `BoundSpec = "sourceWidth" | "sourceHeight" | "minSide" | "maxSide"`, применимо к `NumberSpec`, `SliderSpec`, `OffsetSpec`, `DimensionSpec`. 3. **Что делает RangeControl.** При `bound` вычисляет min/max из текущего размера источника; при смене картинки диапазон пересчитывается, текущее значение пере-клампится. 4. **Первые потребители:** crop (x/y → ±sourceWidth/sourceHeight), shift (offsetX/offsetY), resize (max → maxSide). Это отдельный рефакторинг; source-aware defaults не заменяют bounds. ### FA-C4. verify-is-png: неверное название — решение принято, шаг не выполнен Находка про **страницу**, а не про инструмент: страница `verify-is-png` обещает проверку PNG-файла, а проверяет текст (base64 / data-uri). Сам инструмент назван честно — `id: "verify-png"`, png-интент в нём означает входные данные. Адрес страницы — `slug` `verify-is-png` (`docs/architecture.md`, раздел 3), поэтому «неверное название» относится к `slug`, а не к `id`. Варианты: - `title` → «Verify PNG data», `description` → уточнить; `slug` и `id` не трогать - либо переименовать `slug` → `verify-png-data` с редиректом со старого URL (нужен вордстат, иначе страница выпадает из индекса) **Решение:** поменять title и description, имя страницы не трогать. Переименование `slug` меняет адрес, имя файла, ключи `pages.*` и иконку, то есть это операция с SEO-последствиями, а не строчка в реестре — она не делается в одиночку, вместе с решением по `docs/plan-seo.md` §5. --- ## 4. Волна D — обязательные фиксы (новые параметры) ### FA-D1. remove-alpha-channel: выбор цвета фона Сейчас захардкожен `#ffffff`. Добавить `field.color({ default: "#ffffff" })`. Файл: `alpha.ts`, схема `removeAlphaChannelSchema`. ### FA-D2. extract-alpha-mask: галочка «инвертировать» Добавить `field.checkbox({ label: "fields.invertMask", default: false })`. В `run()`: если `invert`, вызвать `invertAlpha()` после `extractAlphaMask()`. Файл: `alpha.ts`. ### FA-D3. change-canvas-size: position9 вместо select `anchor` сейчас `field.select()` с 9 опциями → `field.position9()`. Файл: `geometry.ts`. Уже есть `PositionControl`. ### FA-D4. Generate: добавить height Инструменты без `size`/`height`: - blend-two — добавить `height: field.slider({ min: 1, max: 1024, default: 512 })` - step-colors — аналогично - complementary, triadic, tetradic, analogous, monochromatic, shades — все используют `paletteBaseSchema` (общий объект), добавить `height` туда - `sort-colors` — своя схема, `paletteBaseSchema` не использует, `height` добавлять вручную - mix-colors — добавить height Файл: `generate.ts`. ### FA-D5. add-border: прозрачность цвета Текущий `field.color()` не поддерживает alpha. Пока что: оставить как есть (прозрачность не поддерживается нативным color picker). Отметить в аудите как blocked. --- ## 5. Волна E — полировка (некритично) ### FA-E1. Плейсхолдер "нет параметров" Инструменты с пустой схемой (`EmptyParams`) выглядят странно. Добавить `EmptyState` или подсказку "No parameters — click Run". Файл: `SchemaToolView.svelte` — показать подсказку если `schema.fields` пуст. ### FA-E2. quantize: пресеты `field.slider` с max 64 → добавить пресеты (8, 16, 32, 64) как кнопки под слайдером. Либо `field.buttons()` с 4 опциями вместо слайдера. ### FA-E3. trim-empty-space: 0-254 → проценты Текущий `min: 0, max: 254` — нестандартно. Добавить переключатель px/% (аналог шага FA-A1 — `variant: "alpha-threshold"` в NumberSpec). ### FA-E4. add-text: plate offset Plate снизу имеет большой отступ — добавить галочку "compact plate" или уменьшить дефолтный padding. ### FA-E5. from-emoji: выбор эмодзи Поле `emoji` → добавить группу часто используемых эмодзи как кнопки-пресеты над полем ввода. --- ## 6. Верификация Проверки и их минимальный набор — `docs/quality-gates.md`, раздел «Быстрые подмножества»; правила e2e — `docs/testing-strategy.md`. Для волн с изменением пользовательского потока (новый контрол, новая раскладка) обязателен ручной смоук в браузере: часть находок — про воспринимаемость, которую не ловит ни один гейт. `pnpm verify` — перед ревью волны. --- ## 7. Не покрыто / требует решения Эти пункты из аудита не попали в волны A–E. Требуют либо дополнительного исследования, либо нормативного решения, либо отложены. ### 7.1 Требуют normative решения - **Pixel ↔ Percent** (circle-mask, square-mask, star-mask, wavy-mask — %→px, round-corners — px→%): нужен компонент переключателя единиц в слайдере. Требует правки `RangeControl` + всех схем — в волны A–E не входит, нужен отдельный этап. - **Smoothing checkbox** для масок (circle-mask, square-mask, star-mask, wavy-mask, round-corners, invert-alpha): нужен `field.checkbox("smoothing")` + правка core (shapes.ts, alpha). Отложен — требует визуального тестирования. - **Vignette**: выбор центра + цвет + feather — 3 новых параметра. Требует normative решения по UI. - **Remove-background**: выбор точки удаления — либо параметр, либо отдельный инструмент. - **Метод масштабирования в `resize`**: поля в схеме нет (только `size` и `keepAspect`), поэтому это уже не правка селекта, а новое поле — нужен ли выбор алгоритма (bilinear/bicubic/nearest)? - **Placeholders**: "нет параметров" — какой текст/компонент показывать. ### 7.2 Операционные (тест-кейсы, описания) - find-contour: расширять на толщину линии (требует правки core) - make-thicker: расширять картинку на толщину линии - make-thinner: уменьшать от краев, fix выступа - feather-edges: расширять картинку на толщину линии - clean-edges: найти тест-кейс, возможно увеличить максимум - despeckle-alpha: найти более заметный тест-кейс - close-holes: проверить поведение на больших полупрозрачных областях - auto-contrast: найти тестовый кейс - sharpen: добавить тест-кейс - center-by-alpha: найти тест-кейс - is-grayscale: улучшить видимость результата ### 7.3 Дизайн-вопросы - add-padding vs add-border: разница неочевидна, нужен review - change-aspect-ratio: не видно что меняется — рамка результата - watermark-tile: заполняет не полностью - sepia: нужна ли настройка силы эффекта? - posterize: настройка для каждого канала отдельно? - pixelate: разные алгоритмы? - swap-channels: обдумать flow через более простые инструменты ### 7.4 Аудит полей (компоненты) - Range slider: поле ввода + кнопки +/- + кнопка сброса - Color picker: пипетка с картинки, прозрачность - Width/height с keep-aspect-ratio: единый компонент - Position offset: отдельные числовые поля X/Y (уже сделано в `fields/schema/OffsetControl.svelte`)