mirror of
https://github.com/Ku6epXBOCTuK/easy-png-tools.git
synced 2026-11-13 17:08:20 +00:00
docs: update plans, backlog, move todo\fixme from code comments
This commit is contained in:
@@ -0,0 +1,647 @@
|
||||
# План: линтеры, тесты и качество кодовой базы
|
||||
|
||||
> Статус: **archived** Архивирован: 2026-09-26 Удалить после: 2026-10-26
|
||||
> Причина: все этапы Q0–Q8 выполнены или закрыты решениями; рабочие части
|
||||
> перенесены в `docs/quality-gates.md`, `docs/testing-strategy.md`,
|
||||
> `docs/agent-workflow.md`, `AGENTS.md`, `web/eslint-plugins/README.md` и
|
||||
> `docs/backlog.md`. Использование: только историческая справка
|
||||
>
|
||||
> Выполнены Q0–Q5, Q6a–Q6f.5, Q7a–Q7e и Q8.1–Q8.3; открытых шагов не осталось.
|
||||
>
|
||||
> Источник: `docs/backlog.md`, аудит конфигурации `web/`, e2e-тестов и
|
||||
> инфраструктурных скриптов. Q0–Q5, Q6a–Q6f.5, Q7a–Q7e и Q8.1–Q8.3 выполнены.
|
||||
> Решения, важные за пределами плана, живут в `docs/quality-gates.md`,
|
||||
> `docs/testing-strategy.md` и `AGENTS.md`.
|
||||
>
|
||||
> Цель: сделать качество кодовой базы воспроизводимым, убрать ложную «зелёность»
|
||||
> проверок, стабилизировать тестовые контракты и только после этого расширять
|
||||
> автоматические правила.
|
||||
|
||||
## 1. Границы и принципы
|
||||
|
||||
1. Сначала stabilизировать существующие проверки и тестовые контракты, а не
|
||||
добавлять новые сложные lint-правила.
|
||||
2. Старый UI удалён: production работает на корневых маршрутах, а `/v1/**` и
|
||||
v1-specific код больше не являются частью приложения.
|
||||
3. Не добавлять новые v1-фичи, не чинить v1-баги и не расширять v1-тесты.
|
||||
4. `core/` и `theme/` — общие модули. Их изменения проверяются unit-тестами и
|
||||
smoke-сценариями production-потребителей.
|
||||
5. Изменения делаются небольшими атомарными частями, в соответствии с
|
||||
`AGENTS.md`.
|
||||
6. Каждый новый lint-rule сначала появляется в report-only/warn-only режиме,
|
||||
получает fixtures и тесты, и только после очистки baseline становится
|
||||
blocking.
|
||||
7. Детали реализации правил остаются в `web/eslint-plugins/README.md`;
|
||||
`AGENTS.md` содержит краткие обязательные политики, понятные агенту без
|
||||
дополнительных поисков, и ссылки на подробные документы.
|
||||
|
||||
### Политика удаления legacy v1
|
||||
|
||||
- Q3 — отдельная атомарная cleanup-задача; поддержка v1 не возвращается.
|
||||
- Старые маршруты и UI-файлы удаляются без redirects и без совместимого
|
||||
maintenance-режима.
|
||||
- В рабочей кодовой базе не оставлять UI-v1-пути, исключения или документацию,
|
||||
которые выглядят как действующий источник совместимости.
|
||||
- Исторические упоминания допустимы только в `docs/archive/`.
|
||||
- Будущий API-контракт `POST /v1/process` из `docs/plan-platform.md` не связан с
|
||||
удалённым UI-v1 и остаётся в планах.
|
||||
|
||||
## 2. Текущее состояние quality-контура
|
||||
|
||||
- `pnpm verify` объединяет typecheck, unit, rule-тесты, lint, форматирование,
|
||||
docs и production build; deploy выполняет его перед сборкой.
|
||||
- `i18n/dict-consistency` даёт 0 warnings на текущем baseline; после Q7 вычищены
|
||||
4 неиспользуемых design token, token audit тоже чист.
|
||||
- E2E-контракт использует semantic locators и общий source-aware schema
|
||||
resolver; активных known-issue `test.fixme` нет.
|
||||
- `ToolError` хранится как key/vars и переводится только при выводе; смена
|
||||
локали обновляет уже видимый alert.
|
||||
- E2E гоняется в четырёх Playwright-проектах: полный Chromium, Firefox и WebKit
|
||||
для browser-critical спецификаций и mobile Chromium 390×844. Job остаётся
|
||||
non-blocking, число воркеров ограничено из-за памяти.
|
||||
|
||||
## 3. Этапы выполнения
|
||||
|
||||
### Q0. Зафиксировать baseline и синхронизировать документацию
|
||||
|
||||
**Статус:** готов к ревью. **Оценка:** S. **Зависимости:** нет.
|
||||
|
||||
- Проверить фактические команды и зафиксировать их в quality-gate документации.
|
||||
- Вынести расширенный workflow агента в `docs/agent-workflow.md`, оставив в
|
||||
`AGENTS.md` только обязательные инварианты и ссылки.
|
||||
- Обновить `docs/backlog.md`:
|
||||
- отметить dictionary linter как реализованный, но не доведённый до
|
||||
zero-warning;
|
||||
- убрать противоречие по кнопке Generate;
|
||||
- синхронизировать числа инструментов и тестовых сценариев.
|
||||
- Восстановить или исправить ссылку на раздел G в
|
||||
`docs/checklist-manual-testing.md`.
|
||||
- Убрать устаревшее утверждение про hardcoded debt в `kit` из `AGENTS.md`, если
|
||||
оно больше не соответствует коду.
|
||||
- Перенести явно устаревшие `docs/analysis.md` и `docs/sorting-backend.md` в
|
||||
`docs/archive/`, добавить даты архивирования и удаления через 30 дней.
|
||||
- Создать `docs/README.md` как индекс активных документов и карту источников
|
||||
истины; подробные правила не дублировать в корневых файлах.
|
||||
|
||||
**Готово, когда:** один документ описывает текущие команды, актуальный baseline
|
||||
и все известные `test.fixme` имеют ссылки на зарегистрированный баг.
|
||||
|
||||
### Q1. Единый quality gate и CI
|
||||
|
||||
**Статус:** готов к ревью. **Проверено:** `pnpm verify` проходит; E2E оставлен
|
||||
отдельным non-blocking job. **Оценка:** S–M. **Зависимости:** Q0.
|
||||
|
||||
Добавить read-only `verify`-команду, которая запускает:
|
||||
|
||||
```text
|
||||
pnpm --dir web check
|
||||
pnpm --dir web test
|
||||
pnpm --dir web test:rules
|
||||
pnpm --dir web lint:all
|
||||
pnpm --dir web exec prettier --check .
|
||||
pnpm check:docs
|
||||
pnpm --dir web build
|
||||
```
|
||||
|
||||
E2E оставить отдельным тяжёлым гейтом:
|
||||
|
||||
```text
|
||||
pnpm --dir web test:e2e
|
||||
```
|
||||
|
||||
- Не дублировать вручную аргументы ESLint/Stylelint в нескольких скриптах;
|
||||
оркестратор должен вызывать package scripts или единый общий модуль.
|
||||
- Описать фактические команды и semantics ошибок/warnings в
|
||||
`docs/quality-gates.md`; здесь нужна точная процедура, а не длинный дубль в
|
||||
`AGENTS.md`.
|
||||
- На первом этапе явно разделить `errors` и `warnings`.
|
||||
- После очистки i18n baseline добавить строгий режим `--max-warnings=0`.
|
||||
- Добавить отдельный workflow для pull request: typecheck, unit, rule-тесты,
|
||||
lint, format и build.
|
||||
- E2E сначала запускать отдельным job; сделать его blocking после закрытия
|
||||
известных ошибок и стабилизации baseline. Условия перехода и текущая политика
|
||||
описаны в `docs/quality-gates.md`, раздел «Политика e2e в CI».
|
||||
|
||||
**Готово, когда:** одна команда воспроизводит все быстрые проверки, а deploy не
|
||||
может пройти при type/unit/lint/build-ошибке.
|
||||
|
||||
### Q2. Политика i18n и zero-warning baseline
|
||||
|
||||
**Статус:** готов к ревью. **Проверено:** i18n lint даёт 0 warnings, rule-тесты
|
||||
и полный `pnpm verify` проходят. **Оценка:** M. **Зависимости:** Q0, Q1.
|
||||
|
||||
- `BASE_LOCALE` — единственный источник registry-English fallback для
|
||||
`tools.*.title` и `tools.*.description`; остальные локали требуют физические
|
||||
ключи.
|
||||
- Временно неиспользуемые `tools.*.params` исключены pattern-based policy до
|
||||
отдельной очистки мёртвого словаря; пустые значения и placeholders не
|
||||
подавляются.
|
||||
- Исправлены три реальных RU option-label gaps и удалены неиспользуемые
|
||||
option-группы из `ru.ts`.
|
||||
- Исправлен tie-break placeholder parity: при равенстве голосов выбирается
|
||||
`BASE_LOCALE`.
|
||||
- `lint-all` запускает ESLint с `--max-warnings=0`; unused-token сообщение
|
||||
оставалось неблокирующим и закрыто в Q7.
|
||||
- Plural-формы остаются отдельной задачей после Q2 и не смешиваются с этим
|
||||
этапом.
|
||||
|
||||
### Q3. Удалить UI-v1 (cleanup завершён)
|
||||
|
||||
**Статус:** выполнен. **Проверено:** `pnpm verify` и production E2E smoke
|
||||
проходят; после проверки обновлены конфиги и активные документы. **Оценка:** M.
|
||||
**Зависимости:** Q0, Q1, Q2.
|
||||
|
||||
- Удалены `web/src/routes/v1/**`, `web/src/lib/v1/**`, `app_v1.css`, v1-specific
|
||||
i18n/tests и fixtures.
|
||||
- Удалены isolation plugin, его тест и все v1-исключения из lint-конфигураций.
|
||||
- Убраны v1-ссылки из production-кода и активных документов; исторические
|
||||
упоминания остаются в `docs/archive/`.
|
||||
- Проверены production build, verify и отдельный E2E smoke production-маршрутов.
|
||||
|
||||
**Результат:** `/v1/**`, UI-v1-файлы и legacy-конфигурация отсутствуют;
|
||||
следующий этап — Q5.
|
||||
|
||||
### Q4. Тестовый контракт e2e
|
||||
|
||||
**Статус:** выполнен. **Проверено:** `pnpm verify` и полный Playwright smoke
|
||||
проходят; 105 тестов passed, 4 known-issue теста skipped. **Оценка:** M.
|
||||
**Зависимости:** Q0–Q3.
|
||||
|
||||
- Добавлен `docs/testing-strategy.md` с locator, fixture, i18n и `test.fixme`
|
||||
policy.
|
||||
- В production markup добавлены только разрешённые hooks для source/result/
|
||||
verdict/empty-state; текстовые элементы получили accessible labels, фильтры —
|
||||
`aria-pressed`, а каталог — семантические heading/status landmarks.
|
||||
- E2E переведён на `role`, label и stable `href`; удалены `TOTAL`/`GROUPS`,
|
||||
exact fuzzy counts, internal CSS selectors, `alt`-locators и полные verdict
|
||||
strings. Точные verdict-переводы перенесены в unit-тесты.
|
||||
- Helpers централизованы в `web/e2e/helpers/page.ts`; исправлены stale
|
||||
theme/source/locale checks. Theme fixme снят.
|
||||
- Q5-фиксы resize/crop, generator, Reset и error-сценария не смешивались с этим
|
||||
этапом.
|
||||
|
||||
**Результат:** изменение формулировки, класса или порядка элементов не ломает
|
||||
поведенческий тест без изменения пользовательского контракта; следующий этап —
|
||||
Q6.
|
||||
|
||||
### Q5. Закрыть известные preview-баги
|
||||
|
||||
**Статус:** выполнен. **Проверено:** unit, Q5 E2E smoke и полный `pnpm verify`
|
||||
проходят; активных known-issue `test.fixme` нет. **Оценка:** M–L.
|
||||
**Зависимости:** Q4.
|
||||
|
||||
- Добавлен общий opt-in `defaultFromSource` resolver в `registry-schema`: он
|
||||
принимает source dimensions, обновляет только source-aware поля при upload и
|
||||
используется в UI, executor и worker. `resize-png` и `crop-png` получают
|
||||
размеры текущего source; static/generator/text schemas не затронуты.
|
||||
- Reset сохраняет source/text, восстанавливает source-aware defaults и запускает
|
||||
deferred auto-run через debounce.
|
||||
- Генераторы проверены в auto-run без кнопки Generate; кнопка не добавляется.
|
||||
- `ToolError` хранится как key/vars, переводится derived-значением при выводе и
|
||||
обновляется при смене локали. E2E использует независимый invalid pixel-count
|
||||
сценарий.
|
||||
- Resize/crop, generator, Reset и localization fixme сняты; Q6a расширяет
|
||||
покрытие, не смешиваясь с исправлением этих контрактов.
|
||||
|
||||
**Результат:** Q5 known issues закрыты и отражены в `docs/backlog.md` и
|
||||
`docs/checklist-manual-testing.md`; Q6a выполнен, следующий этап — Q6b.
|
||||
|
||||
### Q6. Расширение unit и browser-покрытия
|
||||
|
||||
**Оценка:** M–L. **Зависимости:** Q4, Q5.
|
||||
|
||||
#### Q6a. Executor protocol, fallback и debounce
|
||||
|
||||
**Статус:** выполнен. Unit-тесты покрывают debounce, worker image/string/
|
||||
verdict/files protocol, `ToolError` transport, constructor/postMessage/onerror/
|
||||
malformed fallback, source-aware sanitization и DOM metadata. Browser smoke
|
||||
проверяет rapid auto-run и no-worker fallback; deferred transport fallback
|
||||
покрыт unit-тестами.
|
||||
|
||||
- `execute(tool, ctx)` сохраняет публичный API; `createExecutor` и protocol
|
||||
handler являются внутренними test seams.
|
||||
- `ToolError` не запускает direct retry повторно; transport/protocol failures
|
||||
отключают worker и переходят в direct fallback.
|
||||
- DOM-зависимые registry entries помечены `domOnly`, поэтому зелёный smoke не
|
||||
скрывает нормальный worker fallback.
|
||||
- `debounce` покрыт fake timers; E2E подтверждает последние значения при быстрых
|
||||
последовательных изменениях генератора.
|
||||
|
||||
#### Q6b. FileResult → ZIP → download
|
||||
|
||||
**Статус:** выполнен. Q6a закрыл worker-транспорт; Q6b добавил проверяемый ZIP
|
||||
seam, unit round-trip для `FileResult`, защиту пустого результата и error/busy
|
||||
контракт download. Отдельный browser-сценарий скачивает архив и проверяет имена
|
||||
частей и PNG-сигнатуры.
|
||||
|
||||
- `buildZipEntries` не зависит от DOM; `downloadZip` остаётся UI-обёрткой.
|
||||
- `SchemaToolView` переиспользует `downloadBlob`, ловит ошибки кодирования и
|
||||
блокирует повторный download на время сборки.
|
||||
- E2E использует `downloadResultFile` и `unzipSync`, не добавляя новые
|
||||
`data-testid`; files-инструменты не проходят через image-smoke.
|
||||
|
||||
#### Q6c. Output MIME/quality
|
||||
|
||||
**Статус:** выполнен. Q6c покрывает текущий фиксированный output-контракт
|
||||
JPG/WebP/BMP, не превращаясь в UX-райз кнопки Download. Registry проверяет
|
||||
согласованность MIME/extension/quality-поля, unit — границы quality, browser —
|
||||
реальные сигнатуры файлов и разницу размера при изменении quality.
|
||||
|
||||
- `ToolEntry.output` остаётся декларативным описанием формата; новый селектор
|
||||
форматов не добавляется.
|
||||
- `validateOutputQuality` отделяет проверку 0..1 от DOM-рендеринга; JPEG/WebP
|
||||
используют quality `1..100` из schema, BMP не имеет quality-параметра.
|
||||
- E2E проверяет JPEG `FF D8 FF`, WebP `RIFF/WEBP`, BMP `BM` и PNG-фолбэк.
|
||||
- Ошибки кодирования и unsupported MIME используют существующие i18n-ключи.
|
||||
|
||||
#### Q6d. Malformed и special PNG fixtures
|
||||
|
||||
**Статус:** выполнен. Runtime-fixtures покрывают CRC, truncated, palette и
|
||||
16-bit без бинарных файлов в git. E2E фиксирует браузерный контракт: critical
|
||||
CRC/truncated отклоняются без падения, ancillary CRC и хвост после `IEND`
|
||||
допускаются, palette/16-bit декодируются. Unit фиксирует контракт анализаторов
|
||||
для RGBA-данных, которые браузер отдаёт после нормализации.
|
||||
|
||||
- `web/e2e/helpers/fixtures.ts` собирает indexed, 16-bit, CRC-mutation,
|
||||
truncation и tail-варианты из уже существующих `crc32`/`chunk`.
|
||||
- Отклонение битых файлов — свойство декодера движка, а не единый продуктовый
|
||||
контракт: Q6f.3 зафиксировал, что Firefox и WebKit декодируют `idat-cut` и
|
||||
`truncated-no-iend`, а WebKit ещё и `crc-bad-idat`. Это ожидаемое поведение,
|
||||
которое закреплено per-engine в `web/e2e/png-fixtures.spec.ts`.
|
||||
- Обязательное требование при любом исходе: файл не роняет страницу, ошибка
|
||||
показывается alert-ом, а не «пустым» результатом.
|
||||
- Поведение `createImageBitmap` является частью browser-контракта, а не
|
||||
самостоятельного PNG-парсера product-кода.
|
||||
- Сырое сообщение браузера при decode-ошибке не локализуется в Q6d; это
|
||||
отдельная задача в backlog.
|
||||
|
||||
#### Q6e. Registry-wide smoke
|
||||
|
||||
**Статус:** выполнен. Smoke расширен до всех 122 production tools из registry:
|
||||
image→image, image→text, text→image, generator input→image и verdict. FileResult
|
||||
и malformed/special PNG не дублируются — они уже покрыты Q6b/Q6d.
|
||||
|
||||
- `tools-smoke.spec.ts` расширен image/text-output кейсами, включая все
|
||||
незакрытые color/geometry инструменты.
|
||||
- `generators.spec.ts` использует единый data-harness для всех генераторов;
|
||||
`single-color-png` сохраняет отдельную проверку auto-run/размеров.
|
||||
- Text-input сценарии используют общий `renderText` helper, включая
|
||||
`data-uri-to-png`.
|
||||
- Component-тесты не добавлены: Vitest работает в node без DOM/Testing Library,
|
||||
а текущий e2e-контракт уже покрывает поведение preview. Выбор test seam или
|
||||
DOM-окружения перенесён в Q6f.
|
||||
|
||||
#### Q6f.1. Layout resolver seam
|
||||
|
||||
**Статус:** выполнен. Чистая логика группировки полей вынесена из
|
||||
`SchemaFields.svelte` в `resolveLayoutGroups` и покрыта unit-тестами на
|
||||
дедупликацию, неизвестные поля, пустые группы, `cols` и default-хвост.
|
||||
|
||||
- Компонент использует общий resolver, поэтому unit-тесты блокируют изменения
|
||||
layout-логики без DOM/Testing Library.
|
||||
- `SchemaPreview` view-model и DOM-компоненты не менялись; их seam остаётся
|
||||
отдельным решением Q6f.2.
|
||||
- Новые зависимости, vitest-конфиг и browser matrix не добавлялись.
|
||||
|
||||
#### Q6f.2. SchemaPreview model seam
|
||||
|
||||
**Статус:** выполнен. Вычисления `sourceValue`, `resultValue`, `formatValue` и
|
||||
`hasResult` вынесены в `buildSchemaPreviewModel`; Svelte-компонент использует
|
||||
модель, а unit-тесты проверяют все input/result-ветки без DOM.
|
||||
|
||||
- Translator передаётся в модель явно, поэтому unit-тесты не зависят от locale
|
||||
state и проверяют только view-model.
|
||||
- image/text/verdict/files и пустые результаты покрыты в `pnpm verify`.
|
||||
- Новые зависимости, vitest-конфиг и browser matrix не добавлялись.
|
||||
|
||||
#### Q6f.3. Browser matrix и mobile viewport
|
||||
|
||||
**Статус:** выполнен. Playwright запускает четыре проекта: полный Chromium,
|
||||
Firefox и WebKit для browser-critical спецификаций и mobile Chromium 390×844 с
|
||||
`hasTouch` для навигации, каталога, генераторов и pipeline.
|
||||
|
||||
- `web/playwright.config.ts`: `browserContractTests` — `pipeline.spec.ts`,
|
||||
`png-fixtures.spec.ts`, `text-and-verdicts.spec.ts`; `mobileTests` —
|
||||
`navigation.spec.ts`, `catalog.spec.ts`, `generators.spec.ts`,
|
||||
`pipeline.spec.ts`.
|
||||
- `workers` — 1 локально и 2 в CI. Browser matrix ограничена памятью: при 12
|
||||
воркерах на машине с 16 GB RAM браузеры не успевают поднять worker, и тесты
|
||||
падают без ошибок (проверено и в Chromium). Локально matrix поэтому медленная,
|
||||
но почти не нагружает компьютер. Per-project лимита воркеров в Playwright нет.
|
||||
- Per-project `expect.timeout` не задаётся: Firefox и WebKit отдают результат
|
||||
так же быстро, а расхождения объясняются памятью, а не скоростью движка.
|
||||
- Декодеры движков расходятся, и это зафиксировано как ожидаемое поведение:
|
||||
`png-fixtures.spec.ts` хранит список движков, отклоняющих каждый битый файл, и
|
||||
не пропускает проверки. В любом браузере файл не должен ронять страницу, а
|
||||
отказ декодера показывается alert-ом.
|
||||
- CI устанавливает `chromium firefox webkit`; job остаётся non-blocking.
|
||||
- Локальный прогон: 276 passed, 0 skipped, 0 failed. Visual snapshots и coverage
|
||||
thresholds не добавлялись — это Q6f.4.
|
||||
|
||||
#### Q6f.4. Coverage thresholds
|
||||
|
||||
**Статус:** выполнен для чистой логики. Vitest считает покрытие только по
|
||||
`src/lib`, с явным списком исключений, и `pnpm verify` падает при падении ниже
|
||||
порога.
|
||||
|
||||
- `web/vitest.config.ts`: provider `v8`, `reporter: text-summary`, пороги
|
||||
statements 90, branches 82, functions 72, lines 91.
|
||||
- Пороги поставлены на 1.5–2.5 п.п. ниже измеренного baseline (91.77 / 83.3 /
|
||||
74.31 / 92.53), поэтому gate ловит регрессию, а не текущий долг.
|
||||
- Scope — логика, которую node-Vitest вообще может исполнить. Исключены
|
||||
браузерные модули `core/domText.ts` и `core/io.ts` (canvas,
|
||||
`createImageBitmap`), data-модули `i18n/en.ts`, `i18n/ru.ts`, `tool-icons.ts`,
|
||||
rune-файлы `*.svelte.ts`, entry worker-а и type-only `components/define.ts`.
|
||||
Причина каждого исключения зафиксирована в конфиге и в
|
||||
`docs/testing-strategy.md`.
|
||||
- `registry/*` (47–81% statements, 25–47% functions) в scope остаётся: это
|
||||
обычная непокрытая логика, а не ограничение окружения. Покрывается e2e-smoke
|
||||
Q6e, но unit-порог её не скрывает.
|
||||
- `verify` теперь выполняет `test:coverage` вместо `test`: набор тестов тот же,
|
||||
двойного прогона нет.
|
||||
- Порог functions самый слабый (74.31% против 91.77%): добавление инструмента с
|
||||
непокрытым `run` быстрее всего опускает именно его. Это ожидаемо, порог держит
|
||||
запас.
|
||||
- DOM-тесты `SchemaToolView`/controls сознательно не начаты: node-Vitest не
|
||||
имеет DOM, а e2e уже покрывает поведение. Задача живёт в `docs/backlog.md`.
|
||||
|
||||
Не применять один одинаковый smoke-сценарий к image-, text-, generator- и
|
||||
file-result-инструментам.
|
||||
|
||||
#### Q6f.5. Visual snapshots
|
||||
|
||||
**Статус:** решено не делать. Обоснование, чем закрыта потребность сейчас и при
|
||||
каком условии стоит вернуться — в `docs/testing-strategy.md`, раздел 8.
|
||||
|
||||
### Q7. Укрепление lint-инфраструктуры
|
||||
|
||||
**Оценка:** M. **Зависимости:** Q1, Q2, Q3.
|
||||
|
||||
#### Q7a. Очистка неиспользуемых design tokens
|
||||
|
||||
**Статус:** выполнен. Token audit чист: `All app.css tokens are used somewhere`.
|
||||
|
||||
- Удалены `--color-text-accent-muted` (light и dark, иначе ломается parity),
|
||||
`--space-text-s: 0`, `--size-content-max: 880px` и `--duration-m: 0.3s`.
|
||||
- Проверено, что мёртвые токены — это пыль, а не признак хардкода: дублирующих
|
||||
значений (`max-width`, `0.3s`, muted-цвет) в `src` нет. Потребители
|
||||
`space-text-m/xl/2xl`, `size-content-max-wide` и `size-workspace-min`
|
||||
используются, поэтому шкалы не тронуты.
|
||||
- Шкалы остались неполными намеренно (`duration-s/l` без `m`, `space-text` без
|
||||
`s`): мёртвый элемент шкалы хуже, чем «дырявая» шкала, а вернуть его можно
|
||||
вместе с потребителем.
|
||||
- Режим unused-аудита решён в Q7d: он остаётся предупреждением.
|
||||
|
||||
#### Q7b. Тесты token audit и PostCSS HCT-плагина
|
||||
|
||||
**Статус:** выполнен. У обоих не было ни одного теста, хотя HCT-плагин
|
||||
(`web/scripts/postcss-hct.mjs`, 430 строк) пересчитывает всю палитру.
|
||||
|
||||
- `web/scripts/__tests__/postcss-hct.test.mjs` — 17 тестов: literal и
|
||||
производный `hct()`, seed из `:root` и из hex, цепочка hct-токенов, refs
|
||||
`h/c/t`, `calc/min/max/clamp`, обёртка hue, клампы tone/chroma, несколько
|
||||
вызовов в одном объявлении, а также warn-and-keep для неизвестного seed,
|
||||
циклической ссылки и битого тела `hct()`.
|
||||
- `web/scripts/__tests__/token-audit.test.mjs` — 15 тестов на parity
|
||||
(light-only, dark-only, derived, не-цвет), authorship (hct, color-mix,
|
||||
seed-токены, хардкод с локацией, игнор не-цветов и обычных свойств), unused и
|
||||
предикаты `isColorValue`/`isDerived`.
|
||||
- Ожидания не зашивают вывод плагина, а сравнивают его с эквивалентной
|
||||
литеральной записью: `hct(from var(--seed) h c calc(t + 10))` должен совпасть
|
||||
с `hct(120 40 60)`, если seed — `hct(120 40 50)`. Иначе тест просто
|
||||
зафиксировал бы текущее поведение.
|
||||
- Единственный seam: `checkUnused(root, used = collectUsedTokens())` —
|
||||
зависимость от чтения `src` стала инъецируемой, production-вызов не изменился.
|
||||
- `vitest.config.ts` подхватывает `scripts/**/*.test.mjs`, поэтому скрипты
|
||||
покрываются обычным прогоном и `verify`; `test:rules` не трогали.
|
||||
- Проверка мутацией выявила дыру: первая версия тестов не ловила кламп tone в
|
||||
ветке `from` (смена `clamp(t, 0, 100)` на `0, 90` проходила зелёной). Добавлен
|
||||
отдельный тест на производный кламп; после этого мутация падает.
|
||||
|
||||
#### Q7c. Базовый JS recommended для инфраструктуры линтинга
|
||||
|
||||
**Статус:** выполнен. `eslint-plugins/**` и `scripts/**` больше не вне линта.
|
||||
|
||||
- `web/eslint.config.js`:
|
||||
`toolingFiles = ["**/eslint-plugins/**/*.js", "**/scripts/**/*.mjs"]` получают
|
||||
`js.configs.recommended` плюс `languageOptions` с `globals.node` (иначе
|
||||
`no-undef` ругается на `console`, `process` и `URL` в Node-скриптах).
|
||||
- Полные TS/svelte-наборы туда не подключаются: это `.mjs` и конфиги, а не
|
||||
продуктовый код.
|
||||
- Масштаб оказался небольшим: 28 проблем, из них 26 — `no-undef` на
|
||||
Node-глобалах и только 2 настоящие: мёртвые импорты `existsSync` и `resolve` в
|
||||
`scripts/serve-static.mjs`. Линтер сразу нашёл пыль в инфраструктуре, для
|
||||
которой его раньше не было.
|
||||
- `eslint-plugins/**` дал ноль проблем, а новые тесты `scripts/__tests__` тоже
|
||||
попали под правило.
|
||||
- Проверено пробой: после включения неиспользуемая переменная и вызов
|
||||
неопределённого глобала в `scripts/` дают ошибки, то есть правило применилось,
|
||||
а не молча пропустило файлы.
|
||||
|
||||
#### Q7d. Решения по оставшимся вопросам
|
||||
|
||||
**Статус:** закрыт тремя решениями.
|
||||
|
||||
1. **Обычный CSS под те же токен-правила, что Svelte styles — узко.** В `src`
|
||||
всего один CSS-файл, и он же словарь токенов, поэтому переносить туда
|
||||
`no-hardcoded-in-svelte` и подобные нечего: stylelint уже проверяет
|
||||
`src/**/*.css` (префиксы, запрет `!important`, цвет только у brand-токенов),
|
||||
а `check-tokens.mjs` дублирует проверку цвета. Реальный пробел был один —
|
||||
`var()` может ссылаться на несуществующий токен, — его закрыл новый модуль
|
||||
`web/scripts/token-audit/refs.mjs` (см. Q7e). Широкую унификацию механизмов
|
||||
решено не делать: это лишний слой поверх дублирующихся проверок.
|
||||
2. **Конфиги под рекомендации — только JS.** `*.config.js`
|
||||
(`eslint`/`postcss`/`stylelint`) добавлены в `toolingFiles`; по измерению они
|
||||
дают 0 проблем, поэтому это бесплатно. TS-конфиги (`vite`, `vitest`,
|
||||
`playwright`) не подключены: им потребовался бы `tseslint`-парсер и запись
|
||||
`vite.config.ts` в `allowDefaultProject`, а выигрыш близок к нулю. Попутно
|
||||
удалена мёртвая запись `svelte.config.js` в `allowDefaultProject` — такого
|
||||
файла нет.
|
||||
3. **Unused-аудит остаётся предупреждением.** Правило «новый токен — вместе с
|
||||
потребителем» закреплено в `AGENTS.md`. Причина: `verify` входит в
|
||||
deploy-workflow, поэтому стилевой долг блокировал бы деплой сайта; кроме
|
||||
того, чтение токена из JS (`getPropertyValue`) аудит не видит и дал бы ложное
|
||||
срабатывание. Baseline чистый, пыль вычищена в Q7a.
|
||||
|
||||
#### Q7e. Проверка резолва `var()` в `app.css`
|
||||
|
||||
**Статус:** выполнен. Аудит падает, если `var(--x)` в `app.css` указывает на
|
||||
токен, которого нет в этом же файле.
|
||||
|
||||
- `web/scripts/token-audit/refs.mjs` + вызов в `check-tokens.mjs`; режим —
|
||||
ошибка, а не предупреждение, потому что это дефект (объявление молча
|
||||
отбрасывается), а не стилистический долг.
|
||||
- `var(--x, fallback)` не считается ошибкой: fallback легален без определения
|
||||
токена.
|
||||
- 7 тестов в `scripts/__tests__/token-audit.test.mjs`: резолв в обеих темах,
|
||||
опечатка с локацией, fallback, дедуп повторов, ссылка внутри определения
|
||||
токена, `var()` в обычном свойстве.
|
||||
- Проверено на живом файле: временная опечатка `var(--color-bordr)` дала
|
||||
`Unresolved var() references` и код выхода 1; `app.css` восстановлен байт в
|
||||
байт.
|
||||
|
||||
### Q8. Новые автоматические правила
|
||||
|
||||
**Оценка:** L. **Зависимости:** Q4, Q7. **Разбивка:** три атомарных шага, каждый
|
||||
заканчивается либо работающим warn-правилом с нулевым baseline, либо измерением
|
||||
с записью вывода. Механически важно: `lint-all` запускает ESLint с
|
||||
`--max-warnings=0`, поэтому warn-правило обязано приземлиться без срабатываний
|
||||
на текущем коде.
|
||||
|
||||
#### Q8.1. Пользовательские тексты вне i18n
|
||||
|
||||
**Статус:** выполнен. Пилот `i18n/no-hardcoded-user-text` (warn) с нулевым
|
||||
baseline; 2 реальных нарушения исправлены.
|
||||
|
||||
- Измерение до правила: 42 строковых атрибута пользовательского текста, все — в
|
||||
`/kit`; вне кита текстовые узлы дали 2 находки. Итог после исключений: 2
|
||||
настоящих бага и 2 бренда, которые план требовал исключить.
|
||||
- `web/eslint-plugins/i18n/no-hardcoded-user-text.js`: узлы `SvelteText` плюс
|
||||
строковые литералы пользовательских атрибутов; `<style>`/`<script>`
|
||||
пропускаются.
|
||||
- Исключения из плана реализованы списками, а не эвристиками: форматы/единицы/
|
||||
имена файлов/версии/размеры, ALL CAPS идентификаторы (registry source
|
||||
strings), `/kit` через scope, бренд через опцию `allowWords` (в конфиге, а не
|
||||
в коде правила).
|
||||
- Исправлены 2 бага: `ColorPairControl.svelte` показывал английские `From`/`To`.
|
||||
Ключи `ui.from`/`ui.to` уже существовали в обоих словарях — компонент их
|
||||
просто не использовал, поэтому новые ключи не понадобились.
|
||||
- 30 тестов в `__tests__/no-hardcoded-user-text.test.ts`; мутацией проверено,
|
||||
что правило не декоративное: с пустым `allowWords` бренд начинает репортиться.
|
||||
- Известные слепые зоны, зафиксированные как границы пилота: текст, собранный в
|
||||
скрипте и переданный выражением, и текст вне Svelte-шаблонов (например, в
|
||||
атрибутах, приходящих из registry). Расширение поверхности — отдельная работа,
|
||||
а не молчаливое расширение правила.
|
||||
|
||||
#### Q8.2. Дублирование HTML/Svelte-разметки
|
||||
|
||||
**Статус:** измерено, решение — правило не делать.
|
||||
|
||||
Одноразовый аудит (скрипт после измерения удалён) по 65 `.svelte` в
|
||||
`components/**` и `routes/**`: при пороге 5 нормализованных строк — **одна**
|
||||
пара, при пороге 3 строки — 17 пар, и все они шум (`</label>`, `{/each}`,
|
||||
`{#if children}`). Единственная осмысленная находка — побайтово идентичные
|
||||
`src/routes/+page.svelte` и `src/routes/list-tools/+page.svelte`; она заведена в
|
||||
`docs/backlog.md` (идея 16).
|
||||
|
||||
Почему автоматика отменена:
|
||||
|
||||
- построчное сравнение Svelte-разметки принципиально ненадёжно: одинаковый
|
||||
контейнер с разным содержимым даёт ложное срабатывание, а намеренно общий
|
||||
класс — ложное отсутствие сигнала;
|
||||
- чтобы не ловить шум, порог пришлось бы поднять до 12+ строк, и тогда правило
|
||||
срабатывает ровно один раз — на паре страниц, которые по замыслу должны
|
||||
различаться; дальше нужна либо allowlist-запись, либо принудительный
|
||||
рефакторинг;
|
||||
- контроль остаётся на ревью: расхождение видно в диффе, а поведение ловит e2e
|
||||
(`navigation.spec.ts` открывает все маршруты, `catalog.spec.ts` проверяет
|
||||
каталог), а извлечение общей разметки уже закреплено конвенцией в `AGENTS.md`.
|
||||
|
||||
#### Q8.3. Согласованность комментариев
|
||||
|
||||
**Статус:** решение — автоматический lint не делать, это process policy.
|
||||
|
||||
Описания инструментов различаются осознанно, и ловить это текстовым правилом
|
||||
дороже, чем смотреть на ревью. Действенная часть выполнена вручную: все
|
||||
`TODO`/`FIXME` из кода перенесены в `docs/backlog.md` (7 записей), в коде не
|
||||
осталось ни одного незарегистрированного. Правило-детектор не добавлялось.
|
||||
|
||||
## 4. Что описать в `AGENTS.md`
|
||||
|
||||
В `AGENTS.md` добавить только следующие неочевидные правила:
|
||||
|
||||
1. Какой набор команд считается полным quality gate и что означает успешный
|
||||
результат при warnings.
|
||||
2. Production-scope и политику удаления v1: новые изменения относятся только к
|
||||
production UI; исторические UI-v1-упоминания остаются в архиве.
|
||||
3. Политику e2e: semantic locators, допустимый `data-testid`, запрет
|
||||
бессмысленных counts/classes/alt/exact prose.
|
||||
4. Политику i18n: новые пользовательские тексты через i18n; registry source,
|
||||
brand, formats и `/kit` — явные исключения.
|
||||
5. Жизненный цикл `test.fixme`: только для зарегистрированного бага, с ссылкой;
|
||||
исправление и удаление теста происходят вместе.
|
||||
6. Комментарии и `TODO`/`FIXME` должны объяснять «почему» и иметь ссылку на
|
||||
работу.
|
||||
7. Shared-модули `core`/`theme` требуют проверки production-потребителей.
|
||||
|
||||
Синтаксические списки ESLint-правил и подробные токены остаются в
|
||||
`web/eslint-plugins/README.md`.
|
||||
|
||||
### Политика документации для агентов
|
||||
|
||||
`AGENTS.md` не должен превращаться в полный справочник проекта. В нём оставляем
|
||||
только обязательный рабочий контракт: команды, production-scope, ключевые
|
||||
инварианты, исключения и ссылки на подробные документы. Если агенту нужно понять
|
||||
причину или workflow, в `AGENTS.md` должна быть короткая понятная формулировка;
|
||||
длинные примеры, точные процедуры, таблицы и критерии проверки переносятся в
|
||||
`docs/` или в README рядом с соответствующей подсистемой.
|
||||
|
||||
Принцип выбора места документа:
|
||||
|
||||
1. **Поведение агента, которое нельзя пропустить** — кратко в `AGENTS.md`.
|
||||
2. **Точная команда, скрипт, параметр или критерий качества** — в
|
||||
`docs/quality-gates.md` или в README соответствующей подсистемы.
|
||||
3. **Архитектурное решение, поток данных и границы модулей** — в
|
||||
`docs/architecture-overview.md` или существующем плане.
|
||||
4. **Реализация конкретного lint-правила и его fixtures** — только в
|
||||
`web/eslint-plugins/README.md` и исходниках правила.
|
||||
5. **Продуктовая задача, backlog и ручная приёмка** — в `docs/backlog.md`,
|
||||
активном плане или `docs/checklist-manual-testing.md`.
|
||||
|
||||
Подробные документы не дублируются в нескольких местах: у документа должен быть
|
||||
один источник истины, а остальные файлы ссылаются на него. Все подробные
|
||||
материалы хранятся в `docs/`, чтобы не засорять корень репозитория.
|
||||
|
||||
Активный документ должен иметь статус (`draft`, `active`, `completed` или
|
||||
`archived`) и понятную область применения. Архивные планы не используются как
|
||||
текущие критерии приёмки без отдельной сверки с кодом. Новый документ создаётся
|
||||
только когда темы нет в существующем файле; иначе обновляется существующий.
|
||||
|
||||
### Архивирование и срок хранения
|
||||
|
||||
Завершённые, отменённые или явно устаревшие документы переносятся в
|
||||
`docs/archive/`, а не остаются в корне `docs/` как рабочие планы. При переносе
|
||||
добавляются дата архивирования, дата удаления и причина. Архивный документ —
|
||||
временная справка для быстрого просмотра прошлых решений, но не источник текущих
|
||||
требований.
|
||||
|
||||
Срок хранения после архивирования — 30 дней. После этой даты документ можно
|
||||
удалить после проверки ссылок; если он снова нужен, его восстанавливают из git
|
||||
history или пересоздают как новый активный документ. Подробная процедура и
|
||||
шаблон метаданных находятся в `docs/archive/README.md`.
|
||||
|
||||
### Какие документы подготовить
|
||||
|
||||
| Документ | Назначение | Когда создавать/обновлять |
|
||||
| ------------------------------- | --------------------------------------------------------------------------------------- | --------------------------------------------------------------------- |
|
||||
| `docs/README.md` | Индекс активных документов, карта источников истины и краткий порядок чтения для агента | Q0; при добавлении нового активного документа |
|
||||
| `docs/agent-workflow.md` | Подробный workflow: исследование, изменение, проверка и обновление документации | Q0–Q1; только для процессов, не описанных кратко в `AGENTS.md` |
|
||||
| `docs/quality-gates.md` | Команды проверки, semantics errors/warnings, порядок запуска и требования CI | Q1; при изменении scripts/config/CI |
|
||||
| `docs/testing-strategy.md` | Слои тестов, locator policy, i18n assertions, fixtures, `test.fixme` и правила e2e | Q4; при изменении тестовой архитектуры |
|
||||
| `docs/architecture-overview.md` | Карта `core`, registry, executor, production UI и shared-модулей | Q5 или перед крупным рефакторингом; не дублировать существующие планы |
|
||||
| `web/eslint-plugins/README.md` | Детали custom lint rules, token policy и fixtures | Уже существует; менять только вместе с правилами |
|
||||
| `docs/archive/README.md` | Политика временного архива, даты и удаления через 30 дней | Q0; обновлять при изменении архивной политики |
|
||||
|
||||
Не создавать отдельные `docs/commands.md`, `docs/known-issues.md` или копию
|
||||
backlog: команды принадлежат `quality-gates`, известные проблемы — backlog и
|
||||
checklist. `README.md` в корне остаётся кратким обзором продукта, а не вторым
|
||||
руководством агента.
|
||||
|
||||
## 5. Порядок PR
|
||||
|
||||
1. **Q0** — синхронизация backlog/checklist/AGENTS.
|
||||
2. **Q1** — `verify` и PR quality gate.
|
||||
3. **Q2** — i18n parity и strict warning gate.
|
||||
4. **Q3** — удалить v1 отдельной атомарной задачей.
|
||||
5. **Q4** — тестовый контракт и stale-тесты для production UI.
|
||||
6. **Q5** — preview-баги и снятие `fixme`.
|
||||
7. **Q6** — расширенное покрытие.
|
||||
8. **Q7** — тесты lint-инфраструктуры и unused tokens.
|
||||
9. **Q8** — report-only duplicate/text audits; blocking — только после baseline.
|
||||
|
||||
Каждый PR должен быть атомарным и небольшим; не объединять `SchemaPreview`
|
||||
refactor, новые i18n-правила и исправление продуктовых багов в одну работу.
|
||||
|
||||
Продуктовые направления, которые план не начинает (платформа PWA/API/WASM,
|
||||
plural-формы, batch/архивы, region-инструменты), живут в `docs/backlog.md` и
|
||||
`docs/roadmap.md`; границы этого плана — в разделе 1.
|
||||
@@ -7,7 +7,7 @@
|
||||
> Статус: **частично выполнен** — базовая i18n-инфраструктура нового UI,
|
||||
> локализация ошибок, `i18n/dict-consistency` и zero-warning baseline Q2
|
||||
> реализованы; plural-формы остаются отдельной задачей. Актуальные решения и
|
||||
> ограничения — в `docs/plan-codebase-quality.md`.
|
||||
> ограничения — в `docs/testing-strategy.md` и `docs/backlog.md`.
|
||||
|
||||
## 1. Что получается
|
||||
|
||||
|
||||
@@ -0,0 +1,380 @@
|
||||
# План: переезд на новый дизайн (refs) — параллельная сборка
|
||||
|
||||
> Статус: **archived** Архивирован: 2026-09-26 Удалить после: 2026-10-26
|
||||
> Причина: переезд на новый дизайн завершён (C17/C18/Q3), C20 и C21 выполнены,
|
||||
> пункты полировки разобраны и разложены по `docs/backlog.md`. Источник правды
|
||||
> по визуальному языку — `web/src/app.css`, `web/src/lib/components/**`, витрина
|
||||
> `/kit` и приёмка в `docs/checklist-manual-testing.md`. Использование: только
|
||||
> историческая справка
|
||||
|
||||
> Статус: C17 **выполнен**, C18 **выполнен**, Q3 (удаление старого UI)
|
||||
> **выполнен**, C20 **выполнен**, C21 **выполнен**. Открытых шагов не осталось.
|
||||
> Покупка домена НЕ была блокером C17: домен нужен для S1 (`plan-seo.md`) —
|
||||
> canonical, sitemap, снятие `noindex`.
|
||||
>
|
||||
> Источники: `refs/` — Next/React-референс (правда по пикселям и интерактиву),
|
||||
> `refs-html/` — статические HTML-снимки для быстрого просмотра в браузере.
|
||||
>
|
||||
> **Историческая стратегия перехода:** сначала новый UI собирался отдельно,
|
||||
> затем стал production, после чего legacy-ветка была удалена Q3. Текущие
|
||||
> критерии относятся только к production UI; старые решения и детали перехода
|
||||
> остаются в архивных планах.
|
||||
|
||||
## 0. Почему меняем стратегию
|
||||
|
||||
- Переход выполнялся параллельной сборкой, чтобы новый UI можно было проверить
|
||||
независимо; теперь production использует только новые маршруты и `app.css`.
|
||||
- Дизайн-токены и глобальные стили живут в `web/src/app.css`; отдельные layout
|
||||
для удалённого UI-v1 больше не нужны.
|
||||
|
||||
## 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. Маршрутизация: историческая схема перехода
|
||||
|
||||
Во время перехода UI-kit проверялся в отдельной ветке, затем его страницы были
|
||||
перенесены на production-маршруты. Эта схема нужна только для восстановления
|
||||
контекста C17; текущая структура описана в разделе 7, отдельных preview- или
|
||||
legacy-layouts в production больше нет.
|
||||
|
||||
## 7. Фазы выполнения
|
||||
|
||||
- **C1–C16 — UI-kit и проверка нового дизайна.** Токены, компоненты и
|
||||
интерактивные состояния приняты; детали перехода остаются в архивном
|
||||
`plan-design-fix.md`.
|
||||
- **C17/C18 — production-переход.** Новые страницы перенесены на реальные
|
||||
корневые маршруты; `app.css` стал единственным production-стилем.
|
||||
- **Q3/C19 — cleanup.** Удалены UI-v1, legacy CSS, v1-тесты, fixtures и
|
||||
isolation-конфигурация; production build и verify проверяются отдельно.
|
||||
- **C20 — следующий этап.** Финальный проход по брейкпоинтам и визуальной
|
||||
приёмке production UI.
|
||||
- **C21 — отдельная уборка.** Удалить `refs/` и `refs-html/`, если они больше не
|
||||
нужны.
|
||||
|
||||
Критерии каждого этапа — зелёные `test`/`check`/`build`, отсутствие остаточных
|
||||
legacy-путей в production-коде и ревью в браузере для UI-изменений.
|
||||
|
||||
## 8. Смоук (после фазы 3, контроль на фазе 4)
|
||||
|
||||
1. Светлая и тёмная тема: сетка фона, панели, шахматка канвасов читаются,
|
||||
контраст mono-лейблов достаточный.
|
||||
2. Инструмент с параметрами (градиент): slider/color/toggle в стиле
|
||||
setting-groups, автозапуск не регрессировал.
|
||||
3. Удаление фона: сравнение source/result, маска, пипетка работают на новой
|
||||
шахматке.
|
||||
4. Chain из 3+ инструментов: step-cards рендерятся, удаление/сброс работают.
|
||||
5. Узкий экран 800px: настройки над превью, sticky выключается как в рефе.
|
||||
6. RU/EN: новые лейблы локализованы; mono-лейблы рефа — либо ключи i18n, либо
|
||||
осознанно английские брендовые элементы (см. §9).
|
||||
|
||||
## 9. Открытые вопросы
|
||||
|
||||
- **Имя финального каталога компонентов:** решено на C1 — писали в `kit/`, C17
|
||||
разложил его в `components/` (подпапки fields/layout/ui). На C21 — только
|
||||
навести порядок внутри `components/`, переименование не требуется.
|
||||
- **Единый production CSS:** `app.css` подключается в корневом layout; отдельных
|
||||
legacy-layouts и переключателей версий больше нет.
|
||||
- **Mono-лейблы рефа на английском** (`PROCESSING PIPELINE`): завести i18n-
|
||||
ключи, но допустить часть английских брендовых («приборная панель»).
|
||||
- **Версия в шапке/футере:** из `package.json` через `define` или захардкодить —
|
||||
мелочь, решить при сборке topbar.
|
||||
- **`refs/` и `refs-html/` — одноразовый источник, НЕ поддерживаемый.** После
|
||||
внедрения нового дизайна все правки дизайна делаются сразу в продакшен-коде
|
||||
(`design2.css` + `kit/`), а не в `refs/`. Механизм синхронизации «ref → код»
|
||||
не нужен и не создаётся. `refs/` и `refs-html/` — только историческая справка;
|
||||
их можно удалить в C21, они не являются частью рантайма.
|
||||
|
||||
## 10. План исполнения (мелкие коммиты)
|
||||
|
||||
Ветка `feat/redesign`. Каждый шаг — отдельный коммит, цель < ~500 строк. Перед
|
||||
стартом: `test`/`build` зелёные на базе (якорь регрессии).
|
||||
|
||||
### Шаг 0. Изоляция layout (без дизайна)
|
||||
|
||||
- [x] **[C1]** Сделать корневой `routes/+layout.svelte` нейтральным: убрать
|
||||
`import '../app.css'`, оставить только init темы/locale и
|
||||
`<div>{children}</div>`.
|
||||
- [x] **[C2]** Создать `routes/(old)/+layout.svelte` (импорт `app.css` + старая
|
||||
шапка/подвал) и перенести старые страницы (`+page`, `demo`, `list-tools`,
|
||||
`tools/[id]`) в `(old)/`. URL не меняются. Проверка: старый сайт
|
||||
идентичен. _(файлы — move, объём строк минимален)_
|
||||
|
||||
### Шаг 1. Фундамент нового дизайна
|
||||
|
||||
- [x] **[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).
|
||||
- [x] **[C4]** _(слит в C3: шрифты нельзя подключить до создания `design2.css`)_
|
||||
`[data-theme='dark']` (таблица §2). Без утилитарных классов.
|
||||
- [x] **[C5]** Примитивы batch 1: `AppShell`, `BlueprintGrid`, `CheckerCanvas`,
|
||||
`MonoLabel`, `StatusDot`/`StatusLine`.
|
||||
- [x] **[C6]** Примитивы batch 2: `Panel`, `PanelHeading`, `SettingGroup`,
|
||||
`SettingsFooter`, `MetaRow`/`MetaList`, `Segmented`, `Badge`, `StepCard`,
|
||||
`ToolCard`, `ImageCard`/`PreviewTile`.
|
||||
- [x] **[C7]** Контролы: `Icon`, `IconButton`, `Button`, `Field`, `SliderField`,
|
||||
`ColorField`, `TextField`, `SelectField`, `CheckboxField`, `Toggle`,
|
||||
`SegmentedControl` (= `Segmented` из C6), `EmptyState`, `CodeBlock`,
|
||||
`DownloadButton`.
|
||||
|
||||
### Шаг 2. Витрина kit (проверка компонентов)
|
||||
|
||||
- [x] **[C8]** `preview/+layout.svelte` (импорт `design2.css` + `<AppShell>` +
|
||||
переключатель темы) и временная `preview/kit` страница со всеми
|
||||
состояниями (light/dark, hover/disabled, варианты Button/Segmented/Badge и
|
||||
пр.)
|
||||
|
||||
### Шаг 3. Новые страницы
|
||||
|
||||
- [x] **[C9]** `/preview` — воркспейс (eyebrow, h1, lede, поиск, «открыть
|
||||
последний»). Поиск фильтрует `TOOLS` из `$lib/registry`; карточки
|
||||
результата пока ведут на `#` (реальные `/preview/tools/[id]` подключим на
|
||||
C12, иначе prerender падает на 404).
|
||||
- [x] **[C10]** `/preview/list-tools` — каталог карточек (группировка по
|
||||
`CATEGORIES`, `ToolCard`). Ссылки пока `#` (реальные `/preview/tools/[id]`
|
||||
подключим на C12).
|
||||
- [x] **[C11]** `/preview/demo` — зеркало воркспейса (4 шага + панель превью),
|
||||
статичный приёмочный стенд.
|
||||
- [x] **[C12]** `/preview/tools/[id]` — data-driven settings-panel +
|
||||
preview-panel (gradient live, остальные — плейсхолдер); `NumberField`,
|
||||
`entries()` для prerender, ToolCard-ссылки подключены.
|
||||
- [x] **[C13]** `/preview/tools/[id]` — comparison-grid для file-source
|
||||
инструментов (`Dropzone` + before/after `CheckerCanvas`, `result-meta`);
|
||||
охватывает background-remover и прочие.
|
||||
- [x] **[C14]** `/preview/tools/[id]` — исполняемая цепочка: `StepCard`-шаги
|
||||
(параметры inline) + «Add step» (chainable), живой прогон пайплайна на
|
||||
исходнике, превью каждого шага + итог + Download.
|
||||
|
||||
### Шаг 4. Переключатель и проверка
|
||||
|
||||
- [x] **[C15]** В старой шапке ссылка «Preview v2» → `/preview`, ставит
|
||||
`localStorage['easy-png-tools:preview-v2']='1'`.
|
||||
- [x] **[C16]** База под дизайн: рабочий переключатель тем (persist в
|
||||
localStorage, применяется на `<html>`; проверено — фон
|
||||
`#eef1f4`↔`#11171d`), `TopBar` приведён к виду `demo.html` (бренд + синий
|
||||
хлебный крош `/* страница */`, безрамочные иконки, язык — active =
|
||||
`--foreground`/`--background`), страницы `/preview/*` на всю ширину (убран
|
||||
`max-width`). Точечное выравнивание к рефу отложено: реф-дизайн
|
||||
неконсистентен. (доказательство отсутствия регресса). Фиксы по результатам
|
||||
— отдельными мелкими коммитами.
|
||||
|
||||
### Шаг 5. Сделать новый дизайн основным
|
||||
|
||||
> C17 — **выполнен** (`docs/archive/plan-c17.md`): production-маршруты
|
||||
> перенесены в корень, `app.css` стал корневым. C18 (правки импортов/редиректов
|
||||
> после переноса, проверка билда) — **выполнен**. Q3 удалил старые UI-маршруты и
|
||||
> код. Остались C20 и C21.
|
||||
|
||||
- [x] **[C17]** Перенести `preview/*` → реальные маршруты (move), удалить
|
||||
`(old)/`, корневой `+layout.svelte` импортирует `design2.css` + новый
|
||||
shell. Убрать флаг/кнопку. **Снять `noindex, nofollow`** со страницы
|
||||
инструмента (`preview/tools/[id]/+page.svelte`) — страницы становятся
|
||||
боевыми лендингами ровно в момент переезда (SEO: `plan-seo.md`, S1a).
|
||||
**Временное решение:** если домен ещё не куплен, `noindex` можно оставить
|
||||
до S1 — тогда C17 делается без снятия `noindex`, а снятие происходит
|
||||
вместе с подключением canonical в S1. **И обновить `newCode` в
|
||||
`web/eslint.config.js`:** заменить `src/routes/preview/**` на
|
||||
`src/routes/**` (после переезда все маршруты — новый дизайн), компонентный
|
||||
glob `src/lib/components/kit/**` пока оставить. _(Итог: `newCode` обновлён
|
||||
(`components/**`, `routes/**`), `noindex` оставлен до S1 — домен ещё не
|
||||
куплен. Детали: `docs/archive/plan-c17.md`.)_
|
||||
- [x] **[C18]** Правки импортов/редиректов после переноса, проверка билда.
|
||||
_(Билд собирается без ошибок, линт/свелт-чека нет — выполнен.)_
|
||||
|
||||
### Шаг 6. Проверка нового дизайна
|
||||
|
||||
- [x] **[C20]** Финальный проход: grep по старым токенам и классам пуст (0
|
||||
совпадений по `--bg`/`--surface`/`--text`/`--blue`/`--panel` в `web/src`),
|
||||
`app.css` — единственный production-стиль, `components/` разложен как в
|
||||
C17 (`fields`/`layout`/`ui`), production-маршруты и пользовательские
|
||||
потоки покрыты e2e. Визуальная приёмка живёт в
|
||||
`docs/checklist-manual-testing.md`.
|
||||
|
||||
### Шаг 7. Cleanup
|
||||
|
||||
- [x] **[C19/Q3]** Удалить старое: маршруты `routes/v1/**`, код `lib/v1/**`,
|
||||
`app_v1.css`, v1-тесты, fixtures и isolation-плагин; убрать v1-исключения
|
||||
из конфигурации и активной документации.
|
||||
- [x] **[C21]** `refs/` и `refs-html/` удалены — каталогов в репозитории нет.
|
||||
- [x] Навести порядок в `components/`: подпапки `fields`/`layout`/`ui`.
|
||||
|
||||
> C19/Q3 — отдельная cleanup-задача, не источник пользовательской ценности.
|
||||
> После удаления проверяются только production-маршруты и общие модули.
|
||||
>
|
||||
> Если какой-то коммит тянет за собой > 500 строк (например, C6/C7), разбивать
|
||||
> на под-коммиты по 2–4 компонента. Тесты/билд прогонять после каждого коммита,
|
||||
> чтобы регрессия локализовалась одним шагом назад.
|
||||
>
|
||||
> **Атомарность и точки «поломки» между коммитами.**
|
||||
>
|
||||
> - C1+C2 уже сделаны вместе (связаны: нейтральный root без `(old)`-layout
|
||||
> ломает старый сайт). Аналогично связанных пар больше нет.
|
||||
> - C3 и C4 слиты: шрифты подключаются внутри `design2.css`, поэтому файл должен
|
||||
> существовать в том же коммите.
|
||||
> - Коммиты **C3→C16 не трогают старый сайт** `(old)/` — они только добавляют
|
||||
> новые файлы/маршруты, сломать старый сайт на них нельзя.
|
||||
> - **C17 — атомарный переключатель**: перенос `preview/*` на реальные пути +
|
||||
> удаление `(old)/` + смена root-layout — в ОДНОМ коммите. Снятие `noindex`
|
||||
> также в C17, **если домен уже куплен**; иначе — отложить на S1. Не дробить
|
||||
> сам C17.
|
||||
> - Очерёдность зависимостей: `design2.css` (C3) и kit-компоненты (C5–C7) должны
|
||||
> существовать до страниц, которые их импортируют (C8–C14).
|
||||
|
||||
## Бэклог полировки — закрыт по итогам реализации
|
||||
|
||||
Пять пунктов проверены по коду: четыре сняты как выполненные, один перенесён в
|
||||
`docs/backlog.md`.
|
||||
|
||||
- `Toggle` прямоугольный — выполнено (`border-radius: 0`).
|
||||
- `Segmented`: последняя кнопка не растянута — выполнено (`flex: 1` у пунктов).
|
||||
- Отступ в `DownloadButton` между текстом и size-чипом — снято как неактуальное:
|
||||
size-чипа больше нет, компонент тонкая обёртка над `Button`.
|
||||
- Паддинги в полях — выполнено: `TextField` и `SelectField` имеют
|
||||
`padding: var(--space-s) var(--space-m)`.
|
||||
- Радиусы бордеров против референсов — перенесено в `docs/backlog.md`.
|
||||
Reference in New Issue
Block a user