mirror of
https://github.com/Ku6epXBOCTuK/easy-png-tools.git
synced 2026-11-13 17:08:20 +00:00
docs: update plans, backlog
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
| Юнит-тесты | `pnpm --dir web test` |
|
||||
| Тесты lint-правил | `pnpm --dir web test:rules` |
|
||||
| Линтинг | `pnpm --dir web lint:all` |
|
||||
| Только токены | `pnpm --dir web lint:tokens` |
|
||||
| E2E | `pnpm --dir web test:e2e` |
|
||||
| Форматирование | `pnpm format` (код + docs) |
|
||||
| Проверка форматирования | `pnpm check:docs` + `pnpm --dir web format:check` |
|
||||
@@ -35,6 +36,7 @@
|
||||
| Детали lint-правил и токенов | `web/eslint-plugins/README.md` |
|
||||
| Задачи и идеи | `docs/backlog.md` |
|
||||
| Актуальный список инструментов | `docs/tools-map.md` |
|
||||
| Правка полей и раскладки по волнам | `docs/plan-fields-audit-fixes.md` |
|
||||
| Ручная приёмка UI | `docs/checklist-manual-testing.md` |
|
||||
| Индекс остальных документов | `docs/README.md` |
|
||||
|
||||
|
||||
+20
-6
@@ -21,19 +21,22 @@
|
||||
| `docs/agent-workflow.md` | Порядок работы, нагрузка на машину, протокол ревью | active |
|
||||
| `docs/testing-strategy.md` | Правка e2e: locator-ы, fixtures, browser matrix | active |
|
||||
| `docs/backlog.md` | Продуктовые, UX- и инфраструктурные задачи | active |
|
||||
| `docs/roadmap.md` | Направления развития, фазы 1–10 | active |
|
||||
| `docs/roadmap.md` | Направления развития, фазы 1–11 | active |
|
||||
| `docs/tools-map.md` | Актуальный список инструментов против реестра | active |
|
||||
| `docs/tools-audit.md` | Сырые UX/UI-находки по инструментам (вход аудита) | active |
|
||||
| `docs/plan-fields-audit-fixes.md` | Разбивка аудита на волны правок | draft |
|
||||
| `docs/plan-seo.md` | SEO/GEO-план | active |
|
||||
| `docs/plan-platform.md` | API/PWA/шрифты-план | active |
|
||||
| `docs/plan-domain.local.md` | Чек-лист переименования бренда и покупки домена | active |
|
||||
| `docs/checklist-manual-testing.md` | Ручная приёмка production UI | active |
|
||||
| `web/eslint-plugins/README.md` | Правка lint-правил, токенов и их конфигов | active |
|
||||
| `docs/archive/README.md` | Архивирование и удаление старых документов | active |
|
||||
|
||||
`docs/plan-domain.local.md` исключён из git через `.gitignore` (выбранное имя
|
||||
домена не должно попасть в историю до покупки). Ссылки на него в `plan-seo.md` и
|
||||
`backlog.md` при клонировании репозитория не разрешаются — это ожидаемо.
|
||||
`docs/plan-domain.local.md` **исключён из git** через `.gitignore`: выбранное
|
||||
имя домена не должно попасть в историю до покупки. В клоне файла нет — это
|
||||
ожидаемо, и ссылки на него в `plan-seo.md` и `backlog.md` не разрешаются. Строка
|
||||
в таблице оставлена, чтобы задачу R1/R2 можно было найти по индексу; сам
|
||||
чек-лист лежит только на машине разработчика.
|
||||
|
||||
## Правила
|
||||
|
||||
@@ -46,11 +49,22 @@
|
||||
`docs/commands.md` или `docs/known-issues.md`: команды принадлежат
|
||||
`quality-gates`, известные проблемы — backlog и checklist. `README.md` в корне
|
||||
остаётся кратким обзором продукта, а не вторым руководством агента.
|
||||
- Ссылаясь на инструмент по id, сверять id с реестром (`web/src/lib/registry/`):
|
||||
исторические названия в старых планах не действуют.
|
||||
- `docs/backlog.md` содержит только открытые задачи: выполненные удаляются, а
|
||||
критичные для проекта факты из них переносятся в тематический документ.
|
||||
- `docs/archive/` — временная историческая справка, не источник критериев
|
||||
приёмки; срок хранения 30 дней, файлы восстановимы из git history.
|
||||
Единственный документ в архиве со статусом `active` —
|
||||
`docs/archive/README.md`: он описывает процедуру архивирования, а не архивный
|
||||
материал, поэтому не подлежит 30-дневному удалению.
|
||||
- Ссылаясь на инструмент по id, сверять id с реестром (`web/src/lib/registry/`):
|
||||
исторические названия в старых планах не действуют. Сейчас `id` записи — это
|
||||
одновременно адрес страницы `/tools/<id>`, отдельного поля `slug` нет
|
||||
(`docs/architecture.md`, раздел 3).
|
||||
- Поиск по `docs/` задевает `docs/archive/` (объём больше, чем у активных
|
||||
документов вместе). Для правок production-кода архив не нужен: grep по
|
||||
`docs/*.md` без `archive/`.
|
||||
- Проверка ссылок в документации не автоматизирована; ожидаемо неразрешимыми
|
||||
считаются ссылки на `docs/plan-domain.local.md` (файл вне git). Всё остальное
|
||||
неразрешимое — ошибка.
|
||||
- Протокол «остановиться и показать изменения» — `docs/agent-workflow.md`,
|
||||
раздел 6.
|
||||
|
||||
+70
-36
@@ -23,7 +23,7 @@ web/src/lib/
|
||||
web/src/routes/
|
||||
+page.svelte # главная
|
||||
list-tools/ # каталог
|
||||
tools/[id]/ # страница инструмента
|
||||
tools/[id]/ # страница инструмента (адрес = id записи)
|
||||
kit/ # витрина компонентов (production, но исключена из i18n-гейта)
|
||||
```
|
||||
|
||||
@@ -32,6 +32,21 @@ web/src/routes/
|
||||
`registry/**` описывает данные и знает только `core/**` и `registry-schema.ts`.
|
||||
Нарушение этого порядка — повод не заводить импорт, а инвертировать зависимость.
|
||||
|
||||
### Чего в коде нет
|
||||
|
||||
Страница инструмента работает ровно с одним инструментом: загрузил файл →
|
||||
применил одну запись реестра → скачал результат. Отсутствуют:
|
||||
|
||||
- **Пайплайн / цепочки шагов** — модуля нет, состояния нет, localStorage для
|
||||
него нет. `docs/roadmap.md` описывает его как отдельную фазу, а не как
|
||||
поставленное. Под будущую фазу зарезервированы `components/StepCard.svelte`
|
||||
(сейчас импортируется только витриной `/kit`) и ключи `chain.*`,
|
||||
`errors.badPipelineShape`, `errors.noSteps`.
|
||||
- **Разделения `id` и `slug`** — раздел 3.
|
||||
|
||||
Задачи по обоим пунктам — в `docs/backlog.md`; архитектурные решения по
|
||||
пайплайну — `docs/roadmap.md`.
|
||||
|
||||
## 2. Поток данных
|
||||
|
||||
```txt
|
||||
@@ -62,8 +77,8 @@ registry/*.ts ──(TOOLS: ToolEntry[])
|
||||
|
||||
Ключевые решения:
|
||||
|
||||
- **Реестр — единственный источник истины.** Страница, форма и пайплайн строятся
|
||||
из записи; новый инструмент = новая запись + функция `run`.
|
||||
- **Реестр — единственный источник истины.** Страница и форма строятся из
|
||||
записи; новый инструмент = новая запись + функция `run`.
|
||||
- **Исполнение идёт только через `executor.execute`.** Компоненты не зовут
|
||||
`tool.run` напрямую — иначе нельзя перенести тяжёлое в worker/wasm без правки
|
||||
вызывающего кода.
|
||||
@@ -76,22 +91,35 @@ registry/*.ts ──(TOOLS: ToolEntry[])
|
||||
|
||||
`ToolEntry` (`web/src/lib/registry/types.ts`):
|
||||
|
||||
| Поле | Смысл |
|
||||
| ------------- | ------------------------------------------------------------ |
|
||||
| `id` | уникальный ключ; он же URL-anchor и ключ в пайплайне |
|
||||
| `title` | EN-строка реестра, переводится через `tools.*.title` |
|
||||
| `description` | EN-строка реестра, переводится через `tools.*.description` |
|
||||
| `category` | id из `categories.ts` |
|
||||
| `schema` | `toolSchema({ fields, layout?, meta? })` |
|
||||
| `input` | `"image" \| "text" \| "none"` — что ждёт инструмент на входе |
|
||||
| `result` | `"image" \| "text" \| "verdict" \| "files"` — что отдаёт |
|
||||
| `output` | mime/ext/quality для download (не у всех инструментов) |
|
||||
| `run(ctx)` | чистая функция: `ctx` → `ToolResult` |
|
||||
| Поле | Смысл |
|
||||
| ------------- | ------------------------------------------------------------------------------------------------ |
|
||||
| `id` | идентичность инструмента **и** публичный адрес `/tools/<id>` **и** база имени скачиваемого файла |
|
||||
| `title` | EN-строка реестра, переводится через `tools.*.title` |
|
||||
| `description` | EN-строка реестра, переводится через `tools.*.description` |
|
||||
| `category` | id из `categories.ts` |
|
||||
| `schema` | `toolSchema({ fields, layout?, meta? })` |
|
||||
| `input` | `"image" \| "text" \| "none"` — что ждёт инструмент на входе |
|
||||
| `result` | `"image" \| "text" \| "verdict" \| "files"` — что отдаёт |
|
||||
| `domOnly` | инструмент требует DOM (canvas); превью-executor запускает его напрямую, минуя worker |
|
||||
| `output` | mime/ext/quality для download (не у всех инструментов) |
|
||||
| `icon` | id иконки из `tool-icons.ts` |
|
||||
| `run(ctx)` | единственный метод: `ctx` → `ToolResult` |
|
||||
|
||||
Отдельного поля `slug` **нет**: адрес страницы, ключ i18n (`tools.<id>.*`) и имя
|
||||
файла скачивания — это один и тот же `id`. Инструмент работает с любым
|
||||
поддержанным форматом, но страница держит png-интент, поэтому `-png` живёт
|
||||
внутри `id` (`id: "crop-png"` → `/tools/crop-png` → `crop-png.png`). Следствие:
|
||||
переименование `id` меняет адрес страницы, ключи `tools.*` и имя файла.
|
||||
|
||||
Хелперы `genTool` / `imgTool` / `textGen` из `types.ts` задают `run` для типовых
|
||||
случаев (приведение типа результата), чтобы запись не повторяла обвязку. `TOOLS`
|
||||
собирается в `registry/index.ts` из восьми групп; `getTool(id)` — единственный
|
||||
способ достать запись по id.
|
||||
способ достать запись, `registry.test.ts` проверяет уникальность `id`.
|
||||
|
||||
Разделение `id` (внутреннее имя) и `slug` (адрес) — решённое, но **не
|
||||
реализованное** решение: задача и порядок волн в `docs/backlog.md`, раздел
|
||||
«Мелочи всякие». До её выполнения этот документ описывает текущее состояние, а
|
||||
id и slug совпадают.
|
||||
|
||||
## 4. Схема параметров
|
||||
|
||||
@@ -133,15 +161,16 @@ EN-ключей `tools.*` и словарь-переводчик. Инвариа
|
||||
|
||||
## 7. Точки расширения
|
||||
|
||||
| Задача | Где править |
|
||||
| ------------------------------ | ----------------------------------------------------------------- |
|
||||
| Новый инструмент | запись в `registry/<group>.ts` + i18n-ключи + тесты (рецепт ниже) |
|
||||
| Новый вид поля | `registry-schema.ts` + контрол + `FIELDS` (раздел 4) |
|
||||
| Новый маршрут/страница | `web/src/routes/**` + ссылка в каталоге |
|
||||
| Новый визуальный элемент | компонент в `components/**` по образцу `ui/` или `layout/` |
|
||||
| Новый дизайн-токен | `web/src/app.css` (только там) + потребитель сразу |
|
||||
| Тяжёлая операция → worker/wasm | `executor/**`, без правок компонентов |
|
||||
| Мультифайловый результат | `result: "files"` + `zip.ts` |
|
||||
| Задача | Где править |
|
||||
| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Новый инструмент | запись в `registry/<group>.ts` + i18n-ключи + тесты (рецепт ниже) |
|
||||
| Смена `id` | запись реестра + i18n-ключи `tools.*` + `tool-icons.ts` + хардкод id в тестах, `/kit` и e2e; меняется адрес страницы — операция с SEO-последствиями, не часть обычной задачи |
|
||||
| Новый вид поля | `registry-schema.ts` + контрол + `FIELDS` (раздел 4) |
|
||||
| Новый маршрут/страница | `web/src/routes/**` + ссылка в каталоге |
|
||||
| Новый визуальный элемент | компонент в `components/**` по образцу `ui/` или `layout/` |
|
||||
| Новый дизайн-токен | `web/src/app.css` (только там) + потребитель сразу |
|
||||
| Тяжёлая операция → worker/wasm | `executor/**`, без правок компонентов |
|
||||
| Мультифайловый результат | `result: "files"` + `zip.ts` |
|
||||
|
||||
## 8. Рецепт: добавить инструмент
|
||||
|
||||
@@ -150,22 +179,27 @@ EN-ключей `tools.*` и словарь-переводчик. Инвариа
|
||||
2. **Выбрать группу** (`geometry`/`alpha`/`color`/`convert`/`filters`/
|
||||
`generate`/`text`/`analyze`) и положить запись в её экспорт; при новой
|
||||
категории — сначала `categories.ts`.
|
||||
3. **Описать схему** через `field.*`, задать `layout`, если поля нужно
|
||||
3. **Задать `id`.** Он же адрес страницы, он же ключ i18n, он же база имени
|
||||
скачиваемого файла. Страница держит png-интент, поэтому суффикс `-png` входит
|
||||
в `id` (`crop-png`, `resize-png`) — это осознанно, отдельного поля `slug` в
|
||||
реестре нет (раздел 3).
|
||||
4. **Описать схему** через `field.*`, задать `layout`, если поля нужно
|
||||
сгруппировать.
|
||||
4. **Написать `run`** поверх `core/*`; пиксельные операции — чистые функции в
|
||||
5. **Написать `run`** поверх `core/*`; пиксельные операции — чистые функции в
|
||||
`core/`, без DOM.
|
||||
5. **Проверить обязательные контракты** для нового id: entry попадает в `TOOLS`,
|
||||
страница `/tools/<id>` prerender-ится, `input`/`result` согласованы с тем,
|
||||
что делает `run`.
|
||||
6. **Добавить i18n-ключи во все локали** сразу.
|
||||
7. **Тесты:** unit — в `registry/*.test.ts` или рядом с `core`-функцией;
|
||||
6. **Проверить обязательные контракты** для новой записи: она попадает в
|
||||
`TOOLS`, страница `/tools/<id>` prerender-ится, `input`/`result` согласованы
|
||||
с тем, что делает `run`.
|
||||
7. **Добавить i18n-ключи во все локали** сразу (ключ — по `id`).
|
||||
8. **Тесты:** unit — в `registry/*.test.ts` или рядом с `core`-функцией;
|
||||
e2e-smoke — существующий spec по типу `input`/`result` (правила —
|
||||
`docs/testing-strategy.md`).
|
||||
8. **Обновить `docs/tools-map.md`:** новый инструмент обязателен в разделе
|
||||
«Реализовано», формат строки — в шапке раздела. Проверяет
|
||||
`web/src/lib/registry/tools-map-doc.test.ts`: отсутствующий в карте id и
|
||||
несуществующий в реестре id роняют тест. Обоснование и границы автоматизации
|
||||
— `docs/decisions.md`, раздел 4.
|
||||
9. **Обновить `docs/tools-map.md`:** новая страница обязательна в разделе
|
||||
«Реализовано», формат строки — в шапке раздела. Карта перечисляет **`id`**
|
||||
реестра, которые одновременно являются адресами страниц. Проверяет
|
||||
`web/src/lib/registry/tools-map-doc.test.ts`: id без инструмента и инструмент
|
||||
без записи в карте роняют тест. Обоснование и границы автоматизации —
|
||||
`docs/decisions.md`, раздел 4.
|
||||
|
||||
Ожидаемый минимум проверок: `pnpm --dir web check` и `pnpm --dir web test`; для
|
||||
инструмента, меняющего пользовательский поток, — e2e (см. раздел 3
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
# Архив документов
|
||||
|
||||
> Статус: **active**. Здесь хранятся завершённые, отменённые и явно устаревшие
|
||||
> документы как временная историческая справка.
|
||||
> документы как временная историческая справка. Этот README — единственный
|
||||
> документ архива со статусом `active`: он описывает процедуру, а не архивный
|
||||
> материал, поэтому не подлежит 30-дневному удалению.
|
||||
|
||||
## Назначение
|
||||
|
||||
@@ -36,8 +38,12 @@
|
||||
2. Убрать из него ссылки на несуществующие пути и артефакты прошлых волн.
|
||||
3. Добавить обязательные метаданные.
|
||||
4. Переместить файл в `docs/archive/`.
|
||||
5. Проверить ссылки из backlog, планов, checklist и `docs/README.md`: удалённые
|
||||
файлы должны потерять и входящие ссылки.
|
||||
5. Найти оставшиеся входящие ссылки:
|
||||
`git grep -n "<filename>" -- 'docs/*.md' AGENTS.md README.md web/eslint-plugins/README.md`.
|
||||
Шаг 4 ломает их, и агент без этого шага напишет в новом документе ссылку на
|
||||
удалённый файл. Ожидаемо неразрешимыми остаются ссылки на
|
||||
`docs/plan-domain.local.md` — он вне git, это не ошибка (`docs/README.md`,
|
||||
раздел «Правила»).
|
||||
6. Не менять архивный документ под новые требования.
|
||||
|
||||
## Удаление
|
||||
|
||||
+72
-41
@@ -24,6 +24,10 @@
|
||||
|
||||
## Мелочи всякие
|
||||
|
||||
Порядок правок полей и раскладки — `docs/plan-fields-audit-fixes.md`, сами
|
||||
находки («что плохо», с датой аудита) — `docs/tools-audit.md`. Здесь — как это
|
||||
исправить; находка в аудите не отменяет задачу здесь.
|
||||
|
||||
- [ ] Пересмотреть тесты - исправить всякие хрупкие, которые проверяют
|
||||
количество инструментов, точное совпадение текстов, поиск по
|
||||
`img[alt="Result image"]`, svelte классам и тому подобные
|
||||
@@ -55,6 +59,9 @@
|
||||
шрифты с кириллицей? сделать галочку "только с кириллицей"??? (решение о
|
||||
наборе и лицензиях: `docs/plan-platform.md` §4 — только OFL/Apache с
|
||||
паспортом, семейства с кириллицей)
|
||||
- [ ] поле ввода текста - сделать по умолчанию нормального размера, а не одну
|
||||
строку для svg - форматирование или подсветку синтаксиса??? для bin\hex -
|
||||
форматирование по колонкам??? для hex\bytes\rgba - пример в placeholder
|
||||
- [ ] сделать загрузку картинки исходника по ctrl+v и drag-and-drop (dropzone)
|
||||
- [ ] square-mask-png - проверить работу - параметры неверно считаются. width -
|
||||
100, height - 100, x - 100, y - 100, обрезает 50% на 50% картинку
|
||||
@@ -64,16 +71,32 @@
|
||||
значения
|
||||
- [ ] для keep Aspect field - пересчитывать второе поле, учитывать какое было
|
||||
изменено
|
||||
- [ ] поле ввода текста - сделать по умолчанию нормального размера, а не одну
|
||||
строку для svg - форматирование или подсветку синтаксиса??? для bin\hex -
|
||||
форматирование по колонкам??? для hex\bytes\rgba - пример в placeholder
|
||||
- [ ] обновить страницу kit или сделать storybook
|
||||
- [ ] Слаги инструментов (`*-png`) — **развести `id` и адрес страницы.**
|
||||
Инструменты работают с любым форматом, но называются `crop-png`,
|
||||
`resize-png`; имя в коде, пайплайнах и i18n должно быть `crop`, `resize`,
|
||||
а адрес и имя скачиваемого файла — прежними (`/tools/crop-png` →
|
||||
`crop-png.png`). Решение и его SEO-последствия: `docs/plan-seo.md` §0;
|
||||
текущее состояние (поля `slug` нет, роут — `tools/[id]`) —
|
||||
`docs/architecture.md`, раздел 3. Порядок работ: 1. добавить `slug` в
|
||||
`ToolEntry` + `toolSlug()` / `getToolBySlug()` в `registry/index.ts`,
|
||||
проверку уникальности `slug` в `registry.test.ts`; 2. перевести
|
||||
`routes/tools/[id]` → `[slug]` (`entries()`, `load()`, `+page.svelte`),
|
||||
`ToolCard`, крошку в `+layout.svelte` и имя файла download в
|
||||
`SchemaToolView` на `toolSlug(tool)`; старые адреса продолжают работать,
|
||||
редиректы не нужны; 3. переименовать `id` волнами по группам реестра
|
||||
(`convert`, `alpha`, `color`, `geometry`, `filters`, `analyze`,
|
||||
`generate`, `text`); в каждой волне — `registry/<group>.ts`, ключи
|
||||
`tools.*` в `en.ts`/`ru.ts`, `tool-icons.ts`, захардкоженные id в тестах и
|
||||
e2e (`smoke-i18n.test.ts`, `kit/+page.svelte`); 4. `docs/tools-map.md` при
|
||||
этом **не меняется**: карта перечисляет id, а они остаются прежними как
|
||||
адреса. Обратной совместимости не делаем: формат пайплайна ещё не
|
||||
устоялся, старые экспорты и записи в localStorage после переименования
|
||||
просто перестают открываться — это осознанно, миграция усложнила бы код
|
||||
ради пары пользователей.
|
||||
- [ ] придумать процесс редактирования и настройки pipeline - drag-n-drop,
|
||||
удаление, добавление инструментов. переход один инструмент - pipeline без
|
||||
перезагрузки
|
||||
- [ ] Слаги инструментов (`*-png`) — надо переименовть, они работают с любыми
|
||||
изображениями. Надо сделать отдельный интерфейс маппинга инструмент ->
|
||||
ссылка
|
||||
- [ ] `CatalogToolbar`: фильтры используют сырой `<button>`, а не `ui/Button` —
|
||||
единый контрол для всей библиотеки
|
||||
(`web/src/lib/components/CatalogToolbar.svelte`).
|
||||
@@ -99,17 +122,20 @@
|
||||
|
||||
## SEO/GEO
|
||||
|
||||
Общий план: `docs/plan-seo.md` (S0–S4). Старт возможен — пока только domain-free
|
||||
часть S1.
|
||||
Общий план: `docs/plan-seo.md` (S1–S4). Старт возможен — пока только domain-free
|
||||
часть S1. Полное содержание этапов не дублируется здесь, только идентификаторы.
|
||||
|
||||
- [ ] переименование бренда + домен: чек-лист в локальном плане
|
||||
`docs/plan-domain.local.md` (вне git, имя домена в репо не публикуется;
|
||||
план-seo §6, R1/R2)
|
||||
- [ ] переименование бренда + домен: чек-лист в локальном файле
|
||||
`plan-domain.local.md` (в git не входит, в клоне отсутствует; подробности
|
||||
и порядок — `docs/plan-seo.md` §6, R1/R2)
|
||||
- [ ] снять `noindex` со страниц инструментов, когда будут sitemap, canonical и
|
||||
контент (S1h; блокирует индексацию всего сайта) — `docs/plan-seo.md` §4
|
||||
- [ ] download с селектором формата PNG/JPG/WebP/BMP для всех инструментов (райз
|
||||
№7 → S1f)
|
||||
- [ ] форматные страницы (`crop-jpeg`, …) — только по вордстату с уникальным
|
||||
контентом; редиректы только для опечаток; клонов с canonical не делать
|
||||
(план §5)
|
||||
«Download» → S1f)
|
||||
- [ ] форматные страницы по конкурентным слагам (`crop-jpeg`, …) — только по
|
||||
вордстату с уникальным контентом; редиректы только для опечаток и
|
||||
переименований уже опубликованных страниц; клонов с canonical не делать
|
||||
(`docs/plan-seo.md` §5)
|
||||
- [ ] sitemap.xml из реестра + `Sitemap:` в robots.txt (S1b/S1c)
|
||||
- [ ] Seo.svelte: canonical + OG-теги, per-tool OG-картинки (S1d/S1e)
|
||||
- [ ] контент страницы инструмента: seo-блок в словарях, H1/how-to/FAQ/related
|
||||
@@ -119,12 +145,14 @@
|
||||
|
||||
## Платформа (API/PWA)
|
||||
|
||||
Общий план: `docs/plan-platform.md`; WASM-ядро и CLI уже в `docs/roadmap.md`
|
||||
(фазы 3–8) — здесь только новые треки.
|
||||
Общий план: `docs/plan-platform.md`; WASM-ядро, эталоны и CLI —
|
||||
`docs/roadmap.md` (фазы 2–8). Здесь только треки, которых нет в roadmap.
|
||||
|
||||
- [ ] PWA: manifest + service worker, оффлайн-режим, установка (после S1)
|
||||
- [ ] API: спайк серверного ядра (нативный Rust vs edge-wasm), ключи, free tier
|
||||
(после wasm-фаз 5–7)
|
||||
- [ ] Шрифты: лицензирование (F1–F4) и DOM-free рендер (F5–F6) — до фазы 8
|
||||
(`docs/plan-platform.md` §4)
|
||||
|
||||
## Качество production UI
|
||||
|
||||
@@ -137,6 +165,10 @@
|
||||
|
||||
## Идеи
|
||||
|
||||
Идеи 1–5 и 15 относятся к **пайплайну (цепочкам шагов)**, которого в коде пока
|
||||
нет: это фаза 11 `docs/roadmap.md`. Идеи 6–14 — к существующим инструментам и
|
||||
UI. Оценки (S/M/L) — оценка труда, не приоритет.
|
||||
|
||||
1. **Менять первый инструмент цепочки** — оценка M. Сейчас базовый инструмент
|
||||
зафиксирован при открытии страницы; хочется заменить его на другой без
|
||||
разборки цепочки: шаги и их параметры сохраняются, вход перечитывается.
|
||||
@@ -149,8 +181,9 @@
|
||||
|
||||
3. **Несколько цепочек: сохранение и загрузка** — оценка M. Именованные цепочки
|
||||
в localStorage (список, создать/переименовать/удалить), быстрое переключение.
|
||||
Формат экспортного JSON расширить опциональным полем имени, старые файлы
|
||||
читаются как безымянные.
|
||||
Формат экспортного JSON расширить опциональным полем имени; обратной
|
||||
совместимости со старыми выгрузками не делаем (см. задачу про `id`/`slug`
|
||||
выше — формат ещё не устоялся).
|
||||
|
||||
4. **Batch-обработка архивов выбранным chain** — оценка L. Вход — zip-архив
|
||||
картинок (распаковка в браузере), прогон текущего или выбранного сохранённого
|
||||
@@ -164,16 +197,15 @@
|
||||
поднимается в выдаче поиска (бонус к popularity при скоринге).
|
||||
|
||||
6. **Конвертация формата — НЕ отдельный инструмент, а выбор в кнопке Download**
|
||||
— оценка M/L. Инструменты `convert-png-to-jpg` / `convert-png-to-webp` (и др.
|
||||
форматы) не должны жить как самостоятельные инструменты цепочки. Механическая
|
||||
часть уже есть: `ToolEntry.output` (`OutputFormat`: mime/ext/qualityParamId)
|
||||
и кодирование по нему в `SchemaToolView.download()` — `docs/architecture.md`,
|
||||
раздел 3. Осталось (UX-райз «Download»):
|
||||
- выбор формата/качества прямо в кнопке/диалоге Download для любого
|
||||
результата (не отдельными конвертерами);
|
||||
— оценка M/L. Решение и продуктовое обоснование: `docs/plan-seo.md` §0 и
|
||||
задача **S1f**; почему селектор не заменяет существующие convert-страницы —
|
||||
`docs/plan-seo.md` §5. Механическая часть уже есть: `ToolEntry.output`
|
||||
(`OutputFormat`: mime/ext/qualityParamId) и кодирование по нему в
|
||||
`SchemaToolView.download()` — `docs/architecture.md`, раздел 3. Что осталось
|
||||
сверх S1f:
|
||||
- экспорт с лимитом размера → закрывает отложенный `reduce-to-size-png`
|
||||
(целевой KB, бинарный поиск по k из квантования). Проработать вместе с
|
||||
достройкой download-флоу нового UI.
|
||||
достройкой download-флоу.
|
||||
|
||||
7. **`watermark-image-png` (водяной знак картинкой)** — оценка M. Нужна
|
||||
overlay-механика: второй источник изображения (getOverlay/store), параметры
|
||||
@@ -208,20 +240,19 @@
|
||||
а `--color-danger` в light-теме на границе: после завершения редизайна
|
||||
вернуть порог 4.5 и перепроверить оба токена.
|
||||
|
||||
14. **Отображение маски для инструментов (runMask/preview-mask)** — придумать и
|
||||
реализовать процесс показа маски результата. **Контекст:** в старом
|
||||
контракте поле `preview` у `remove-color`/`remove-background` показывало
|
||||
быстрый ч/б превью-маску (`colorMask`/`backgroundMaskPreview`) того, что
|
||||
будет вырезано. При миграции на единый `run(ctx)` поле убрано (шаг 8,
|
||||
`web/src/lib/registry/alpha.ts`) — новый executor и schema-превью зовут
|
||||
`run()` напрямую, маска пока не нужна. **Идея 1:** дать каждому `run`
|
||||
возможность вернуть (или инструмент — объявить) `runMask` — ч/б изображение
|
||||
маски изменений либо иной подход (отдельное поле `mask` + функция маски,
|
||||
второй результат, overlay). Оценка — на усмотрение при проработке (S/M).
|
||||
Пока живём без неё. **Идея 2:** каждый `run` возвращает два изображения —
|
||||
для отображения (показывает маску или сам результат) и для передачи в
|
||||
следующий инструмент (всегда результат). Оценка — на усмотрение при
|
||||
проработке (S/M). Пока живём без неё.
|
||||
14. **Отображение маски для инструментов (`runMask`)** — придумать и реализовать
|
||||
процесс показа маски результата. **Контекст:** удалённый UI-v1 умел
|
||||
показывать ч/б превью-маску «что будет вырезано» отдельным полем; при
|
||||
переходе на единый `run(ctx)` этого поля не осталось — executor и
|
||||
schema-превью зовут `run()` напрямую, маска не возвращается. **Идея 1:**
|
||||
дать каждому `run` возможность вернуть (или инструмент — объявить) `runMask`
|
||||
— ч/б изображение маски изменений либо иной подход (отдельное поле `mask` +
|
||||
функция маски, второй результат, overlay). Оценка — на усмотрение при
|
||||
проработке (S/M). Пока живём без неё. **Идея 2:** каждый `run` возвращает
|
||||
два изображения — для отображения (показывает маску или сам результат) и для
|
||||
передачи в следующий инструмент (всегда результат). Оценка — на усмотрение
|
||||
при проработке (S/M). Пока живём без неё. Первые кандидаты на маску:
|
||||
`remove-color-from-png`, `remove-background-png`.
|
||||
|
||||
15. **Набор файлов в пайплайне: 1 → many не должен быть терминальным** — оценка
|
||||
L. **Сейчас** `execute` принимает один `source: PixelImage | undefined` и
|
||||
|
||||
@@ -2,14 +2,24 @@
|
||||
|
||||
> Статус: **draft** — на ревью.
|
||||
>
|
||||
> Источник: `docs/tools-audit.md`. Этот документ разбивает аудит на конкретные
|
||||
> шаги, группирует по типу работы и фиксирует решения. Порядок и правила
|
||||
> проверки — `docs/quality-gates.md`; точки касания при добавлении вида поля —
|
||||
> `docs/architecture.md`, раздел 4.
|
||||
> Находки — `docs/tools-audit.md` (что плохо, с датой прохода). Этот документ
|
||||
> задаёт **порядок правок** полей и раскладки волнами; сами задачи — в
|
||||
> `docs/backlog.md`. Проверки и гейты — `docs/quality-gates.md`; точки касания
|
||||
> при добавлении вида поля — `docs/architecture.md`, раздел 4.
|
||||
>
|
||||
> **Ids инструментов в таблицах волны B сверены с реестром.** Названия из
|
||||
> `tools-audit.md` вида `png-to-hsl` или `color-wheel-generator` в реестре нет
|
||||
> (`hsl`, `color-wheel-png`).
|
||||
> **Идентификаторы шагов — `FA-*` (fields-audit).** Буквы `C*`/`Q*` в других
|
||||
> документах относятся к архивным волнам редизайна и здесь не используются.
|
||||
>
|
||||
> **Как читать волны.** Шаг с пометкой «реализовано» — закрытый, его правку
|
||||
> можно не повторять; шаг без пометки — работа. Пометка стоит в первом абзаце
|
||||
> шага, а не в заголовке, чтобы не потерять её при чтении оглавления. Задачи,
|
||||
> которые в бэклоге, здесь не дублируются: этот документ задаёт **порядок**,
|
||||
> backlog — **учёт** (`docs/backlog.md`).
|
||||
>
|
||||
> **Ids инструментов сверены с реестром** (`web/src/lib/registry/`) для шагов
|
||||
> FA-B и волн C–E. Названия из `docs/tools-audit.md` вида `png-to-hsl`,
|
||||
> `color-wheel-generator`, `webp-to-png` в реестре отсутствуют (реальные —
|
||||
> `hsl`, `color-wheel-png`): при переносе пункта аудита в шаг id сверять.
|
||||
|
||||
## 0. Ключевое решение: select → buttons
|
||||
|
||||
@@ -87,7 +97,7 @@ buttons: <V extends string>(s: Omit<ButtonsSpec<V>, "kind">): Field<V> => ({
|
||||
|
||||
## 1. Волна A — инфраструктура (1 коммит)
|
||||
|
||||
### Шаг A1. Новый тип `buttons` в схеме
|
||||
### FA-A1. Новый тип `buttons` в схеме
|
||||
|
||||
Файлы:
|
||||
|
||||
@@ -96,7 +106,7 @@ buttons: <V extends string>(s: Omit<ButtonsSpec<V>, "kind">): Field<V> => ({
|
||||
- `web/src/lib/components/fields/schema/ButtonsControl.svelte` — новый компонент
|
||||
- `web/src/lib/components/SchemaFields.svelte` — импорт + запись в `FIELDS`
|
||||
|
||||
### Шаг A2. Seed → input + randomize
|
||||
### FA-A2. Seed → input + randomize
|
||||
|
||||
Для `field.number()` с `kind: "seed"` (или новый подтип `field.seed()`) —
|
||||
рендерить поле ввода + кнопку «Random» вместо range slider.
|
||||
@@ -122,7 +132,7 @@ buttons: <V extends string>(s: Omit<ButtonsSpec<V>, "kind">): Field<V> => ({
|
||||
Каждый шаг — один коммит, < 500 строк. Меняем `field.select()` →
|
||||
`field.buttons()` в перечисленных ниже схемах.
|
||||
|
||||
### B1. geometry.ts
|
||||
### FA-B1. geometry.ts
|
||||
|
||||
| Инструмент | Поле | Опции |
|
||||
| ----------------------- | -------- | ----------------------------------------- |
|
||||
@@ -138,7 +148,7 @@ buttons: <V extends string>(s: Omit<ButtonsSpec<V>, "kind">): Field<V> => ({
|
||||
`field.select()`, т.к. 9 кнопок в ряд не поместятся. Аналогично `position9` (уже
|
||||
отдельный компонент `PositionControl`).
|
||||
|
||||
### B2. color.ts
|
||||
### FA-B2. color.ts
|
||||
|
||||
| Инструмент | Поле | Опции |
|
||||
| ------------------------ | --------- | ------------------------------------ |
|
||||
@@ -150,7 +160,7 @@ buttons: <V extends string>(s: Omit<ButtonsSpec<V>, "kind">): Field<V> => ({
|
||||
**Исключение:** channel spaces (hsl/hsv/... component) — 3-6 опций, но это
|
||||
динамические инструменты, оставить `select`.
|
||||
|
||||
### B3. analyze.ts
|
||||
### FA-B3. analyze.ts
|
||||
|
||||
| Инструмент | Поле | Опции |
|
||||
| ------------------------- | ---- | ------------------ |
|
||||
@@ -162,13 +172,13 @@ buttons: <V extends string>(s: Omit<ButtonsSpec<V>, "kind">): Field<V> => ({
|
||||
|
||||
Все используют общий `maskBaseFields` — правка в одном месте.
|
||||
|
||||
### B4. filters.ts
|
||||
### FA-B4. filters.ts
|
||||
|
||||
| Инструмент | Поле | Опции |
|
||||
| ------------- | ---- | ------------ |
|
||||
| add-noise-png | mode | mono / color |
|
||||
|
||||
### B5. generate.ts
|
||||
### FA-B5. generate.ts
|
||||
|
||||
| Инструмент | Поле | Опции |
|
||||
| ------------------ | ------------ | --------------------- |
|
||||
@@ -187,14 +197,15 @@ buttons: <V extends string>(s: Omit<ButtonsSpec<V>, "kind">): Field<V> => ({
|
||||
|
||||
## 3. Волна C — обязательные фиксы (баги + дефолты)
|
||||
|
||||
### C1. Source-aware defaults для dimension-полей
|
||||
### FA-C1. Source-aware defaults для dimension-полей — реализовано
|
||||
|
||||
Source-aware defaults реализованы: общий resolver в `registry-schema`
|
||||
(`applySourceDefaults`). Для `resize-png` и `crop-png` дефолт после загрузки
|
||||
равен размерам текущего source; до загрузки используется положительный fallback
|
||||
`1×1`. Один-sided `0` у resize сохраняет режим auto.
|
||||
`1×1`. Один-sided `0` у resize сохраняет режим auto. Проверено тестом
|
||||
`registry.test.ts` («resize и crop получают размеры текущего source»).
|
||||
|
||||
### C2. symmetric-copy-png: "keep side" не работает
|
||||
### FA-C2. symmetric-copy-png: "keep side" не работает
|
||||
|
||||
Баг в `run()`: `keepSide` не учитывает `axis`. При `axis: "vertical"` (удвоение
|
||||
ширины) работают только `left`/`right`, `top`/`bottom` не имеют смысла. Нужно:
|
||||
@@ -205,11 +216,12 @@ Source-aware defaults реализованы: общий resolver в `registry-s
|
||||
значение. Пока достаточно в `run()`: если `axis === "vertical"` и `keepSide` ∈
|
||||
{top, bottom} → заменить на `left`.
|
||||
|
||||
### C3. Source-aware bounds (отдельный следующий шаг)
|
||||
### FA-C3. Source-aware bounds (отдельный следующий шаг) — не начат
|
||||
|
||||
Реализованы только defaults dimension-полей через `SchemaContext` и
|
||||
`applySourceDefaults`. Динамические min/max для crop x/y, shift и resize
|
||||
остаются отдельной задачей: bounds не следует смешивать с default resolver.
|
||||
Source-aware **defaults** реализованы через `SchemaContext` и
|
||||
`applySourceDefaults`. Динамические min/max для crop x/y, shift и resize не
|
||||
реализованы и остаются отдельной задачей: bounds не следует смешивать с default
|
||||
resolver.
|
||||
|
||||
**Что нужно спроектировать и завести:**
|
||||
|
||||
@@ -240,40 +252,43 @@ Source-aware defaults реализованы: общий resolver в `registry-s
|
||||
|
||||
Это отдельный рефакторинг; source-aware defaults не заменяют bounds.
|
||||
|
||||
### C4. verify-is-png: неверное название
|
||||
### FA-C4. verify-is-png: неверное название — решение принято, шаг не выполнен
|
||||
|
||||
Проверяет текст, не PNG-файл. Переименовать:
|
||||
Проверяет текст, не PNG-файл. Варианты:
|
||||
|
||||
- `id` → `verify-is-png-data` (или оставить для обратной совместимости)
|
||||
- `title` → "Verify PNG data"
|
||||
- `description` → уточнить
|
||||
- `title` → "Verify PNG data", `description` → уточнить; `id` оставить — он же
|
||||
адрес страницы, переименование ломает ссылку
|
||||
- либо полное переименование `id` → `verify-is-png-data` с редиректом со старого
|
||||
URL (нужен вордстат, иначе страница выпадает из индекса)
|
||||
|
||||
**Решение:** поменять title и description, id не трогать (url-dependent).
|
||||
**Решение:** поменять title и description, id не трогать. Переименование `id`
|
||||
затрагивает адрес — с ним связана задача разведения `id`/`slug`
|
||||
(`docs/backlog.md`), поэтому в одиночку не делается.
|
||||
|
||||
---
|
||||
|
||||
## 4. Волна D — обязательные фиксы (новые параметры)
|
||||
|
||||
### D1. remove-alpha-channel-png: выбор цвета фона
|
||||
### FA-D1. remove-alpha-channel-png: выбор цвета фона
|
||||
|
||||
Сейчас захардкожен `#ffffff`. Добавить `field.color({ default: "#ffffff" })`.
|
||||
|
||||
Файл: `alpha.ts`, схема `removeAlphaChannelSchema`.
|
||||
|
||||
### D2. extract-alpha-mask-png: галочка «инвертировать»
|
||||
### FA-D2. extract-alpha-mask-png: галочка «инвертировать»
|
||||
|
||||
Добавить `field.checkbox({ label: "fields.invertMask", default: false })`. В
|
||||
`run()`: если `invert`, вызвать `invertAlpha()` после `extractAlphaMask()`.
|
||||
|
||||
Файл: `alpha.ts`.
|
||||
|
||||
### D3. change-canvas-size-png: position9 вместо select
|
||||
### FA-D3. change-canvas-size-png: position9 вместо select
|
||||
|
||||
`anchor` сейчас `field.select()` с 9 опциями → `field.position9()`.
|
||||
|
||||
Файл: `geometry.ts`. Уже есть `PositionControl`.
|
||||
|
||||
### D4. Generators: добавить height
|
||||
### FA-D4. Generators: добавить height
|
||||
|
||||
Инструменты без `size`/`height`:
|
||||
|
||||
@@ -287,7 +302,7 @@ Source-aware defaults реализованы: общий resolver в `registry-s
|
||||
|
||||
Файл: `generate.ts`.
|
||||
|
||||
### D5. add-border-png: прозрачность цвета
|
||||
### FA-D5. add-border-png: прозрачность цвета
|
||||
|
||||
Текущий `field.color()` не поддерживает alpha. Пока что: оставить как есть
|
||||
(прозрачность не поддерживается нативным color picker). Отметить в аудите как
|
||||
@@ -297,29 +312,29 @@ blocked.
|
||||
|
||||
## 5. Волна E — полировка (некритично)
|
||||
|
||||
### E1. Плейсхолдер "нет параметров"
|
||||
### FA-E1. Плейсхолдер "нет параметров"
|
||||
|
||||
Инструменты с пустой схемой (`EmptyParams`) выглядят странно. Добавить
|
||||
`EmptyState` или подсказку "No parameters — click Run".
|
||||
|
||||
Файл: `SchemaToolView.svelte` — показать подсказку если `schema.fields` пуст.
|
||||
|
||||
### E2. quantize-png: пресеты
|
||||
### FA-E2. quantize-png: пресеты
|
||||
|
||||
`field.slider` с max 64 → добавить пресеты (8, 16, 32, 64) как кнопки под
|
||||
слайдером. Либо `field.buttons()` с 4 опциями вместо слайдера.
|
||||
|
||||
### E3. trim-empty-space-png: 0-254 → проценты
|
||||
### FA-E3. trim-empty-space-png: 0-254 → проценты
|
||||
|
||||
Текущий `min: 0, max: 254` — нестандартно. Добавить переключатель px/% (аналог
|
||||
шага A1 — `variant: "alpha-threshold"` в NumberSpec).
|
||||
шага FA-A1 — `variant: "alpha-threshold"` в NumberSpec).
|
||||
|
||||
### E4. add-text-png: plate offset
|
||||
### FA-E4. add-text-png: plate offset
|
||||
|
||||
Plate снизу имеет большой отступ — добавить галочку "compact plate" или
|
||||
уменьшить дефолтный padding.
|
||||
|
||||
### E5. emoji-to-png: выбор эмодзи
|
||||
### FA-E5. emoji-to-png: выбор эмодзи
|
||||
|
||||
Поле `text` → добавить группу часто используемых эмодзи как кнопки- пресеты над
|
||||
полем ввода.
|
||||
@@ -328,12 +343,11 @@ Plate снизу имеет большой отступ — добавить г
|
||||
|
||||
## 6. Верификация
|
||||
|
||||
После каждой волны:
|
||||
|
||||
1. `pnpm --dir web build` — без ошибок
|
||||
2. `pnpm --dir web exec svelte-check --tsconfig ./tsconfig.json` — без ошибок
|
||||
3. `pnpm --dir web lint` — без ошибок (кроме ожидаемых долгов)
|
||||
4. Ручная проверка в браузере: параметры рендерятся, инструменты работают
|
||||
Проверки и их минимальный набор — `docs/quality-gates.md`, раздел «Быстрые
|
||||
подмножества»; правила e2e — `docs/testing-strategy.md`. Для волн с изменением
|
||||
пользовательского потока (новый контрол, новая раскладка) обязателен ручной
|
||||
смоук в браузере: часть находок — про воспринимаемость, которую не ловит ни один
|
||||
гейт. `pnpm verify` — перед ревью волны.
|
||||
|
||||
---
|
||||
|
||||
@@ -346,7 +360,8 @@ Plate снизу имеет большой отступ — добавить г
|
||||
|
||||
- **Pixel ↔ Percent** (circle-mask, square-mask, star-mask, wavy-mask — %→px,
|
||||
round-corners — px→%): нужен компонент переключателя единиц в слайдере.
|
||||
Отложен до волны E, т.к. требует правки `RangeControl` + всех схем.
|
||||
Требует правки `RangeControl` + всех схем — в волны A–E не входит, нужен
|
||||
отдельный этап.
|
||||
- **Smoothing checkbox** для масок (circle, square, star, wavy, round-corners,
|
||||
invert-alpha): нужен `field.checkbox("smoothing")` + правка core (shapes.ts,
|
||||
alpha). Отложен — требует визуального тестирования.
|
||||
|
||||
+27
-18
@@ -1,18 +1,19 @@
|
||||
# План: платформа — общее ядро, API и PWA
|
||||
|
||||
> **Статус (2026-09-09):** план-набросок. WASM-ядро и CLI уже описаны в
|
||||
> `docs/roadmap.md` (фазы 3–8) и здесь не дублируются — этот план добавляет то,
|
||||
> чего в roadmap нет: **API** (фаза 9, возможная монетизация) и **PWA** (фаза
|
||||
> 10, оффлайн + установка). Оба стартуют после wasm-фаз; PWA не ждёт API и может
|
||||
> идти раньше.
|
||||
> **Статус: active.** Этот план добавляет к `docs/roadmap.md` то, чего там нет:
|
||||
> **API** (фаза 9, возможная монетизация) и **PWA** (фаза 10, оффлайн +
|
||||
> установка), плюс шрифты (раздел 4 — частично относится к фазе 8, поэтому
|
||||
> решения-phase 8 здесь дублируются). WASM-ядро, эталоны и CLI — фазы 2–8
|
||||
> `docs/roadmap.md`, они здесь не пересказываются. Оба трека стартуют после
|
||||
> wasm-фаз; PWA не ждёт API и может идти раньше.
|
||||
|
||||
## 0. Решения
|
||||
|
||||
- **Одно ядро — три потребителя.** Rust-крейт `core/` (roadmap фаза 3) даёт:
|
||||
- **Одно ядро — три потребителя.** Rust-крейт `core/` (roadmap фаза 4) даёт:
|
||||
wasm-пакет для сайта (фаза 5), нативный CLI (фаза 6), серверный слой API (фаза
|
||||
9). Единый контракт — JSON-пайплайн `{tool, params}` (заложен в roadmap §0);
|
||||
эталонные файлы и harness (фазы 2, 4, 7) гарантируют одинаковый результат во
|
||||
всех рантаймах.
|
||||
9). Единый контракт — JSON-пайплайн `{tool, params}` (заложен в roadmap §0,
|
||||
реализуется в фазе 11); эталонные файлы и harness (фазы 2, 3, 7) гарантируют
|
||||
одинаковый результат во всех рантаймах.
|
||||
- **API — нативный Rust на сервере, не wasm.** В браузере wasm нужен ради
|
||||
доставки и песочницы; на сервере крейт линкуется напрямую — быстрее и проще.
|
||||
Исключение — edge-воркеры (см. спайк ниже): там исполняется тот же
|
||||
@@ -29,7 +30,7 @@
|
||||
- [ ] **[A1]** Спайк сервера: (а) axum/actix + нативный `core/` на VPS против
|
||||
(б) wasm-артефакт фазы 5 на edge (CF Workers). Сравнить: стоимость, лимиты
|
||||
памяти/времени на больших картинках, сложность деплоя и rate-limiting.
|
||||
Решение зафиксировать здесь.
|
||||
Решение записать в §0 этого файла.
|
||||
- [ ] **[A2]** Контракт: `POST /v1/process` — multipart (image) + JSON-пайплайн
|
||||
→ изображение/архив; лимиты размера, коды ошибок; OpenAPI-спека.
|
||||
- [ ] **[A3]** Ключи и лимиты: Bearer-ключи, free tier (N запросов/мес), rate
|
||||
@@ -63,7 +64,8 @@
|
||||
|
||||
## 3. Порядок и связи
|
||||
|
||||
- WASM-ядро, эталоны, CLI — `docs/roadmap.md` фазы 2–8 (не дублируются).
|
||||
- WASM-ядро, эталоны, CLI — `docs/roadmap.md` фазы 2–8; пайплайн как продуктовая
|
||||
фича — фаза 11 того же документа.
|
||||
- PWA — после S1 `plan-seo.md`; API — после фаз 5–7.
|
||||
- Синергия с дистрибуцией: «works offline, installable» и публичный API —
|
||||
аргументы для Product Hunt / alternative.to / GitHub (plan-seo S4c) и для
|
||||
@@ -71,11 +73,16 @@
|
||||
|
||||
## 4. Шрифты: лицензирование и DOM-free рендер текста
|
||||
|
||||
Текст — единственный класс операций, который roadmap (фаза 8) держит в TS из-за
|
||||
canvas. Для CLI/API его всё равно придётся рендерить в Rust, поэтому решение:
|
||||
**перевести text-инструменты в ядро** (в браузере canvas-рендер остаётся
|
||||
TS-фолбэком для A/B). Два вопроса — легальность шрифтов в дистрибутиве и рендер
|
||||
без DOM.
|
||||
Текст — один из canvas-зависимых классов, которые roadmap (фаза 8) держит в TS
|
||||
(там их четыре: текст, градиенты, штампы, `ctx.filter`); для текста выбран курс
|
||||
на реимплементацию, остальные классы не переносятся. Для CLI/API текст всё равно
|
||||
придётся рендерить в Rust, поэтому решение: **перевести text-инструменты в
|
||||
ядро** (в браузере canvas-рендер остаётся TS-фолбэком для A/B). Два вопроса —
|
||||
легальность шрифтов в дистрибутиве и рендер без DOM.
|
||||
|
||||
**Когда начинать:** блока F1–F4 нет — они не привязаны к фазе roadmap, но F5/F6
|
||||
без них бессмысленны (нечего рендерить). Реализуется всё это в фазе 8, поэтому
|
||||
лицензирование стоит закрыть до неё, а не после.
|
||||
|
||||
### Лицензирование (решить до выбора шрифтов, backlog «Font Style»)
|
||||
|
||||
@@ -90,8 +97,10 @@ TS-фолбэком для A/B). Два вопроса — легальност
|
||||
право класть TTF в дистрибутив CLI/API — у OFL/Apache таких ограничений
|
||||
нет, у прочих «free»-лицензий бывают.
|
||||
- [ ] **[F3]** Паспорт шрифта в `core/fonts/<name>/` (или эквивалент): файл(ы),
|
||||
LICENSE, источник, версия, хеш; CI-проверка «у каждого файла шрифта есть
|
||||
паспорт». Браузер грузит шрифты из бандла, не из ОС и не с
|
||||
LICENSE, источник, версия, хеш; **новый гейт** «у каждого файла шрифта
|
||||
есть паспорт» — его ещё нет, проектируется здесь и добавляется в
|
||||
`pnpm verify` по правилам `docs/quality-gates.md` (решение о fail-режиме —
|
||||
`docs/decisions.md`). Браузер грузит шрифты из бандла, не из ОС и не с
|
||||
`fonts.googleapis.com`-CDN (сторонний запрос = утечка IP пользователя,
|
||||
противоречит «no tracking»), — одинаковый результат у всех пользователей и
|
||||
во всех рантаймах.
|
||||
|
||||
+100
-64
@@ -1,38 +1,41 @@
|
||||
# План: SEO + GEO — индексация и конкуренция в image tools
|
||||
|
||||
> **Статус (2026-09-11; уточнено 2026-09-27):** план к выполнению; маршруты
|
||||
> инструментов — корневые (`/tools/[id]`). **`noindex` на страницах инструментов
|
||||
> не выставляется** — снимать нечего, каталог открыт для индексации; запрет
|
||||
> индексации держится только на служебных путях (`robots.txt`). Задачи S1
|
||||
> разбиты на domain-free (можно делать параллельно с покупкой домена) и
|
||||
> domain-required (после покупки и настройки домена).
|
||||
> **Статус: active.** Адреса страниц инструментов — `/tools/<id>` (`crop-png` и
|
||||
> подобные), каталог — `/list-tools`; отдельного поля `slug` в реестре пока нет,
|
||||
> `id` и есть адрес.
|
||||
>
|
||||
> **Цель:** каждая страница инструмента — индексируемый лендинг под конкретный
|
||||
> интент («crop png», «make png transparent», «jpg to png»…), конкуренция с
|
||||
> onlinepngtools.com за ядро запросов + попадание в ответы генеративных систем
|
||||
> (ChatGPT, Perplexity, AI Overviews) — GEO.
|
||||
> **`noindex, nofollow` выставлен на каждой странице инструмента**
|
||||
> (`web/src/routes/tools/[id]/+page.svelte`) — осознанно, до появления
|
||||
> уникального контента (S2). Пока он стоит, сайт не индексируется вообще, и
|
||||
> **S1b–S1e (sitemap, `robots.txt` с `Sitemap:`, canonical, OG) не дают
|
||||
> эффекта**: их имеет смысл делать вместе со снятием `noindex`, а не вместо
|
||||
> него. Условие снятия и порядок — §4, S1.
|
||||
>
|
||||
> Задача: «для каждого инструмента — своя страница, и можно реально
|
||||
> конкурировать с onlinepngtools».
|
||||
> **Цель:** страница каждого инструмента — индексируемый лендинг под конкретный
|
||||
> интент («crop png», «make png transparent», «jpg to png»…) плюс попадание в
|
||||
> ответы генеративных систем (ChatGPT, Perplexity, AI Overviews) — GEO.
|
||||
>
|
||||
> **Позиционирование (уточнено 2026-09-09):** сайт — не «PNG-инструменты», а
|
||||
> **image tools с PNG-ядром**: любой инструмент работает с PNG/JPG/WebP/GIF/BMP
|
||||
> на входе и отдаёт результат в PNG/JPG/WebP/BMP через селектор в кнопке
|
||||
> Download. Дифференциатор — прозрачность/альфа/фон и no-upload-приватность.
|
||||
> Референс-конкурент по ядру — onlinepngtools.com (см. §2). Долгосрочно ядро
|
||||
> переезжает на wasm и обрастает API + PWA (оффлайн/установка) —
|
||||
> `docs/roadmap.md` фазы 3–10, `docs/plan-platform.md`; для SEO это аргументы
|
||||
> дистрибуции (S4c) и GEO-факты, схему сайта они не меняют.
|
||||
> **Позиционирование:** не «PNG-инструменты», а **image tools с PNG-ядром** —
|
||||
> любой инструмент принимает PNG/JPG/WebP/GIF/BMP и отдаёт результат в
|
||||
> PNG/JPG/WebP/BMP через селектор в кнопке Download. Дифференциатор —
|
||||
> прозрачность/альфа/фон и no-upload-приватность. Референс-конкурент по ядру —
|
||||
> onlinepngtools.com (§2). Долгосрочно ядро переезжает на wasm и обрастает API +
|
||||
> PWA (`docs/plan-platform.md`); для SEO это аргументы дистрибуции и GEO-факты,
|
||||
> схему сайта они не меняют.
|
||||
>
|
||||
> **Слаги в этом документе:** `jpg-to-png`, `crop-jpeg` и подобное — интенты и
|
||||
> URL конкурента, а не id наших инструментов. Наши id — в `docs/tools-map.md`; в
|
||||
> тексте ниже реальные id инструментов приводятся в бэктиках.
|
||||
|
||||
## 0. Решения
|
||||
|
||||
- **Бренд: переименовать до индексации** (новое имя и домен сознательно не
|
||||
записаны в этом репозитории — см. §6 и локальный план
|
||||
`docs/plan-domain.local.md`, исключён из git: выбранное имя не должно утекать
|
||||
в историю коммитов до покупки домена). Легенда честная: «started as PNG tools,
|
||||
now all formats» — это контент-плюс, а не баг. Названия инструментов
|
||||
(`crop-png`, …) и localStorage-ключи не переименовывать (см. решение про слаги
|
||||
ниже). Точка истины по бренду: константа в i18n + README; хардкод в
|
||||
записаны в этом репозитории — чек-лист в локальном файле
|
||||
`plan-domain.local.md`, см. §6, он вне git: выбранное имя не должно утекать в
|
||||
историю коммитов до покупки домена). Легенда честная: «started as PNG tools,
|
||||
now all formats» — это контент-плюс, а не баг. Адреса страниц
|
||||
(`/tools/crop-png`) и localStorage-ключи не переименовывать (см. решение про
|
||||
`slug` ниже). Точка истины по бренду: константа в i18n + README; хардкод в
|
||||
компонентах недопустим.
|
||||
- **Форматы: формат = выбор в UI, не отдельные страницы.** Download-кнопка
|
||||
каждого инструмента — с селектором PNG/JPG/WebP/BMP (+quality для lossy). Райз
|
||||
@@ -40,10 +43,19 @@
|
||||
есть (`ToolEntry.output`), нужна как продуктовая основа «image
|
||||
tools»-позиционирования. В направлении «из формата в PNG» отдельных
|
||||
инструментов нет (загрузка принимает любой формат) — см. §5.
|
||||
- **Слаги инструментов `-png` сохраняются.** Они держат низкоконкурентные
|
||||
png-интенты; широкие интенты («crop image online») переносятся теми же
|
||||
страницами контентом: H1 «Crop image», текст «works with PNG, JPG, WebP».
|
||||
URL-алиасы без `-png` не создаются (канонический беспорядок).
|
||||
- **Адреса страниц `-png` сохраняются, внутренние имена — без.** Инструмент
|
||||
работает с любым форматом, поэтому его `id` не должен врать (`crop`, а не
|
||||
`crop-png`); png-интент держит адрес страницы и имя скачиваемого файла.
|
||||
Требуется поле `slug` в `ToolEntry` + переход `routes/tools/[id]` → `[slug]`.
|
||||
**Сейчас не реализовано:** в реестре `id: "crop-png"`, поля `slug` нет, роут —
|
||||
`tools/[id]/`; текущее состояние описано в `docs/architecture.md`, раздел 3.
|
||||
**Следствие для SEO: редиректы не нужны, canonical и sitemap не меняются,
|
||||
адреса остаются теми же** — поэтому это безопасная правка, которую можно
|
||||
делать до индексации, а не после. Побочная выгода: широкие интенты («crop
|
||||
image online») переносятся теми же страницами контентом — H1 «Crop image»,
|
||||
текст «works with PNG, JPG, WebP»; URL-алиасы без `-png` по-прежнему не
|
||||
создаются (канонический беспорядок). Порядок работ — `docs/backlog.md`, задача
|
||||
«Слаги инструментов (`*-png`) — развести `id` и адрес страницы».
|
||||
- **Индексируемая локаль — EN** (базовая; конкурент англоязычный). RU-версия —
|
||||
отдельно и позже: префикс `/ru/`, prerender обеих локалей, hreflang
|
||||
(сознательно отложено). Пока язык переключается на клиенте (localStorage) —
|
||||
@@ -53,10 +65,11 @@
|
||||
canonical и «корневость» страниц. Свой домен + корень. Текущий деплой — GitHub
|
||||
Pages (`.github/workflows/deploy.yml`); перенос на свой домен выполняется
|
||||
сменой origin в CI и `PUBLIC_SITE_ORIGIN`.
|
||||
- **URL-схема инструментов:** короткие плоские адреса. После C17 страницы
|
||||
инструментов находятся в корне на `/tools/[id]`, каталог — на `/list-tools`;
|
||||
текущую схему не менять без отдельного SEO-плана: после индексации
|
||||
переименование URL означает 301 и потерю позиций.
|
||||
- **URL-схема инструментов:** короткие плоские адреса — `/tools/<id>` для
|
||||
инструмента, `/list-tools` для каталога, где `<id>` берётся из записи реестра.
|
||||
Схему не менять без отдельного SEO-плана: после индексации переименование URL
|
||||
означает 301 и потерю позиций. Разделение `id`/`slug` (решение выше) адресов
|
||||
не меняет.
|
||||
- **Без GA и рекламных скриптов:** аналитика — self-hosted Plausible/Umami или
|
||||
Cloudflare Web Analytics. «No tracking» — часть позиционирования, её нельзя
|
||||
подрывать собственными скриптами.
|
||||
@@ -83,6 +96,7 @@
|
||||
|
||||
| Пробел | Где сейчас | Этап |
|
||||
| -------------------------------------------------- | ----------------------------------- | ---- |
|
||||
| `noindex` на страницах инструментов | стоит (`+page.svelte`) | S1 |
|
||||
| `sitemap.xml` | нет | S1 |
|
||||
| `robots.txt` без `Sitemap:` | allow-all (`web/static/robots.txt`) | S1 |
|
||||
| `rel=canonical` | нет | S1 |
|
||||
@@ -93,6 +107,10 @@
|
||||
| `llms.txt`, хабы категорий с текстом | нет | S3 |
|
||||
| GSC + приватная аналитика | нет | S4 |
|
||||
|
||||
Строка `noindex` — первая не потому, что её сложнее всего, а потому, что она
|
||||
блокирует всё остальное: пока тег стоит, остальные строки таблицы не дают
|
||||
трафика. Подробности и условие снятия — §4, S1.
|
||||
|
||||
## 2. Конкурент (onlinepngtools.com)
|
||||
|
||||
Снято с живого сайта (2026-09-09):
|
||||
@@ -141,6 +159,15 @@
|
||||
Разбито на две группы: **domain-free** (делаем параллельно с покупкой домена) и
|
||||
**domain-required** (после покупки и DNS-настройки).
|
||||
|
||||
- [ ] **[S1h]** Снять `noindex, nofollow` со страниц инструментов
|
||||
(`web/src/routes/tools/[id]/+page.svelte`) — заменить на
|
||||
`<meta name="robots" content="index, follow" />`. **Это первым
|
||||
обязательным шагом S1:** пока тег стоит, остальные задачи S1 готовят
|
||||
инфраструктуру, но не приводят трафик. Порядок: S1h ставится после
|
||||
S1b/S1c/S1d, иначе Google проиндексирует сайт без sitemap и canonical.
|
||||
Снимать `noindex` до готовности S2-контента осознанно не стоит: в индекс
|
||||
попадут тонкие страницы с одним title/description.
|
||||
|
||||
#### S1-free: без домена
|
||||
|
||||
- [ ] **[S1e]** OG-картинки per-tool: генерация PNG-карточек скриптом на
|
||||
@@ -150,12 +177,12 @@
|
||||
OG-картинки будут абсолютными и корректными.
|
||||
- [ ] **[S1f]** Download-кнопка с селектором формата PNG/JPG/WebP/BMP (+quality
|
||||
для lossy) для всех инструментов — механика `ToolEntry.output` уже есть
|
||||
(backlog №7, райз «выбор формата/качества»). Продуктовая основа «image
|
||||
tools»-позиционирования: без неё сайт остаётся PNG-сайтом. Вести
|
||||
(`docs/backlog.md`, «Идеи» №6, UX-райз «Download»). Продуктовая основа
|
||||
«image tools»-позиционирования: без неё сайт остаётся PNG-сайтом. Вести
|
||||
параллельно с текущими quality-задачами.
|
||||
- [ ] **[S1g]** Поведение конвертеров после S1f: не предлагать в меню форматов
|
||||
формат исходника (disable) — чтобы `convert-png-to-jpg` не мог сохранить в
|
||||
png. Существующие страницы не трогать: `jpg-to-png`, `convert-png-to-jpg`,
|
||||
png. Существующие страницы не трогать: `convert-png-to-jpg`,
|
||||
`convert-png-to-webp`, `png-to-bmp` остаются самостоятельными лендингами
|
||||
со своим контентом (§5).
|
||||
|
||||
@@ -177,9 +204,14 @@
|
||||
twitter:card. Подключить в root-layout. **Требует `PUBLIC_SITE_ORIGIN`**
|
||||
для абсолютных URL в canonical и OG:url.
|
||||
|
||||
> **Порядок S1:** S1e/S1f/S1g → покупка домена → S1a+S1b+S1c+S1d. Гейты: build
|
||||
> зелёный; в `build/` лежит корректный `sitemap.xml` со всеми инструментами;
|
||||
> canonical/OG присутствуют на всех страницах; e2e зелёные.
|
||||
> **Порядок S1:** S1e/S1f/S1g → покупка домена → S1a+S1b+S1c+S1d → **S1h**
|
||||
> (снятие `noindex`) → S2-контент. Гейты: `pnpm --dir web build` зелёный; в
|
||||
> `build/` лежит корректный `sitemap.xml` со всеми инструментами; canonical/OG
|
||||
> присутствуют на всех страницах; `noindex` отсутствует в собранном HTML
|
||||
> (`pnpm --dir web build` и проверка `build/tools/*/index.html`);
|
||||
> `pnpm --dir web test:e2e` — по правилам `docs/quality-gates.md`, в CI job
|
||||
> `e2e` non-blocking, поэтому его статус читается по отдельному job
|
||||
> (`docs/decisions.md`, раздел 10).
|
||||
|
||||
### S2. Контентный слой (параллельно quality-плану, волнами по категориям)
|
||||
|
||||
@@ -278,11 +310,13 @@
|
||||
|
||||
Три возможных механики — важно не перепутать:
|
||||
|
||||
1. **301-редирект `/crop-jpeg` → `/crop-png`** — законно только для опечаток и
|
||||
альтернативных написаний (`crop-jpg` → `crop-jpeg`) и переименований
|
||||
существующих страниц. Для самостоятельного запроса редирект — это отказ от
|
||||
страницы: Google перестаёт показывать редирект, ранжировать будет только
|
||||
цель. Как посадочная под «crop jpeg» не работает вообще.
|
||||
1. **301-редирект на существующую страницу** (`/crop-jpg` → `/crop-jpeg`, или
|
||||
`/crop-png` → новый слаг после переименования) — законно **только** для
|
||||
опечаток, альтернативных написаний и переименований **уже опубликованной**
|
||||
страницы: URL, которого никогда не было в индексе, редиректом не закрывается.
|
||||
Для самостоятельного запроса редирект — это отказ от страницы: Google
|
||||
перестаёт показывать редирект, ранжировать будет только цель. Как посадочная
|
||||
под «crop jpeg» не работает вообще.
|
||||
2. **Зеркало с canonical на `/crop-png`** — технически легально, но бесполезно:
|
||||
canonical-дубль в индекс не попадает, видимости не добавляет, а массовое
|
||||
производство таких зеркал выглядит как накрутка площади и при ошибке в
|
||||
@@ -307,9 +341,9 @@ with PNG, JPG, WebP» в тексте + селектор формата в Downl
|
||||
|
||||
- **«convert png to jpg»** — интент «страница-конвертер»: пользователь ищет
|
||||
конкретную пару форматов (лучшее качество конверсии, меньше ошибок), это
|
||||
готовые лендинги. Существующие (`jpg-to-png`, `png-to-jpg`, `png-to-webp`,
|
||||
`png-to-bmp`, `svg-to-png`, …) **оставить и наполнить контентом как все**:
|
||||
свой H1 («Convert PNG to JPG — free, no upload»), FAQ про
|
||||
готовые лендинги. Существующие (`convert-png-to-jpg`, `convert-png-to-webp`,
|
||||
`png-to-bmp`, `svg-to-png`, `base64-to-png`, …) **оставить и наполнить
|
||||
контентом как все**: свой H1 («Convert PNG to JPG — free, no upload»), FAQ про
|
||||
качество/прозрачность/фон, related. Селектор формата (S1f) их **не заменяет**
|
||||
— он про «уже открыл инструмент и хочу другой формат на выходе».
|
||||
- **«convert image» / «image converter»** — интент «швейцарский нож». Под него —
|
||||
@@ -331,24 +365,25 @@ with PNG, JPG, WebP» в тексте + селектор формата в Downl
|
||||
|
||||
## 6. Переименование бренда и домен (сделать до S1-domain, дёшево сейчас)
|
||||
|
||||
Новое имя и конкретный домен **не записаны в этом репозитории** — детальный
|
||||
чек-лист в `docs/plan-domain.local.md` (исключён из git через `.gitignore`),
|
||||
чтобы выбранное имя не попало в историю коммитов до покупки домена.
|
||||
Выбранное имя и домен **в этом репозитории не записаны**: детальный чек-лист
|
||||
лежит в локальном файле `plan-domain.local.md` рядом с этим планом, он исключён
|
||||
из git через `.gitignore`, чтобы имя не попало в историю коммитов до покупки
|
||||
домена. Агент, работающий из клона, этого файла не увидит — при необходимости
|
||||
попросить чек-лист у пользователя.
|
||||
|
||||
- [ ] **[R1]** Механический переезд бренда на новое имя по чек-листу из
|
||||
локального плана: GitHub-репо + `git remote set-url`, README,
|
||||
`package.json` (корень + `web/`), `TopBar.svelte` / `Footer.svelte`,
|
||||
`favicon.svg` (title в SVG), i18n-строки (`en.ts`, `ru.ts`: defaultTitle,
|
||||
pageTitle, metaDescription). НЕ трогать: слаги инструментов (`*-png`),
|
||||
ключи localStorage (`easy-png-tools:*` — или мигрировать отдельной
|
||||
мелочью), историю docs/archive.
|
||||
- [ ] **[R1]** Механический переезд бренда на новое имя. Список файлов и строк с
|
||||
хардкодом — в локальном плане (раздел «Где живёт бренд в коде»); здесь
|
||||
только SEO-ограничения. **НЕ трогать:** адреса страниц инструментов
|
||||
(`/tools/crop-png`), ключи localStorage (`easy-png-tools:*` — или
|
||||
мигрировать отдельной мелочью), историю `docs/archive`. Бренд должен стать
|
||||
i18n-константой, а не тремя независимыми хардкодами.
|
||||
- [ ] **[R2]** Купить выбранный домен (кандидаты и пробы — в локальном плане) и
|
||||
перевести прод на него без `BASE_PATH` (§0). Домен нужен до S1-domain:
|
||||
canonical и sitemap генерятся от `PUBLIC_SITE_ORIGIN`.
|
||||
- **Порядок:** R1+R2 можно делать параллельно с текущими quality-задачами (не
|
||||
блокируют). R2 (домен) — блокер только для S1-domain (canonical + sitemap +
|
||||
снятие `noindex`). После индексации переименование = переезд с 301, потеря
|
||||
позиций и ссылок — не делать.
|
||||
блокируют). R2 (домен) — блокер для S1-domain (canonical + sitemap), а
|
||||
S1-domain — блокер для S1h (снятие `noindex`). После индексации переименование
|
||||
= переезд с 301, потеря позиций и ссылок — не делать.
|
||||
|
||||
## 7. Что осознанно НЕ делаем
|
||||
|
||||
@@ -368,7 +403,8 @@ with PNG, JPG, WebP» в тексте + селектор формата в Downl
|
||||
|
||||
- `docs/tools-map.md` — карта «наш инструмент ↔ интент конкурента».
|
||||
- `docs/backlog.md` — пункты SEO/GEO в общем бэклоге.
|
||||
- `docs/roadmap.md` — фазы 2–8 (ядро TS→Rust/wasm, CLI); фазы 9–10 — API и PWA.
|
||||
- `docs/roadmap.md` — фазы 2–8 (ядро TS→Rust/wasm, CLI), фаза 11 (пайплайн);
|
||||
фазы 9–10 — API и PWA.
|
||||
- `docs/plan-platform.md` — планы API и PWA (общее ядро, оффлайн-режим).
|
||||
- `docs/plan-domain.local.md` — план переименования/домена; **в git не входит**
|
||||
(`.gitignore`), лежит только локально.
|
||||
- `plan-domain.local.md` — локальный чек-лист переименования и покупки домена;
|
||||
**в git не входит** (`.gitignore`), в клоне отсутствует.
|
||||
|
||||
+6
-11
@@ -19,7 +19,8 @@ pnpm verify
|
||||
3. `pnpm --dir web test:rules` — отдельный быстрый прогон тестов ESLint-правил.
|
||||
4. `pnpm --dir web lint:all` — ESLint, Stylelint и token audit.
|
||||
5. `pnpm --dir web format:check` — форматирование кода `web/`.
|
||||
6. `pnpm check:docs` — форматирование Markdown.
|
||||
6. `pnpm check:docs` — форматирование Markdown (`AGENTS.md`, `README.md`,
|
||||
`docs/`).
|
||||
7. `pnpm --dir web build` — production build.
|
||||
|
||||
Полный `test` уже включает тесты lint-правил; отдельный `test:rules` в `verify`
|
||||
@@ -28,19 +29,13 @@ pnpm verify
|
||||
## Coverage
|
||||
|
||||
`pnpm --dir web test:coverage` считает покрытие только по `src/lib` и падает
|
||||
ниже порога. Scope и список исключений — в `web/vitest.config.ts`.
|
||||
|
||||
| Метрика | Порог |
|
||||
| ---------- | ----- |
|
||||
| statements | 90 |
|
||||
| branches | 82 |
|
||||
| functions | 72 |
|
||||
| lines | 91 |
|
||||
ниже порога. Пороги и список исключений из scope — в `web/vitest.config.ts`
|
||||
(`coverage.thresholds`, `coverage.exclude`); здесь они не дублируются, чтобы
|
||||
числа не устарели молча (`AGENTS.md`).
|
||||
|
||||
Порог — нижняя граница, а не оценка качества: он блокирует регрессию и не
|
||||
требует сначала закрывать весь долг. Актуальные проценты даёт
|
||||
`pnpm --dir web test:coverage`; здесь они не дублируются, чтобы число не
|
||||
устарело молча (`AGENTS.md`). Локальный замер занимает единицы секунд, поэтому
|
||||
`pnpm --dir web test:coverage`. Локальный замер занимает единицы секунд, поэтому
|
||||
отдельного прогона в `verify` нет — `test:coverage` заменяет `test`.
|
||||
|
||||
Исключения из scope и их причины:
|
||||
|
||||
+59
-68
@@ -1,20 +1,23 @@
|
||||
# План развития: easy-png-tools
|
||||
|
||||
> **Статус (2026-09):** Фаза 1 (полноценный TS-сайт) в основном выполнена — сайт
|
||||
> живёт, каталог переведён в типизированный `registry`, production UI — корневые
|
||||
> маршруты. Tech debt не закрыт полностью; состав quality-проверок —
|
||||
> `docs/quality-gates.md`, архитектура кода — `docs/architecture.md`, список
|
||||
> инструментов — `docs/tools-map.md`. Текущий фокус: SEO S1-free
|
||||
> (`docs/plan-seo.md`) + продуктовые задачи из `docs/backlog.md`. Домен нужен
|
||||
> для SEO (`plan-seo.md`, S1-domain). Фазы 2–8 — будущие; фазы 9–10 спланированы
|
||||
> в `docs/plan-platform.md`.
|
||||
> **Статус:** фаза 1 выполнена — сайт живёт на типизированном реестре,
|
||||
> production UI — корневые маршруты. Текущий фокус: SEO S1-free
|
||||
> (`docs/plan-seo.md`) + открытые задачи (`docs/backlog.md`). Домен нужен для
|
||||
> SEO (S1-domain). Фазы 2–8 — будущие; 9–10 спланированы в
|
||||
> `docs/plan-platform.md`; 11 — пайплайн.
|
||||
>
|
||||
> **Нумерация фаз этого документа не совпадает с волнами разработки** (`Q*`/`C*`
|
||||
> в других документах) и не используется как идентификатор задач: ссылаться на
|
||||
> фазу, а не на номер волны.
|
||||
> **Идентификаторы.** Ссылаться на фазу этого документа. Пространства
|
||||
> идентификаторов задач: `FA-*` — правки полей и раскладки
|
||||
> (`docs/plan-fields-audit-fixes.md`); `S*` — SEO/GEO (`docs/plan-seo.md`); `A*`
|
||||
> — API, `P*` — PWA, `F*` — шрифты (`docs/plan-platform.md`); `R*` — бренд и
|
||||
> домен (`docs/plan-seo.md`, §6). Буквы `Q*`/`C*` в архивных документах
|
||||
> относятся к волнам редизайна и как идентификаторы задач не используются.
|
||||
|
||||
## 0. Решения
|
||||
|
||||
Продуктовые и стратегические — здесь; устройство кода (реестр, исполнитель,
|
||||
слои) — `docs/architecture.md`, разделы 2–3.
|
||||
|
||||
- **Набор утилит для изображений, всё в браузере** (SvelteKit +
|
||||
`adapter-static`), данные не покидают машину.
|
||||
- **Первый и главный приоритет — полностью рабочий сайт**: максимум простых
|
||||
@@ -23,24 +26,12 @@
|
||||
- **Никакого wasm/Rust на этом этапе.** TS-реализация на canvas + `ImageData` —
|
||||
эталон алгоритмов, из которого позже генерируются эталонные файлы для проверки
|
||||
Rust-порта. Rust-ядро, WASM и CLI — отдельные фазы позже, когда сайт живёт.
|
||||
- **Пайплайн = JSON-список шагов** `{tool, params}`. Формат закладывается сейчас
|
||||
и потом станет общим для браузера, wasm и CLI.
|
||||
- **Реестр инструментов** (`web/src/lib/registry/`: id, title, description,
|
||||
category, schema, `run`) — источник истины: из него генерируются страницы,
|
||||
формы, пайплайны. Позже — общий формат для Rust-парсера.
|
||||
- **Пайплайн = JSON-список шагов** `{tool, params}`. Формат зафиксирован этим
|
||||
решением и станет общим для браузера, wasm и CLI; реализуется в фазе 11, не в
|
||||
фазе 1.
|
||||
- Валидация будущего Rust-порта — эталонные файлы, сгенерированные из
|
||||
TS-реализации. Сравнение: **бит-в-бит** для чистых пиксельных операций,
|
||||
**perceptual diff** для canvas-зависимых.
|
||||
- **Единая точка исполнения инструментов.** Страницы вызывают операции только
|
||||
через `web/src/lib/executor/` — нигде напрямую `tool.run`. Контракт уже
|
||||
асинхронный, поэтому:
|
||||
- когда появятся тяжёлые MEDIUM-операции (свёртки, квантование), внутренности
|
||||
исполнителя переезжают в Web Worker — воркер импортирует тот же чистый core
|
||||
как TS-фолбэк, алгоритмы не дублируются, вызывающий код не меняется;
|
||||
- в WASM-фазе тот же воркер принимает wasm-модуль и диспетчеризует по `toolId`
|
||||
— свитч A/B из фазы 5 сводится к замене реализации внутри исполнителя;
|
||||
- до появления MEDIUM-операций воркер не вводится: EASY-операции быстры и не
|
||||
блокируют UI.
|
||||
**perceptual diff** для canvas-зависимых (правила сравнения — раздел 3).
|
||||
|
||||
## 1. Где что лежит
|
||||
|
||||
@@ -50,46 +41,22 @@
|
||||
|
||||
## 2. Фазы
|
||||
|
||||
### Фаза 1 — Полноценный TS-сайт (единственный приоритет)
|
||||
### Фаза 1 — Полноценный TS-сайт (выполнена)
|
||||
|
||||
**Цель: сайт, которым можно пользоваться.** Без wasm, без Rust, без
|
||||
clamp-семантики (в TS это делает `Uint8ClampedArray` сам).
|
||||
Поставлено: каркас SvelteKit + static, дизайн-система и общий лейаут; ядро над
|
||||
`ImageData` (загрузка/декодирование, кодирование, операции по категориям) без
|
||||
DOM-зависимостей; типизированный реестр с универсальным рендерером страницы и
|
||||
формы; массовая реализация EASY-инструментов; i18n, темы, состояния
|
||||
загрузки/ошибок. Checkpoint достигнут: загрузил PNG → применил инструмент →
|
||||
скачал результат, без wasm.
|
||||
|
||||
1.1 **Каркас и UX/UI.** SvelteKit + static adapter, дизайн-система (цвета,
|
||||
типографика, компоненты), общий лейаут, шапка с навигацией по категориям.
|
||||
**В фазе 1 пайплайна нет.** Workspace цепочек с сохранением в localStorage и
|
||||
экспортом в JSON — фаза 11, не поставленная часть фазы 1. Страница инструмента
|
||||
работает ровно с одной записью реестра; что именно зарезервировано под пайплайн
|
||||
(компонент `StepCard`, ключи `chain.*`), перечислено в `docs/architecture.md`,
|
||||
раздел 1 «Чего в коде нет».
|
||||
|
||||
1.2 **Ядро.** Операции над `ImageData` (загрузка/декодирование файла,
|
||||
кодирование и скачивание, библиотека операций
|
||||
color/alpha/geometry/format/text/analyze). Всё без DOM-зависимостей внутри
|
||||
`core`.
|
||||
|
||||
1.3 **Реестр инструментов.** Одна запись =
|
||||
`{ id, title, description, category, schema, run }`. Универсальный рендерер:
|
||||
страница инструмента и форма параметров строятся из записи реестра
|
||||
автоматически; маршруты-утилиты генерируются из реестра на билде. Новый
|
||||
инструмент = новая запись + функция `run` (рецепт — `docs/architecture.md` §8).
|
||||
|
||||
1.4 **Пайплайн-workspace**: загрузил изображение → список шагов (инструмент +
|
||||
его параметры) → последовательное применение с превью каждого шага → скачивание
|
||||
финального результата. Добавление/удаление шагов. Сохранение пайплайнов в
|
||||
localStorage, экспорт/импорт JSON.
|
||||
|
||||
1.5 **Массовая реализация EASY-инструментов** по категориям:
|
||||
|
||||
- конвертация форматов (png/jpg/webp/bmp/base64/gif) и текстовые представления;
|
||||
- прозрачность и альфа-канал;
|
||||
- цвет: замена, тон, каналы, оттенки серого, инверсии;
|
||||
- геометрия и холст: resize, crop, rotate, flip, border, padding, background;
|
||||
- текст и простые эффекты: watermark, add-text, рамки, шум, pixelate;
|
||||
- анализ: размеры, палитра, проверки, просмотр. Приоритет — ширина, не глубина:
|
||||
как можно больше простых инструментов.
|
||||
|
||||
1.6 **Полировка.** Описания и подсказки для всех инструментов, состояния
|
||||
загрузки/ошибок, доступность, пустые состояния.
|
||||
|
||||
**Checkpoint:** загрузил PNG → применил пайплайн из N шагов → сохранил и вернул
|
||||
пайплайн → скачал результат. Все инструменты 1.5 работают на сайте. Ни одного
|
||||
wasm.
|
||||
Незакрытый долг по фазе — в `docs/backlog.md`; он не входит в фазы 2–8 и 11.
|
||||
|
||||
### Фаза 2 — Тестовые PNG и генератор эталонов
|
||||
|
||||
@@ -159,6 +126,29 @@ Manifest + service worker, оффлайн-режим («работает без
|
||||
устройство»), установка как приложение. Wasm-фаз не требует, идёт после S1 из
|
||||
`plan-seo.md`. Чек-лист — `docs/plan-platform.md`, §2.
|
||||
|
||||
### Фаза 11 — Пайплайн (цепочки шагов)
|
||||
|
||||
Продуктовая фича, **не зависит от wasm-фаз**: работает на существующем
|
||||
исполнителе и реестре, поэтому может идти параллельно с SEO. Номер 11 выбран
|
||||
чтобы не сдвигать уже используемые ссылки на фазы 2–10.
|
||||
|
||||
- **Формат шага** — `{tool, params}`, список шагов применяется последовательно,
|
||||
результат предыдущего идёт в следующий. Формат зафиксирован в §0 как общий для
|
||||
браузера, wasm и CLI, поэтому версионируется с первого дня.
|
||||
- **Первый шаг фазы:** модуль состояния цепочки + сохранение в localStorage +
|
||||
экспорт/импорт JSON (ошибки разбора уже описаны ключами
|
||||
`errors.badPipelineShape` / `errors.pipelineVersion` / `errors.noSteps`).
|
||||
- **Затем:** редактирование шагов (drag-n-drop, удаление, добавление), переход
|
||||
«один инструмент → цепочка» без перезагрузки, сохранённые именованные цепочки,
|
||||
отображение маски результата.
|
||||
- **Checkpoint:** собрал цепочку из N шагов → сохранил → вернулся позже → скачал
|
||||
результат последнего шага, всё в браузере.
|
||||
|
||||
Ограничения и открытые вопросы (1 → many не терминален, many → 1, частичные
|
||||
ошибки, имена частей) — `docs/backlog.md`, раздел «Идеи». Пайплайн из архивного
|
||||
UI-v1 удалён; что осталось в коде под эту фазу — `docs/architecture.md`,
|
||||
раздел 1.
|
||||
|
||||
## 3. Правила сравнения (для фаз 4–7)
|
||||
|
||||
| Класс операции | Критерий |
|
||||
@@ -187,7 +177,8 @@ Manifest + service worker, оффлайн-режим («работает без
|
||||
|
||||
## 5. Команды верификации
|
||||
|
||||
Текущие команды и матрица «изменение → проверки» — `docs/quality-gates.md`
|
||||
(`pnpm verify` — быстрый gate текущего этапа). Для будущих фаз добавляются
|
||||
`cargo test` (Rust-ядро против эталонных файлов, фаза 4+) и
|
||||
`harness compare tests/reference` (сквозная сверка native + wasm, фаза 7).
|
||||
Команды текущего этапа и матрица «изменение → проверки» —
|
||||
`docs/quality-gates.md` (`pnpm verify` — быстрый gate). Команды фаз 2–8
|
||||
(`cargo test`, `harness compare`) **в проекте не существуют** и появятся вместе
|
||||
с соответствующими фазами: до этапа 2 проверять нечем, кроме `pnpm verify` и
|
||||
e2e.
|
||||
|
||||
+20
-5
@@ -1,10 +1,21 @@
|
||||
# Аудит использования - UX\UI
|
||||
|
||||
> Статус: **active**. Сырые наблюдения по инструментам и полям — вход для
|
||||
> `docs/plan-fields-audit-fixes.md`, который разбивает их на волны правок, и для
|
||||
> `docs/backlog.md`. Здесь фиксируется, **что замечено**, а не порядок работы.
|
||||
> Ids инструментов сверять с `web/src/lib/registry/` — исторические названия
|
||||
> (`webp-to-png`, `color-wheel-generator`, `png-info`) в реестре отсутствуют.
|
||||
> Статус: **active**. **Дата прохода: 2026-09-13** (инструменты и поля). Здесь
|
||||
> только «что плохо» — наблюдения, без порядка работ и без критериев приёмки.
|
||||
> Новый проход — новая дата сверху; закрытая находка не удаляется, а помечается
|
||||
> ссылкой на задачу в `docs/backlog.md`.
|
||||
>
|
||||
> Как исправлять — `docs/backlog.md` (задачи) и
|
||||
> `docs/plan-fields-audit-fixes.md` (порядок правок полей). Находка в аудите и
|
||||
> задача в бэклоге — не дубль: пока задачи нет, находка просто висит.
|
||||
>
|
||||
> Ids ниже **не сверены с реестром** и часть из них в коде отсутствует
|
||||
> (`webp-to-png`, `color-wheel-generator`, `png-info`, `png-to-hsl`). Перед
|
||||
> работой по любому пункту — найти соответствующий инструмент в
|
||||
> `web/src/lib/registry/` или `docs/tools-map.md`; несуществующий id не
|
||||
> resurrect'ить, а переименовать пункт. Чтобы не сверять вручную каждый раз, ищи
|
||||
> по смыслу: категория в заголовке раздела + ключевое слово в пункте →
|
||||
> `docs/tools-map.md`, где id уже фактические.
|
||||
|
||||
## везде
|
||||
|
||||
@@ -154,6 +165,7 @@
|
||||
|
||||
## аудит полей
|
||||
|
||||
- у полей plate, position9 и font-style вторая подпись дублирует первую
|
||||
- position offset: должны быть отдельные range slider на x и y
|
||||
- range slider: поле ввода, кнопки + и -, кнопка сброса на умолчание
|
||||
- range slider with presets: слайдер, как выше, добавочные кнопки с пресетами
|
||||
@@ -161,3 +173,6 @@
|
||||
- ввод высоты и ширины с keep aspect ratio должен быть единым компонентом
|
||||
- поле seed (random) - range slider не нужен, поле для ввода и "randomize"
|
||||
кнопка
|
||||
- поле текста: нормальная высота по умолчанию (не одна строка), для svg —
|
||||
форматирование или подсветка синтаксиса, для bin\hex — колонки, для
|
||||
hex\bytes\rgba — пример в placeholder
|
||||
|
||||
+12
-9
@@ -2,11 +2,15 @@
|
||||
|
||||
> Статус: **active**. Живой документ для сверки с каталогом.
|
||||
>
|
||||
> **Источник правды по id — реестр** `web/src/lib/registry/`. Этот список —
|
||||
> навигационный: сверять перед использованием. Формат записи:
|
||||
> `id — параметры через запятую`. Сравнение с каталогом источника (объём,
|
||||
> нишевые клоны серий Logo*/Icon*/Stamp*/Signature*) — замер в
|
||||
> `docs/plan-seo.md`, §2; здесь он не дублируется.
|
||||
> **Карта перечисляет `id` записи реестра (`web/src/lib/registry/`), которые
|
||||
> одновременно являются адресами страниц:** `/tools/crop-png` — это
|
||||
> `id: "crop-png"`. Отдельного поля `slug` в `ToolEntry` нет, поэтому id и адрес
|
||||
> сейчас совпадают; их разделение — решённая, но не реализованная задача
|
||||
> (`docs/backlog.md`, раздел «Мелочи всякие», и `docs/architecture.md`, раздел
|
||||
> 3). Источник правды — реестр. Этот список навигационный: сверять перед
|
||||
> использованием. Формат записи: `id — параметры через запятую`. Сравнение с
|
||||
> каталогом источника (объём, нишевые клоны серий Logo*/Icon*/Stamp*Signature*)
|
||||
> — замер в `docs/plan-seo.md`, §2; здесь он не дублируется.
|
||||
>
|
||||
> Количество инструментов здесь не фиксируется — оно выводится из раздела 1 и из
|
||||
> `TOOLS` в `registry/index.ts`, и дублировать его цифрой значит заставлять себя
|
||||
@@ -237,7 +241,6 @@ Ids приведены к фактическим в `web/src/lib/registry/`.
|
||||
- **Нишевые серии** (logo/icon/stamp/signature — замер в шапке документа) —
|
||||
сознательно не копируем: это обычные операции над конкретным контентом, у нас
|
||||
они доступны через базовые инструменты + цепочки.
|
||||
- **Входные «из формата в PNG»** (jpg-to-png, webp-to-png) сознательно не
|
||||
заведены: загрузка файла уже принимает любой поддержанный формат, а
|
||||
инструмент-конвертер добавил бы лишнее звено цепочки. Отдельные страницы под
|
||||
эти интенты — предмет решения в `docs/plan-seo.md` §5.
|
||||
- **Входные «из формата в PNG»** (jpg-to-png, webp-to-png) — решение и его SEO-
|
||||
последствия описаны в разделе 1 выше; отдельные страницы под эти интенты —
|
||||
предмет решения в `docs/plan-seo.md` §5.
|
||||
|
||||
+2
-2
@@ -9,8 +9,8 @@
|
||||
"dev": "pnpm --dir web dev",
|
||||
"build": "pnpm --dir web build",
|
||||
"format": "pnpm format:docs && pnpm --dir web format",
|
||||
"format:docs": "prettier --write docs --log-level warn --cache",
|
||||
"check:docs": "prettier --check docs --cache",
|
||||
"format:docs": "prettier --write AGENTS.md README.md docs --log-level warn --cache",
|
||||
"check:docs": "prettier --check AGENTS.md README.md docs --cache",
|
||||
"generate:fixtures": "node tests/fixtures/manual-edge-cases/generate.mjs"
|
||||
},
|
||||
"keywords": [],
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
# ESLint-плагины и дизайн-токены: детали
|
||||
|
||||
Эта часть репозитория подробно описана здесь, а не в корневом `AGENTS.md`:
|
||||
рутина-разработка её не требует, детали нужны только при правке самих
|
||||
линт-правил или конфигов.
|
||||
> Статус: **active**. Область применения — `web/eslint-plugins/**`,
|
||||
> `web/scripts/**`, конфиги линтинга. Остальная рутина разработки её не требует;
|
||||
> детали нужны только при правке самих линт-правил или конфигов. Команды и
|
||||
> пороги — `docs/quality-gates.md`.
|
||||
|
||||
## Структура
|
||||
|
||||
|
||||
Reference in New Issue
Block a user