20 KiB
План: исправления по аудиту 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).Инструменты в этом документе названы по адресам страниц (slug). В коде у страницы
slug(registry/pages/), у вызываемого инструмента —idбез png-интента (crop-png→id: "crop",png-to-hsl→to-hsl), модель —docs/architecture.md, раздел 3. Названия изdocs/tools-audit.mdвидаcolor-wheel-generator,webp-to-pngв реестре отсутствуют (реальная страница —color-wheel-png): при переносе пункта аудита в шаг сверять сPAGES.
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 коммит)
FA-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
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/filters.ts—randomizePixels,addNoise: поле seed →{ ...field.number(...), spec: { ...spec, variant: "seed" } }(или пересоздать черезfield.seed())
2. Волна B — select → buttons (по файлам)
Каждый шаг — один коммит, < 500 строк. Меняем field.select() →
field.buttons() в перечисленных ниже схемах.
FA-B1. geometry.ts
| Инструмент | Поле | Опции |
|---|---|---|
| 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).
FA-B2. color.ts
| Инструмент | Поле | Опции |
|---|---|---|
| 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.
FA-B3. analyze.ts
| Инструмент | Поле | Опции |
|---|---|---|
| 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 — правка в одном месте.
FA-B4. filters.ts
| Инструмент | Поле | Опции |
|---|---|---|
| add-noise-png | mode | mono / color |
FA-B5. generate.ts
| Инструмент | Поле | Опции |
|---|---|---|
| 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 — обязательные фиксы (баги + дефолты)
FA-C1. Source-aware defaults для dimension-полей — реализовано
Source-aware defaults реализованы: общий resolver в registry-schema
(applySourceDefaults). Для resize-png и crop-png дефолт после загрузки
равен размерам текущего source; до загрузки используется положительный fallback
1×1. Один-sided 0 у resize сохраняет режим auto. Проверено тестом
registry.test.ts («resize и crop получают размеры текущего source»).
FA-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.
FA-C3. Source-aware bounds (отдельный следующий шаг) — не начат
Source-aware defaults реализованы через SchemaContext и
applySourceDefaults. Динамические min/max для crop x/y, shift и resize не
реализованы и остаются отдельной задачей: bounds не следует смешивать с default
resolver.
Что нужно спроектировать и завести:
-
Где берём размер.
SchemaToolViewуже знает размер послеdecodeFile()→PixelImage(width/height); для bounds понадобится передать этот context вSchemaFieldsи контролы. -
Как схеме описать зависимость. В спеках добавить ссылку на размер источника вместо жёстких чисел. Кандидат:
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 из текущего размера источника; при смене картинки диапазон пересчитывается, текущее значение пере-клампится. -
Первые потребители: crop (x/y → ±sourceWidth/sourceHeight), shift (offsetX/offsetY), resize (max → maxSide).
Это отдельный рефакторинг; source-aware defaults не заменяют bounds.
FA-C4. verify-is-png: неверное название — решение принято, шаг не выполнен
Проверяет текст, не PNG-файл. Варианты:
title→ "Verify PNG data",description→ уточнить;idоставить — он же адрес страницы, переименование ломает ссылку- либо полное переименование
id→verify-is-png-dataс редиректом со старого URL (нужен вордстат, иначе страница выпадает из индекса)
Решение: поменять title и description, id не трогать. Переименование id
затрагивает адрес — с ним связана задача разведения id/slug
(docs/backlog.md), поэтому в одиночку не делается.
4. Волна D — обязательные фиксы (новые параметры)
FA-D1. remove-alpha-channel-png: выбор цвета фона
Сейчас захардкожен #ffffff. Добавить field.color({ default: "#ffffff" }).
Файл: alpha.ts, схема removeAlphaChannelSchema.
FA-D2. extract-alpha-mask-png: галочка «инвертировать»
Добавить field.checkbox({ label: "fields.invertMask", default: false }). В
run(): если invert, вызвать invertAlpha() после extractAlphaMask().
Файл: alpha.ts.
FA-D3. change-canvas-size-png: position9 вместо select
anchor сейчас field.select() с 9 опциями → field.position9().
Файл: geometry.ts. Уже есть PositionControl.
FA-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.
FA-D5. add-border-png: прозрачность цвета
Текущий field.color() не поддерживает alpha. Пока что: оставить как есть
(прозрачность не поддерживается нативным color picker). Отметить в аудите как
blocked.
5. Волна E — полировка (некритично)
FA-E1. Плейсхолдер "нет параметров"
Инструменты с пустой схемой (EmptyParams) выглядят странно. Добавить
EmptyState или подсказку "No parameters — click Run".
Файл: SchemaToolView.svelte — показать подсказку если schema.fields пуст.
FA-E2. quantize-png: пресеты
field.slider с max 64 → добавить пресеты (8, 16, 32, 64) как кнопки под
слайдером. Либо field.buttons() с 4 опциями вместо слайдера.
FA-E3. trim-empty-space-png: 0-254 → проценты
Текущий min: 0, max: 254 — нестандартно. Добавить переключатель px/% (аналог
шага FA-A1 — variant: "alpha-threshold" в NumberSpec).
FA-E4. add-text-png: plate offset
Plate снизу имеет большой отступ — добавить галочку "compact plate" или уменьшить дефолтный padding.
FA-E5. emoji-to-png: выбор эмодзи
Поле text → добавить группу часто используемых эмодзи как кнопки- пресеты над
полем ввода.
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, 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)