22 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).В этом документе инструменты названы по
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-кнопки с
отступами. Каждая опция — отдельная <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/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.
Что нужно спроектировать и завести:
-
Где берём размер.
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: неверное название — решение принято, шаг не выполнен
Находка про страницу, а не про инструмент: страница 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)