35 KiB
План: переезд на новый дизайн (refs) — параллельная сборка
Статус: план к выполнению. Шаги 5–6 (C17–C21) остаются следующим этапом. Они больше не привязаны к «верности против рефа»:
docs/archive/plan-design-fix.mdзакрыт, аудит против рефа прекращён. Tech debt закрыт (docs/archive/plan-tech-debt.md). Перед C17: backlog №14 (переезд старых файлов вold/) и покупка домена (plan-domain.local.md).Источники:
refs/— Next/React-референс (правда по пикселям и интерактиву),refs-html/— статические HTML-снимки для быстрого просмотра в браузере.Стратегия изменена. Вместо in-place рестайла (старый план A–F, правка общих компонентов на месте с алиасами токенов) — параллельная сборка: новый дизайн строится отдельно, изолируется от старого, проверяется целиком, затем становится основным и старый удаляется. Старое приложение остаётся рабочим и шippable на всём протяжении перехода.
0. Почему меняем стратегию
- Старый план (A–F): правим
ui/*и страницы на месте, держим старые токены как алиасы, чтобы ничего не сломалось до этапа F. Риск: общие компоненты редактируются вживую → регресс старого UI во время работы, новый дизайн нельзя принять изолированно. - Новый подход (5 фаз): (1) строим новый ui-kit, (2) строим новые страницы рядом, не трогая старые, (3) проверяем новый дизайн, (4) делаем его основным, (5) удаляем старый. Ниже фазы 1–5 — это требуемые шаги.
- Главный технический риск и его решение. Сейчас
web/src/app.cssимпортируется в корневомweb/src/routes/+layout.svelte:2и потому грузится на ВСЕХ маршрутах, включая новые. Решение — не префиксовать новый CSS (никакого.ds2), а разнести глобальные стили по layout'ам без наследования: (а) убратьimport '../app.css'из корневого layout; (б) старые маршруты обернуть группой(old)/с собственным layout, импортирующимapp.css; (в) новые маршруты (preview/*) — собственный layout, импортирующийdesign2.css. SvelteKit делает code-splitting CSS по маршрутам, а стили компонентов скоупятся сами — поэтому глобальные стили каждого дизайна живут только в своей ветке, и префикс-обёртка.ds2не нужна. Старыеui/и страницы не трогаются до фазы 5.
1. Источники и их оценка
refs-html/ — витрина разметки
Покрытие достаточное:
| Файл | Что показывает | Оценка |
|---|---|---|
index.html |
Главная: pipeline-воркспейс + превью | ✅ |
demo.html |
Зеркало index.html (идентичны) |
⚠️ дубликат |
gradient.html |
Инструмент «Градиент»: настройки + превью | ✅ (тёмная тема) |
background-remover.html |
Инструмент «Удаление фона»: source/result | ✅ (тёмная тема) |
list-tools.html |
Каталог инструментов | ✅ |
- Статика без JS — интерактив (тумблеры/слайдеры/перетаскивание) брать из
кода
refs/, не из HTML. HTML — только визуальная правда по разметке/классам. - По 4 файлам собран инвентарь классов — это и есть спецификация kit'а:
app-shell,page-grid,eyebrow,brand-mark,topbar,footer,panel-heading,setting-group,settings-footer,settings-panel,segmented,status,step-card(step-index/step-heading/step-type/step-body/drag),color-field(swatch/native-color),preview-toolbar/checker(tile-canvas/remover-canvas/comparison-grid),result-meta/meta-row,tool-card/image-card(tool-icon/tool-index/tool-copy),code-block,icon-btn,download-btn,reset-btn,lede,auto-note,label,toggle/toggle-group,direction-grid,gradient-bar. - Дыры в refs-html: нет состояний empty/error/loading, нет глобальных модалок/тултипов, i18n не представлен (все лейблы — английские брендовые). Решения — см. §9.
2. Токены (фундамент нового дизайна)
Живут в src/lib/styles/design2.css как обычный :root { … } (плюс
[data-theme='dark'] { … }). Файл грузится только layout'ом новой ветки
(§5), поэтому глобально не конфликтует со старым app.css. Маппинг старых
токенов web/src/app.css → новые:
| Старый | Новый | Light | Dark |
|---|---|---|---|
--bg |
--background |
#eef1f4 |
#11171d |
--surface |
--panel |
#f8fafb |
#182129 |
--text |
--foreground |
#17212b |
#e8eef2 |
--text-muted |
--muted |
#6d7883 |
#91a0ac |
--border |
--line |
#cbd3da |
#33414c |
--accent |
--blue |
#1769d2 |
#54a2ff |
| — | --cyan |
#00a8c7 |
#00a8c7 |
| — | --amber |
#bd7411 |
#bd7411 |
| — | --success |
#25a96a |
#25a96a (статус-точки) |
--danger |
без изменений | #e5484d |
#ff7479 |
Прочее:
- Радиусы: один
--radius: 4px(мягкость старой темы уходит). - Шрифты:
IBM Plex Sans+IBM Plex Mono, self-host через@fontsource(offline-first, CDN нельзя). Mono — рабочий шрифт для лейблов/метаданных/ цифр; Sans — заголовки и текст. Задаются на.app-shell(корень новой ветки), чтобы не зависеть от старых глобальных правилbody. - Фон-сетка (32px blueprint-grid) и шахматка канвасов — утилиты
.page-gridи.checker-canvasвнутри.ds2, не токены. - Тёмная тема — на
[data-theme='dark'](инлайн-скрипт уже есть вapp.html); класс.dark-modeиз рефа не переносим, используем наш механизм. На переходный период никаких алиасов старых токенов (старые страницы сами живут на своёмapp.css).
3. Базовые примитивы дизайн-системы (все — компоненты)
Правило фазы 1: любой повторяющийся паттерн — компонент, даже «просто div с
двумя стилями». Никакой копипасты классов между страницами. Каждый примитив ниже
— отдельный .svelte в src/lib/components/kit/.
MonoLabel— микро-лейбл: mono 10px, uppercase, letter-spacing .1em, muted; слот-модификатор accent (синие eyebrow/version).StatusDot/StatusLine— точка--success+ подпись («AUTO PIPELINE», «LIVE PREVIEW»).Panel/PanelHeading/SettingGroup/SettingsFooter— панель с 1px-бордером, составные части с разделителями.CheckerCanvas— рабочая поверхность превью: тёмная подложка + шахматка.MetaRow/MetaList— пары «CAPTION значение».Segmented— сегмент-переключатель (язык, тип градиента, пресеты).Badge— тег типа шага (BACKGROUND/TRANSFORM/STYLE), чип «✓ AUTO».StepCard— нумерованная карточка этапа: индекс01, drag-handle, Badge типа, заголовок, кнопка удаления; слот для параметров.ToolCard— карточка каталога (icon, index, title, copy, arrow).ImageCard/PreviewTile— label сверху + canvas-превью.AppShell— корневой контейнер.app-shell+.page-grid(обёртка новой ветки маршрутов); задаёт шрифты и фон нового дизайна.BlueprintGrid— фоновая сетка (используется вAppShell).IconButton,Icon(обёрткаlucide-svelte, 16–17px, muted).
4. Минимальный UI-kit (src/lib/components/kit/)
Контролы, поверх примитивов §3. API проектируем сразу под новый дизайн (обратной
совместимости со старым ui/ не нужно — старые страницы его продолжают
использовать).
| Компонент | Назначение |
|---|---|
Button.svelte |
primary (solid blue) / secondary (outline) / ghost + icon |
Field.svelte |
обёртка: MonoLabel + слот значения/контрола |
SliderField.svelte |
range + <output>, опц. строка подсказок |
ColorField.svelte |
swatch + hex-input + нативный picker |
TextField.svelte |
текстовый ввод в стиле дизайна |
SelectField.svelte |
селект в стиле дизайна |
CheckboxField.svelte |
чекбокс → switch-toggle 32×18 |
Toggle.svelte |
переключатель (для toggle-group) |
SegmentedControl.svelte |
на базе Segmented — RU/EN, Linear/Radial, пресеты |
Badge.svelte |
на базе Badge |
MetaList.svelte |
на базе MetaRow |
EmptyState.svelte |
пустое состояние под новый стиль |
CodeBlock.svelte |
блок кода (gradient-art / direction-grid) |
DownloadButton.svelte |
кнопка скачивания (можно переиспользовать идею старой) |
Иконки: lucide-svelte (те же пути, MIT), 16–17px, stroke, muted.
5. Изоляция старого и нового (критично)
Без префиксов и обёрток — только за счёт того, какие layout грузят какой CSS.
- Корневой layout
src/routes/+layout.svelteперестаёт импортировать../app.css. В нём остаётся только общая логика (init темы/locale) и нейтральный<div>{children}</div>без глобальных стилей дизайна. - Старая ветка
src/routes/(old)/+layout.svelteимпортируетapp.cssи содержит текущую шапку/подвал (разметку из нынешнего корневого layout). Все старые страницы переносятся внутрь(old)/(группа в скобках не меняет URL):(old)/+page.svelte,(old)/demo/...,(old)/list-tools/...,(old)/tools/[id]/.... - Новая ветка
src/routes/preview/+layout.svelteимпортируетdesign2.cssи рендерит<AppShell>. Сюда же — новые страницы (§6). - Благодаря code-splitting CSS в SvelteKit: на старых маршрутах грузится только
app.css, наpreview/*— толькоdesign2.css. Скоуп стилей Svelte-компонентов добавляет второй уровень изоляции. Никаких.ds2. - Переключатель для ручной проверки: временный флаг
previewDesignвlocalStorage+ кнопка в старой шапке («Preview v2»), ведущая на/preview. На фазе 4 флаг убираем.
Почему не
(new)-группа с теми же путями вместоpreview/*: две группы не могут одновременно определять один и тот же URL ((old)/+page.svelteи(new)/+page.svelteоба →/= конфликт маршрутов). Поэтому новая ветка живёт под префиксомpreview/, а на фазе 4 файлы переносятся на реальные пути и группа(old)удаляется.Это совпадает с рекомендованным в документации SvelteKit паттерном: «put your entire app inside one or more groups except the routes that should not inherit the common layouts» — старое целиком в
(old), новое снаружи. Синтаксис@-breakout (+layout@.svelte,+page@.svelte) здесь не требуется: «выломаться» можно лишь до корня, а нам нужно, чтобы именно у корня НЕ былоapp.css; если позже захотим держать новые маршруты внутри группы,@пригодится для сброса родительского layout.
6. Маршрутизация параллельных страниц
Новые страницы живут в отдельной ветке preview/*, не трогая старые:
src/routes/+layout.svelte # нейтральный, БЕЗ app.css
src/routes/(old)/+layout.svelte # импорт app.css + старая шапка/подвал
src/routes/(old)/+page.svelte # / (старое)
src/routes/(old)/demo/+page.svelte # /demo
src/routes/(old)/list-tools/...
src/routes/(old)/tools/[id]/...
src/routes/preview/+layout.svelte # импорт design2.css, <AppShell>
src/routes/preview/+page.svelte # /preview — воркспейс (index/demo)
src/routes/preview/demo/+page.svelte # /preview/demo
src/routes/preview/list-tools/+page.svelte
src/routes/preview/tools/[id]/+page.svelte
- Каждая новая страница импортирует только
kit/*. Старые страницы не изменяются (только перемещены в(old)/). - Фаза 4: содержимое
preview/*переносится на реальные маршруты (move-файлы), группа(old)/удаляется,design2.cssподключается в корневом+layout.svelte(как новый основной слой), временный флаг/кнопка убираются.
7. Фазы выполнения (1–5 по запросу)
Каждая фаза заканчивается зелёными test/check/build и ревью в браузере
(обе темы) против refs-html/.
-
Фаза 1 — Фундамент + UI-kit. Токены §2,
design2.cssпо §5, все примитивы §3 и контролы §4 как отдельные компоненты. Критерий: демо-страница kit'а (/previewили временная) показывает все состояния (light/dark, hover/disabled, все варианты Button/Segmented/Badge); unit-тесты форм не падают; старые страницы визуально не изменились (изоляция работает). -
Фаза 2 — Новые страницы. Под
preview/*(§6): воркспейс, demo, list-tools, tool-page (двухпанельная раскладка из рефа: settings-panel + preview-panel, step-cards для chain). Критерий: разметка соответствуетrefs-html/index.html,gradient.html,background-remover.html,list-tools.html; интерактив (слайдеры/тоглы/загрузка/автозапуск) работает в новом стиле; старые маршруты продолжают работать как раньше. -
Фазa 3 — Проверка нового дизайна. Смоук §8 (обе темы, узкий экран, 800px), скриншоты против
refs-html/, прогон старого тест-сьюта для доказательства отсутствия регресса, RU/EN-локализация новых лейблов. Критерий: визуальное совпадение с рефом, зелёные тесты, записанные скриншоты приняты. -
Фаза 4 — Сделать новый дизайн основным. Перенос
preview/*→ реальные маршруты;design2.cssподключается в корневом+layout.svelte; удаление preview-группы и временного флага/кнопки. Старые страницы теперь не используются (мёртвый код), но физически ещё в репо. Критерий: реальные маршруты отдают новый дизайн, тесты зелёные, билд без предупреждений. -
Фаза 5 — Удалить старый дизайн. Удаление старых страниц, старого
ui/, старогоapp.css(и его токенов), неиспользуемых классов; переименованиеkit/→ui/(финальное имя) либо оставлениеkit/как основного — решается на фазе 1. Финальный проход по брейкпоинтам 1200/1100/800/480. Критерий:grepпо старым токенам/классам пуст, build чистый, старый дизайн нигде не упоминается.
Оценка объёма: Фаза 1 — фундамент+kit (~1 день), Фаза 2 — страницы (~1–2 дня), Фаза 3 — проверка (~полдня), Фаза 4–5 — переключение и чистка (~полдня).
8. Смоук (после фазы 3, контроль на фазе 4)
- Светлая и тёмная тема: сетка фона, панели, шахматка канвасов читаются, контраст mono-лейблов достаточный.
- Инструмент с параметрами (градиент): slider/color/toggle в стиле setting-groups, автозапуск не регрессировал.
- Удаление фона: сравнение source/result, маска, пипетка работают на новой шахматке.
- Chain из 3+ инструментов: step-cards рендерятся, удаление/сброс работают.
- Узкий экран 800px: настройки над превью, sticky выключается как в рефе.
- RU/EN: новые лейблы локализованы; mono-лейблы рефа — либо ключи i18n, либо осознанно английские брендовые элементы (см. §9).
9. Открытые вопросы
- Имя финального каталога компонентов: оставить
kit/как основное имя или переименовать вui/на фазе 5 (удалив староеui/). Рекомендую сразу писать вkit/, на фазе 5 переименовать вui/. Решить на фазе 1. - Изоляция через layout:
app.cssвынесен из корневого layout в(old)/+layout.svelte; новыйdesign2.css— только вpreview/+layout. Проверить на фазе 1, что на/preview/*не попадают стили старого дизайна (DevTools → вкладка Styles, нет правил изapp.css). - Переключатель для проверки: рекомендую
localStorage-флаг + кнопка «Preview v2» в старой шапке + группаpreview/*. Убрать на фазе 4. - Mono-лейблы рефа на английском (
PROCESSING PIPELINE): завести i18n- ключи, но допустить часть английских брендовых («приборная панель»). - Версия в шапке/футере: из
package.jsonчерезdefineили захардкодить — мелочь, решить при сборке topbar. refs/иrefs-html/— одноразовый источник, НЕ поддерживаемый. После внедрения нового дизайна все правки дизайна делаются сразу в продакшен-коде (design2.css+kit/), а не вrefs/. Механизм синхронизации «ref → код» не нужен и не создаётся.refs/иrefs-html/можно удалить из репо на фазе 5 (по желанию), они не являются частью рантайма.
10. План исполнения (мелкие коммиты)
Ветка feat/redesign. Каждый шаг — отдельный коммит, цель < ~500 строк. Перед
стартом: test/build зелёные на базе (якорь регрессии).
Шаг 0. Изоляция layout (без дизайна)
- [C1] Сделать корневой
routes/+layout.svelteнейтральным: убратьimport '../app.css', оставить только init темы/locale и<div>{children}</div>. - [C2] Создать
routes/(old)/+layout.svelte(импортapp.css+ старая шапка/подвал) и перенести старые страницы (+page,demo,list-tools,tools/[id]) в(old)/. URL не меняются. Проверка: старый сайт идентичен. (файлы — move, объём строк минимален)
Шаг 1. Фундамент нового дизайна
- [C3] Создать
src/lib/styles/design2.css(токены:root+[data-theme='dark']по §2) и подключить@fontsource/ibm-plex-sans+@fontsource/ibm-plex-mono(self-host, offline-first). подключить шрифты вdesign2.css(self-host, offline-first). - [C4] (слит в C3: шрифты нельзя подключить до создания
design2.css)[data-theme='dark'](таблица §2). Без утилитарных классов. - [C5] Примитивы batch 1:
AppShell,BlueprintGrid,CheckerCanvas,MonoLabel,StatusDot/StatusLine. - [C6] Примитивы batch 2:
Panel,PanelHeading,SettingGroup,SettingsFooter,MetaRow/MetaList,Segmented,Badge,StepCard,ToolCard,ImageCard/PreviewTile. - [C7] Контролы:
Icon,IconButton,Button,Field,SliderField,ColorField,TextField,SelectField,CheckboxField,Toggle,SegmentedControl(=Segmentedиз C6),EmptyState,CodeBlock,DownloadButton.
Шаг 2. Витрина kit (проверка компонентов)
- [C8]
preview/+layout.svelte(импортdesign2.css+<AppShell>+ переключатель темы) и временнаяpreview/kitстраница со всеми состояниями (light/dark, hover/disabled, варианты Button/Segmented/Badge и пр.)
Шаг 3. Новые страницы
- [C9]
/preview— воркспейс (eyebrow, h1, lede, поиск, «открыть последний»). Поиск фильтруетTOOLSиз$lib/registry; карточки результата пока ведут на#(реальные/preview/tools/[id]подключим на C12, иначе prerender падает на 404). - [C10]
/preview/list-tools— каталог карточек (группировка поCATEGORIES,ToolCard). Ссылки пока#(реальные/preview/tools/[id]подключим на C12). - [C11]
/preview/demo— зеркало воркспейса (4 шага + панель превью), статичный приёмочный стенд. - [C12]
/preview/tools/[id]— data-driven settings-panel + preview-panel (gradient live, остальные — плейсхолдер);NumberField,entries()для prerender, ToolCard-ссылки подключены. - [C13]
/preview/tools/[id]— comparison-grid для file-source инструментов (Dropzone+ before/afterCheckerCanvas,result-meta); охватывает background-remover и прочие. - [C14]
/preview/tools/[id]— исполняемая цепочка:StepCard-шаги (параметры inline) + «Add step» (chainable), живой прогон пайплайна на исходнике, превью каждого шага + итог + Download.
Шаг 4. Переключатель и проверка
- [C15] В старой шапке ссылка «Preview v2» →
/preview, ставитlocalStorage['easy-png-tools:preview-v2']='1'. - [C16] База под дизайн: рабочий переключатель тем (persist в
localStorage, применяется на
<html>; проверено — фон#eef1f4↔#11171d),TopBarприведён к видуdemo.html(бренд + синий хлебный крош/* страница */, безрамочные иконки, язык — active =--foreground/--background), страницы/preview/*на всю ширину (убранmax-width). Точечное выравнивание к рефу отложено: реф-дизайн неконсистентен. (доказательство отсутствия регресса). Фиксы по результатам — отдельными мелкими коммитами.
Шаг 5. Сделать новый дизайн основным — СЛЕДУЮЩИЙ ЭТАП
Tech debt закрыт. Перед стартом C17: покупка домена (
plan-domain.local.md). Переезд старых файлов вlib/old/(backlog №14) — выполнено.
- [C17] Перенести
preview/*→ реальные маршруты (move), удалить(old)/, корневой+layout.svelteимпортируетdesign2.css+ новый shell. Убрать флаг/кнопку. И обновитьnewCodeвweb/eslint.config.js: заменитьsrc/routes/preview/**наsrc/routes/**(после переезда все маршруты — новый дизайн), компонентный globsrc/lib/components/kit/**пока оставить. И снятьnoindex, nofollowсо страницы инструмента (preview/tools/[id]/+page.svelte) — страницы становятся боевыми лендингами ровно в момент переезда (SEO:plan-seo.md, S1a). - [C18] Правки импортов/редиректов после переноса, проверка билда.
Шаг 6. Удаление старого
- [C19] Удалить старое
ui/, прочие старые компоненты, старыйapp.css, неиспользуемые классы. Переименоватьkit/→ui/(если решено на C1 — тогда вeslint.config.jsобновить и компонентный glob). И раскрыть ESLintrecommendedна весь код — убрать scoped-блокnewCodeвweb/eslint.config.js(см. AGENTS.md), прогнатьlintи починить всплывшие ошибки в оставшемся коде. Примечание: доменные модули (lib/core,lib/registry, тесты и т.п.) — это не «старый дизайн», у них свой линт-долг; расширениеrecommendedна них может выдать много ошибок. Решить на C19: либо чиним сразу, либо расширяем scoped только наsrc/routes/**+src/lib/components/**. - [C20] Финальный проход по брейкпоинтам 1200/1100/800/480; build без
предупреждений;
grepпо старым токенам/классам пуст. - [C21] (опционально) Удалить
refs/иrefs-html/из репо — они больше не источник правды.
Если какой-то коммит тянет за собой > 500 строк (например, C6/C7), разбивать на под-коммиты по 2–4 компонента. Тесты/билд прогонять после каждого коммита, чтобы регрессия локализовалась одним шагом назад.
Атомарность и точки «поломки» между коммитами.
- C1+C2 уже сделаны вместе (связаны: нейтральный root без
(old)-layout ломает старый сайт). Аналогично связанных пар больше нет.- C3 и C4 слиты: шрифты подключаются внутри
design2.css, поэтому файл должен существовать в том же коммите.- Коммиты C3→C16 не трогают старый сайт
(old)/— они только добавляют новые файлы/маршруты, сломать старый сайт на них нельзя.- C17 — единственный атомарный переключатель: перенос
preview/*на реальные пути + удаление(old)/+ смена root-layout должны быть в ОДНОМ коммите (иначе конфликт маршрутов или пропажа страниц). Не дробить.- C19 должен идти строго после C17 (старый
app.css/ui/удаляем только когда old-сайт уже выключен).- Очерёдность зависимостей:
design2.css(C3) и kit-компоненты (C5–C7) должны существовать до страниц, которые их импортируют (C8–C14).
Бэклог полировки (после C9–C21, отдельным проходом)
Конкретные расхождения витрины preview/kit с refs-html/. Править системно в
конце, а не по ходу сборки базы.
- Toggle в дизайне прямоугольный, у нас — закруглённый. Сделать прямоугольным (или уточнить радиус у свича отдельно от общих токенов).
- Радиусы бордеров не совпадают: в
refsрадиусы вообще 0, а мы взяли ненулевой--radiusдля всех бордеров. Проверить токен--radiusпротив референсов; возможно он должен быть 0 (или близко), а скругления оставить только там, где они реально есть в дизайне. - DownloadButton: нет отступа между текстом и размером файла (
size-чип). Добавитьgap/отступ внутри кнопки. - Поля ввода (controls): выглядят без паддингов (возможно артефакт самой
витрины, но визуально плохо). Проверить паддинги в
TextField/SelectField/ColorField/SliderFieldпротив референса и поправить. - Segmented: на витрине последняя кнопка растянута и нечитаемо, что это
сегмент. Проверить, как
Segmentedвыглядит в реальных раскладках (C9–C14); возможно нужен фиксированный/авто размер сегментов или перенос.