18 KiB
План: исправления по аудиту UX/UI
Статус: draft — на ревью.
Источник:
docs/tools-audit.md. Этот документ разбивает аудит на конкретные шаги, группирует по типу работы и фиксирует решения.
0. Ключевое решение: select → buttons
Существующий Segmented.svelte — компонент с группированными кнопками (общая
рамка, разделители между сегментами). Для параметров инструментов он не
подходит: при 3+ опциях сегменты сжимаются, текст не читается.
Решение: новый компонент ButtonsControl — раздельные outline-кнопки с
отступами. Каждая опция — отдельная <button> со своей рамкой, активная
подсвечивается заливкой (--color-main). Не зависит от Segmented.
Компонент ButtonsControl
<!-- web/src/lib/components/fields/schema/ButtonsControl.svelte -->
<div class="buttons" role="group">
{#each options as opt}
<button
class="btn"
class:selected={value === opt.value}
aria-pressed={value === opt.value}
onclick={() => select(opt.value)}
>{opt.label}</button>
{/each}
</div>
.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:
export interface ButtonsSpec<V extends string = string> extends FieldSpecBase {
kind: "buttons";
default: V;
options: { value: V; label: string }[];
}
Фабрика:
buttons: <V extends string>(s: Omit<ButtonsSpec<V>, "kind">): Field<V> => ({
spec: { kind: "buttons", ...s },
}),
Регистрация: добавить "buttons": {} as ButtonsSpec в fieldSpecs, добавить
ButtonsControl в FIELDS в SchemaFields.svelte, добавить дефолт/санитайз в
sanitizeSchemaParams и defaultSchemaParams.
1. Волна A — инфраструктура (1 коммит)
Шаг A1. Новый тип buttons в схеме
Файлы:
web/src/lib/registry-schema.ts—ButtonsSpec, фабрикаfield.buttons(), кейс вsanitizeSchemaParams, кейс вdefaultSchemaParamsweb/src/lib/components/fields/schema/ButtonsControl.svelte— новый компонентweb/src/lib/components/SchemaFields.svelte— импорт + запись вFIELDS
Шаг 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/filters.ts—randomizePixels,addNoise: поле seed →{ ...field.number(...), spec: { ...spec, variant: "seed" } }(или пересоздать черезfield.seed())
2. Волна B — select → buttons (по файлам)
Каждый шаг — один коммит, < 500 строк. Меняем field.select() →
field.buttons() в перечисленных ниже схемах.
B1. geometry.ts (8 инструментов)
| Инструмент | Поле | Опции |
|---|---|---|
| rotate-png | angle | 90° / 180° / 270° |
| flip-png | axis | horizontal / vertical |
| swap-orientation-png | target | portrait / landscape |
| symmetric-copy-png | axis | vertical / horizontal |
| symmetric-copy-png | keepSide | left / right / top / bottom |
| change-aspect-ratio-png | ratio | 1:1 / 4:3 / 3:4 / 3:2 / 2:3 / 16:9 / 9:16 |
| change-aspect-ratio-png | mode | crop / pad |
Исключение: change-canvas-size-png (anchor, 9 опций) — оставить
field.select(), т.к. 9 кнопок в ряд не поместятся. Аналогично position9 (уже
отдельный компонент PositionControl).
B2. color.ts (4 инструмента)
| Инструмент | Поле | Опции |
|---|---|---|
| dithering-png | pattern | floyd-steinberg / bayer |
| extract-channel-png | channel | red / green / blue |
| swap-channels-png | pair | r-g / r-b / g-b |
| decrease-color-count-png | maxColors | 2 / 4 / 8 / 16 / 32 / 64 / 128 / 256 |
Исключение: channel spaces (hsl/hsv/... component) — 3-6 опций, но это
динамические инструменты, оставить select.
B3. analyze.ts (5 инструментов)
| Инструмент | Поле | Опции |
|---|---|---|
| show-transparent-png | mode | binary / highlight |
| show-grayscale-pixels-png | mode | (тот же) |
| show-color-pixels-png | mode | (тот же) |
| light-pixel-mask-png | mode | (тот же) |
| dark-pixel-mask-png | mode | (тот же) |
Все используют общий maskBaseFields — правка в одном месте.
B4. filters.ts (1 инструмент)
| Инструмент | Поле | Опции |
|---|---|---|
| add-noise-png | mode | mono / color |
B5. generate.ts (12 инструментов)
| Инструмент | Поле | Опции |
|---|---|---|
| color-spectrum-png | direction | horizontal / vertical |
| step-colors-png | layout | grid / strip |
| complementary-png | layout | grid / strip |
| triadic-png | layout | grid / strip |
| tetradic-png | layout | grid / strip |
| analogous-png | layout | grid / strip |
| monochromatic-png | layout | grid / strip |
| shades-png | layout | grid / strip |
| sort-colors-png | layout | grid / strip |
| mix-colors-png | (нет select) | — |
3. Волна C — обязательные фиксы (баги + дефолты)
C1. resize-png: дефолт 0×0 → осмысленный
geometry.ts: width: 0, height: 0 → width: 512, height: 512 (или
width: imgWidth, height: imgHeight — но дефолт в схеме не может знать размер
изображения). Решение: дефолт 512×512.
C2. symmetric-copy-png: "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.
C3. Механизм проброса размеров картинки в схему
Проблема. Поля, значения которых зависят от размеров исходника (crop x/y,
resize, offsets), объявлены в статической схеме (-100000…100000), а реальная
картинка обычно 800–4000px. Диапазон надо считать от фактического размера
изображения, а не от потолков.
Что нужно спроектировать и завести:
-
Где берём размер.
SchemaToolViewуже знает размер послеdecodeFile()→PixelImage(width/height). Сейчас он никуда не пробрасывается — надо прокинуть вSchemaFieldsи дальше в контролы, например через context или пропsourceSize. -
Как схеме описать зависимость. В спеках добавить ссылку на размер источника вместо жёстких чисел. Кандидат:
x: field.slider({ label: "fields.x", bound: "sourceWidth", // min/max = ±sourceWidth default: 0, }),BoundSpec = "sourceWidth" | "sourceHeight" | "minSide" | "maxSide", применимо кNumberSpec,SliderSpec,OffsetSpec,DimensionSpec. -
Что делает RangeControl. При
boundвычисляет min/max из текущего размера источника; при смене картинки диапазон пересчитывается, текущее значение пере-клампится. Дефолт иsanitizeостаются на статической основе (или тоже учат bound) — решить при проектировании. -
Первые потребители: crop (x/y → ±sourceWidth/sourceHeight), shift (offsetX/offsetY), resize (max → maxSide).
Это отдельный шаг плана: завести механизм, спроектировать на ревью интерфейс
спека (bound vs range: (size) => [min, max]), затем применить к crop и
остальным полям размерности. Пока механизма нет — поля crop остаются как есть.
C4. verify-is-png: неверное название
Проверяет текст, не PNG-файл. Переименовать:
id→verify-is-png-data(или оставить для обратной совместимости)title→ "Verify PNG data"description→ уточнить
Решение: поменять title и description, id не трогать (url-dependent).
4. Волна D — обязательные фиксы (новые параметры)
D1. remove-alpha-channel-png: выбор цвета фона
Сейчас захардкожен #ffffff. Добавить field.color({ default: "#ffffff" }).
Файл: alpha.ts, схема removeAlphaChannelSchema.
D2. extract-alpha-mask-png: галочка «инвертировать»
Добавить field.checkbox({ label: "fields.invertMask", default: false }). В
run(): если invert, вызвать invertAlpha() после extractAlphaMask().
Файл: alpha.ts.
D3. change-canvas-size-png: position9 вместо select
anchor сейчас field.select() с 9 опциями → field.position9().
Файл: geometry.ts. Уже есть PositionControl.
D4. Generators: добавить height
Инструменты без size/height:
- blend-two-png — добавить
height: field.slider({ min: 1, max: 1024, default: 512 }) - step-colors-png — аналогично
- complementary, triadic, tetradic, analogous, monochromatic, shades,
sort-colors — все используют
paletteBaseSchema(общий объект), добавитьheightтуда - mix-colors-png — добавить height
Файл: generate.ts.
D5. add-border-png: прозрачность цвета
Текущий field.color() не поддерживает alpha. Пока что: оставить как есть
(прозрачность не поддерживается нативным color picker). Отметить в аудите как
blocked.
5. Волна E — полировка (некритично)
E1. Плейсхолдер "нет параметров"
Инструменты с пустой схемой (EmptyParams) выглядят странно. Добавить
EmptyState или подсказку "No parameters — click Run".
Файл: SchemaToolView.svelte — показать подсказку если schema.fields пуст.
E2. quantize-png: пресеты
field.slider с max 64 → добавить пресеты (8, 16, 32, 64) как кнопки под
слайдером. Либо field.buttons() с 4 опциями вместо слайдера.
E3. trim-empty-space-png: 0-254 → проценты
Текущий min: 0, max: 254 — нестандартно. Добавить переключатель px/% (аналог
шага A1 — variant: "alpha-threshold" в NumberSpec).
E4. add-text-png: plate offset
Plate снизу имеет большой отступ — добавить галочку "compact plate" или уменьшить дефолтный padding.
E5. emoji-to-png: выбор эмодзи
Поле text → добавить группу часто используемых эмодзи как кнопки- пресеты над
полем ввода.
6. Верификация
После каждой волны:
pnpm --dir web build— без ошибокpnpm --dir web exec svelte-check --tsconfig ./tsconfig.json— без ошибокpnpm --dir web lint— без ошибок (кроме ожидаемых долгов)- Ручная проверка в браузере: параметры рендерятся, инструменты работают
7. Не покрыто / требует решения
Эти пункты из аудита не попали в волны A–E. Требуют либо дополнительного исследования, либо нормативного решения, либо отложены.
7.1 Требуют normative решения
- Pixel ↔ Percent (circle-mask, square-mask, star-mask, wavy-mask — %→px,
round-corners — px→%): нужен компонент переключателя единиц в слайдере.
Отложен до волны E, т.к. требует правки
RangeControl+ всех схем. - Smoothing checkbox для масок (circle, square, star, wavy, round-corners,
invert-alpha): нужен
field.checkbox("smoothing")+ правка core (shapes.ts, alpha). Отложен — требует визуального тестирования. - Vignette: выбор центра + цвет + feather — 3 новых параметра. Требует normative решения по UI.
- Remove-background: выбор точки удаления — либо параметр, либо отдельный инструмент.
- Precision/quality: select в resize (bilinear/bicubic/nearest) — нужен ли выбор алгоритма?
- Placeholders: "нет параметров" — какой текст/компонент показывать.
7.2 Операционные (тест-кейсы, описания)
- find-contour-png: расширять на толщину линии (требует правки core)
- make-thicker-png: расширять картинку на толщину линии
- make-thinner-png: уменьшать от краев, fix выступа
- feather-edges-png: расширять картинку на толщину линии
- clean-edges-png: найти тест-кейс, возможно увеличить максимум
- despeckle-alpha-png: найти более заметный тест-кейс
- close-holes-png: проверить поведение на больших полупрозрачных областях
- auto-contrast-png: найти тестовый кейс
- sharpen-png: добавить тест-кейс
- center-by-alpha-png: найти тест-кейс
- png-is-grayscale: улучшить видимость результата
7.3 Дизайн-вопросы
- add-padding-png vs add-border-png: разница неочевидна, нужен review
- change-aspect-ratio-png: не видно что меняется — рамка результата
- watermark-tile-png: заполняет не полностью
- sepia-png: нужна ли настройка силы эффекта?
- posterize-png: настройка для каждого канала отдельно?
- pixelate-png: разные алгоритмы?
- swap-channels-png: обдумать flow через более простые инструменты
7.4 Аудит полей (компоненты)
- Range slider: поле ввода + кнопки +/- + кнопка сброса
- Color picker: пипетка с картинки, прозрачность
- Width/height с keep-aspect-ratio: единый компонент
- Position offset: отдельные слайдеры x/y (уже сделано в
OffsetControl)