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