mirror of
https://github.com/Ku6epXBOCTuK/easy-png-tools.git
synced 2026-11-13 17:08:20 +00:00
docs: review all
This commit is contained in:
@@ -26,19 +26,28 @@
|
||||
|
||||
Читать только нужную строку, а не все документы подряд.
|
||||
|
||||
| Задача | Документ |
|
||||
| --------------------------------------------- | ---------------------------------- |
|
||||
| Слои, поток данных, новый инструмент/поле | `docs/architecture.md` |
|
||||
| Какие проверки запускать, пороги, CI | `docs/quality-gates.md` |
|
||||
| Правка самих гейтов: «почему так, а не иначе» | `docs/decisions.md` |
|
||||
| Порядок работы, нагрузка на машину | `docs/agent-workflow.md` |
|
||||
| Locator-ы, fixtures, browser matrix | `docs/testing-strategy.md` |
|
||||
| Детали lint-правил и токенов | `web/eslint-plugins/README.md` |
|
||||
| Задачи и идеи | `docs/backlog.md` |
|
||||
| Актуальный список инструментов | `docs/tools-map.md` |
|
||||
| Правка полей и раскладки по волнам | `docs/plan-fields-audit-fixes.md` |
|
||||
| Ручная приёмка UI | `docs/checklist-manual-testing.md` |
|
||||
| Индекс остальных документов | `docs/README.md` |
|
||||
| Задача | Документ |
|
||||
| ---------------------------------------------- | ---------------------------------- |
|
||||
| Слои, поток данных, новый инструмент/поле | `docs/architecture.md` |
|
||||
| Какие проверки запускать, пороги, CI | `docs/quality-gates.md` |
|
||||
| Правка самих гейтов: «почему так, а не иначе» | `docs/decisions.md` |
|
||||
| Порядок работы, нагрузка на машину | `docs/agent-workflow.md` |
|
||||
| Locator-ы, fixtures, browser matrix | `docs/testing-strategy.md` |
|
||||
| Детали lint-правил и токенов | `web/eslint-plugins/README.md` |
|
||||
| Задачи и идеи | `docs/backlog.md` |
|
||||
| Актуальный список инструментов | `docs/tools-map.md` |
|
||||
| Правка полей и раскладки по волнам | `docs/plan-fields-audit-fixes.md` |
|
||||
| Ручная приёмка UI | `docs/checklist-manual-testing.md` |
|
||||
| Направления развития, фазы 1–11 | `docs/roadmap.md` |
|
||||
| SEO/GEO-план (адреса, `noindex`, sitemap) | `docs/plan-seo.md` |
|
||||
| API/PWA/шрифты | `docs/plan-platform.md` |
|
||||
| Переименование бренда и покупка домена | `docs/plan-domain.local.md` † |
|
||||
| Сырые UX-находки по инструментам (вход аудита) | `docs/tools-audit.md` |
|
||||
| Архивирование и удаление старых документов | `docs/archive/README.md` |
|
||||
| Индекс остальных документов | `docs/README.md` |
|
||||
|
||||
† `docs/plan-domain.local.md` вне git, в клоне отсутствует — в этом случае
|
||||
задачу ищут по `docs/plan-seo.md` §6.
|
||||
|
||||
## Инварианты, которые линтер не проверяет
|
||||
|
||||
|
||||
@@ -12,16 +12,16 @@ and open source.
|
||||
transparency/alpha, color, channel decomposition, geometry, filters,
|
||||
pixel-property masks, generation, text/watermarks, analysis & verdicts — the
|
||||
current list lives in [`docs/tools-map.md`](docs/tools-map.md)
|
||||
- **Pipeline system (workspace)** — load image → chain multiple tool steps →
|
||||
sequential application → download result; pipelines saved to localStorage,
|
||||
exportable as JSON
|
||||
- **Web Worker execution** — heavy operations run off the main thread with
|
||||
automatic fallback to direct calls
|
||||
- **i18n** — full Russian and English localization
|
||||
- **Dark/Light themes** — persisted to localStorage
|
||||
- **Pure TypeScript core** — `lib/core/` operates on
|
||||
`ImageData`/`Uint8ClampedArray` with no DOM dependency
|
||||
- **Fully static** — no backend, no analytics, no tracking
|
||||
- **Fully static** — no backend; the image never leaves your browser. The only
|
||||
third-party request is a Google Analytics page-view counter — it never sees
|
||||
the file or its contents; the boundary and its limits are in
|
||||
`docs/plan-seo.md` §0
|
||||
|
||||
## Tech Stack
|
||||
|
||||
@@ -65,8 +65,9 @@ pnpm verify # full fast quality gate — the pre-review gate
|
||||
pnpm build # static production export → web/build/
|
||||
```
|
||||
|
||||
Полный список команд и матрица «какие проверки запускать при каком изменении» —
|
||||
в `AGENTS.md` и `docs/quality-gates.md`.
|
||||
For the full command list and the "which checks to run for which change" matrix,
|
||||
see [`AGENTS.md`](AGENTS.md) and
|
||||
[`docs/quality-gates.md`](docs/quality-gates.md).
|
||||
|
||||
## Architecture
|
||||
|
||||
@@ -74,8 +75,8 @@ Everything is built around the **two-layer registry** (`web/src/lib/registry/`):
|
||||
a tool is a self-contained implementation `{id, schema, input, result, run}`
|
||||
(`registry/tools/`), and a page is `{slug, title, description, category, steps}`
|
||||
(`registry/pages/`) that names the address and the tools to run — one tool can
|
||||
back several pages. Pages, forms, and pipelines are generated from it, and all
|
||||
execution goes through a single `executor.execute` entry point.
|
||||
back several pages. Pages and forms are generated from it, and all execution
|
||||
goes through a single `executor.execute` entry point.
|
||||
|
||||
Core image processing lives in `web/src/lib/core/` and operates directly on
|
||||
`ImageData`, with no browser API dependencies.
|
||||
|
||||
+4
-3
@@ -15,6 +15,7 @@
|
||||
| Документ | Когда читать | Статус |
|
||||
| ---------------------------------- | -------------------------------------------------- | ------ |
|
||||
| `AGENTS.md` | Всегда: контракт агента | active |
|
||||
| `docs/README.md` | Индекс документов, правила ведения документации | active |
|
||||
| `docs/architecture.md` | Слои, поток данных, новый инструмент/поле/маршрут | active |
|
||||
| `docs/quality-gates.md` | Какие проверки запускать, пороги, coverage, CI | active |
|
||||
| `docs/decisions.md` | Правка самих гейтов; чтение CI после пуша | active |
|
||||
@@ -60,9 +61,9 @@
|
||||
`web/src/lib/registry/tools/`, адрес и имя страницы (`slug`) — в
|
||||
`web/src/lib/registry/pages/`; исторические названия в старых планах не
|
||||
действуют (`docs/architecture.md`, раздел 3).
|
||||
- Поиск по `docs/` задевает `docs/archive/` (объём больше, чем у активных
|
||||
документов вместе). Для правок production-кода архив не нужен: grep по
|
||||
`docs/*.md` без `archive/`.
|
||||
- Поиск по `docs/` задевает `docs/archive/`: там вынесены завершённые планы и
|
||||
исследования, поэтому grep по `docs/` даёт больше шума, чем пользы. Для правок
|
||||
production-кода архив не нужен: grep по `docs/*.md` без `archive/`.
|
||||
- Проверка ссылок в документации не автоматизирована; ожидаемо неразрешимыми
|
||||
считаются ссылки на `docs/plan-domain.local.md` (файл вне git). Всё остальное
|
||||
неразрешимое — ошибка.
|
||||
|
||||
@@ -12,7 +12,10 @@
|
||||
4. Определить границу изменения: production UI, shared-модуль или инфраструктура
|
||||
— `AGENTS.md`, раздел «Границы изменений». Изменение shared-кода требует
|
||||
проверки production-потребителей.
|
||||
5. Не добавлять исключения линтера ради отдельных модулей без записи в baseline.
|
||||
5. Не добавлять исключения линтера ради отдельных модулей: осознанные
|
||||
отступления живут в конфиге правила (`allowPaths`, `allowWords`,
|
||||
`ignoreMissingPatterns`, `allowDefaultProject` —
|
||||
`web/eslint-plugins/README.md`), а не в самих проверяемых файлах.
|
||||
|
||||
## 2. Порядок изменения
|
||||
|
||||
@@ -27,8 +30,10 @@
|
||||
warnings и чтение CI — в `docs/quality-gates.md`. Здесь только то, что относится
|
||||
к работе агента.
|
||||
|
||||
`lint:all` может завершиться успешно при warnings. Это не означает чистый
|
||||
baseline: нужно отдельно показать число errors, warnings и token warnings.
|
||||
`lint:all` роняет gate на любых ESLint warnings (`--max-warnings=0`).
|
||||
Единственное, что он пропускает, — unused-token сообщения (неблокирующие).
|
||||
Поэтому «команда прошла» не равно «нечего чинить»: нужно отдельно показать число
|
||||
errors, warnings и token warnings.
|
||||
|
||||
E2E запускается отдельно от быстрых проверок, потому что поднимает build и
|
||||
статический сервер. Для UI-изменений сначала выбирается минимальный
|
||||
|
||||
+55
-32
@@ -16,9 +16,9 @@ web/src/lib/
|
||||
catalog.ts # группировка страниц и счётчики для /list-tools
|
||||
categories.ts # id/названия категорий
|
||||
tool-icons.ts # slug страницы → иконка
|
||||
core/ # чистый TS: операции над ImageData, без DOM
|
||||
core/ # операции над ImageData; без DOM, кроме io.ts/domText.ts
|
||||
executor/ # единственная точка исполнения инструментов
|
||||
i18n/ # словари ru/en, t(), списки для i18n-правил линтера
|
||||
i18n/ # словари ru/en, t(), состояние локали
|
||||
components/ # UI (fields/, layout/, ui/, schema-компоненты)
|
||||
zip.ts # сборка мультифайлового результата (1 → many)
|
||||
theme.svelte.ts # light/dark + localStorage
|
||||
@@ -30,9 +30,15 @@ web/src/routes/
|
||||
```
|
||||
|
||||
Правило зависимостей: `components/**` и `routes/**` знают про `registry/**`,
|
||||
`executor/**` и `core/**`; `core/**` не знает ни о ком и не зависит от DOM;
|
||||
`registry/**` описывает данные и знает только `core/**` и `registry-schema.ts`.
|
||||
Нарушение этого порядка — повод не заводить импорт, а инвертировать зависимость.
|
||||
`executor/**` и `core/**`; `core/**` не знает ни о ком; `registry/**` описывает
|
||||
данные и знает только `core/**` и `registry-schema.ts`. Нарушение этого порядка
|
||||
— повод не заводить импорт, а инвертировать зависимость.
|
||||
|
||||
Исключение из «core без DOM» — `core/io.ts` (декодирование файла, кодирование,
|
||||
скачивание) и `core/domText.ts` (canvas-рендер текста и эмодзи). Они трогают
|
||||
`document`, `createImageBitmap` и `HTMLCanvasElement`, поэтому не работают в
|
||||
node-Vitest и исключены из coverage-гейта (`docs/quality-gates.md`, раздел
|
||||
Coverage). Всё остальное в `core/**` — чистые функции над `ImageData`.
|
||||
|
||||
### Чего в коде нет
|
||||
|
||||
@@ -45,9 +51,11 @@ web/src/routes/
|
||||
полей двух инструментов — отдельная работа, описанная как фаза 11
|
||||
`docs/roadmap.md`. Под неё зарезервированы `components/StepCard.svelte`
|
||||
(сейчас импортируется только витриной `/kit`) и ключи `chain.*`,
|
||||
`errors.badPipelineShape`, `errors.noSteps`.
|
||||
- **Разделения страниц, которые делят один инструмент** — сводит 14
|
||||
convert-страниц на один инструмент; задача в `docs/backlog.md`.
|
||||
`errors.badPipelineShape`, `errors.pipelineVersion`, `errors.noSteps`.
|
||||
- **Разделения страниц, которые делят один инструмент** — сводит все
|
||||
convert-страницы на один инструмент; задача в `docs/backlog.md`. Пока
|
||||
`registry.test.ts` не требует, чтобы на инструмент ссылались две страницы, а
|
||||
на практике не ссылается ни одна.
|
||||
|
||||
Задачи по обоим пунктам — в `docs/backlog.md`; архитектурные решения по
|
||||
пайплайну — `docs/roadmap.md`.
|
||||
@@ -67,7 +75,7 @@ registry/{tools,pages}/*.ts
|
||||
├─► SchemaFields.svelte поля из schema.fields (FIELDS: kind → контрол)
|
||||
├─► SchemaSourceTile/SchemaTextSource.svelte вход
|
||||
└─► SchemaPreview + schema-preview-model.ts вид превью
|
||||
│ buildSchemaPreviewModel(inputMode, resultKind, …) → source/result/hasResult
|
||||
│ buildSchemaPreviewModel(input) → { sourceValue, resultValue, formatValue, hasResult }
|
||||
▼
|
||||
executor.execute(ctx) единственная точка исполнения
|
||||
│ сериализация параметров → worker → worker-handler → tool.run(ctx)
|
||||
@@ -104,9 +112,9 @@ registry/{tools,pages}/*.ts
|
||||
| `id` | внутреннее имя инструмента, уникально; ключ `tools.<id>.*` и `toolId` в worker |
|
||||
| `schema` | `toolSchema({ fields, layout?, meta? })` |
|
||||
| `input` | `"image" \| "text" \| "none"` — что ждёт инструмент на входе |
|
||||
| `result` | `"image" \| "text" \| "verdict" \| "files"` — что отдаёт |
|
||||
| `domOnly` | инструмент требует DOM (canvas); превью-executor запускает его напрямую |
|
||||
| `output` | mime/ext/quality для скачивания (не у всех) |
|
||||
| `result`? | `"image" \| "text" \| "verdict" \| "files"` — что отдаёт |
|
||||
| `domOnly`? | инструмент требует DOM (canvas); превью-executor запускает его напрямую |
|
||||
| `output`? | mime/ext/quality для скачивания (не у всех) |
|
||||
| `run(ctx)` | единственный метод: `ctx` → `ToolResult` |
|
||||
|
||||
`Page` (записи в `registry/pages/<group>.ts`):
|
||||
@@ -121,15 +129,22 @@ registry/{tools,pages}/*.ts
|
||||
|
||||
Кто по чему адресуется: `slug` — маршрут, имя файла, `TOOL_ICONS`, поиск по
|
||||
каталогу; `id` — ключ `tools.<id>.*` (подписи полей, `options`, `results`),
|
||||
`toolId` worker-протокола и запись пайплайна. Один инструмент может обслуживать
|
||||
несколько страниц, одна страница — состоять из нескольких инструментов.
|
||||
`toolId` worker-протокола и запись пайплайна. Модель допускает, что один
|
||||
инструмент обслуживает несколько страниц, а страница состоит из нескольких
|
||||
инструментов; на практике пока каждая страница ссылается на свой инструмент, а
|
||||
исполняется первый (и единственный) шаг.
|
||||
|
||||
Инструмент работает с любым поддержанным форматом, поэтому png-интент живёт
|
||||
только в `slug`: `id: "crop"` → страница `slug: "crop-png"` → `crop-png.png`.
|
||||
Правило имени: снять суффикс `-png` и в середине имени (`png-to-base64` →
|
||||
`to-base64`, `X-to-png` → `from-X`, `verify-is-png` → `verify-png`); следствие —
|
||||
Правило имени `id`: снять суффикс `-png` и в середине имени (`png-to-base64` →
|
||||
`to-base64`, `X-to-png` → `from-X`, `verify-is-png` → `verify-png`). Правило
|
||||
относится только к `id`: у `slug` своя история и SEO-значение, поэтому
|
||||
переименование `id` не трогает адреса, а переименование `slug` меняет адрес, имя
|
||||
файла, ключ `pages.*`, иконку и запись в `docs/tools-map.md`.
|
||||
файла, ключ `pages.*`, иконку и запись в `docs/tools-map.md`. Слаги, сложившиеся
|
||||
до разделения реестра, правилу не соответствуют (`convert-png-to-jpg`,
|
||||
`change-png-opacity`, `png-is-grayscale`, `verify-is-png` и другие) — это не
|
||||
ошибка, менять их без SEO-решения нельзя. Форму slug проверяет
|
||||
`registry.test.ts` (только форма, не смысл).
|
||||
|
||||
Хелперы `genTool` / `imgTool` / `textGen` из `types.ts` задают `run` для типовых
|
||||
случаев (приведение типа результата), чтобы запись не повторяла обвязку. `TOOLS`
|
||||
@@ -154,15 +169,18 @@ registry/{tools,pages}/*.ts
|
||||
допустимому виду. Правка поведения полей идёт здесь, а не в компоненте.
|
||||
|
||||
Новый вид поля требует: спека + фабрика `field.*` → запись в `fieldSpecs` →
|
||||
контрол в `components/fields/schema/` → ключ в `FIELDS` → дефолт и санитизация →
|
||||
контрол (`components/fields/schema/`, а для `dimension` —
|
||||
`fields/DimensionField.svelte`) → ключ в `FIELDS` → дефолт и санитизация →
|
||||
i18n-подписи → тест в `registry-schema.test.ts`.
|
||||
|
||||
## 5. Исполнение
|
||||
|
||||
`executor/executor.ts` — фабрика `createExecutor(factories?)` плюс готовый
|
||||
экспорт `execute`. Контракт асинхронный (`Promise`), параметры сериализуются,
|
||||
исполнение уходит в `executor.worker.ts`; `worker-handler.ts` разбирает
|
||||
сообщение и зовёт `run`. Если worker недоступен — прямой вызов.
|
||||
`executor/executor.ts` — фабрика `createExecutor({ workerFactory? })` плюс
|
||||
готовый экспорт `execute`. Контракт асинхронный (`Promise`), параметры
|
||||
сериализуются по `executor/protocol.ts`, исполнение уходит в
|
||||
`executor.worker.ts`; `worker-handler.ts` разбирает сообщение и зовёт `run`.
|
||||
Если worker недоступен — прямой вызов. UI импортирует `execute` из
|
||||
`executor/index.ts`.
|
||||
|
||||
Отсюда следует: подключение wasm позже означает замену реализации **внутри**
|
||||
исполнителя, а не правку компонентов.
|
||||
@@ -170,13 +188,16 @@ i18n-подписи → тест в `registry-schema.test.ts`.
|
||||
## 6. i18n
|
||||
|
||||
Словари — `web/src/lib/i18n/` (`en.ts`, `ru.ts`, общий `dict.ts` с
|
||||
`LOCALES`/`BASE_LOCALE`). Пользовательский текст живёт только в словарях и
|
||||
читается через `t()`. Секции разделены по слоям: `pages.<slug>.title` и
|
||||
`pages.<slug>.description` — тексты страницы (в базовой локали секция пустая, EN
|
||||
берётся из реестра), `tools.<id>.options/params/results` — подписи полей и
|
||||
тексты вердиктов инструмента, `fields.*`/`groups.*` — общие подписи схемы.
|
||||
Инварианты паритета ключей, плейсхолдеров и запрет хардкода проверяются
|
||||
линтером, детали — `web/eslint-plugins/README.md`.
|
||||
`LOCALES`/`BASE_LOCALE`, `t.ts` с `t()`, `locale.svelte.ts` с состоянием
|
||||
локали). Пользовательский текст живёт только в словарях и читается через `t()`.
|
||||
Списки ключей и допустимых атрибутов для i18n-правил линтера лежат не здесь, а в
|
||||
`web/eslint-plugins/i18n/lists.js`. Секции разделены по слоям:
|
||||
`pages.<slug>.title` и `pages.<slug>.description` — тексты страницы (в базовой
|
||||
локали секция пустая, EN берётся из реестра),
|
||||
`tools.<id>.options/params/results` — подписи полей и тексты вердиктов
|
||||
инструмента, `fields.*`/`groups.*` — общие подписи схемы. Инварианты паритета
|
||||
ключей, плейсхолдеров и запрет хардкода проверяются линтером, детали —
|
||||
`web/eslint-plugins/README.md`.
|
||||
|
||||
## 7. Точки расширения
|
||||
|
||||
@@ -204,7 +225,7 @@ i18n-подписи → тест в `registry-schema.test.ts`.
|
||||
`from-base64`, `is-grayscale`).
|
||||
4. **Описать схему** через `field.*`, задать `layout`, если поля нужно
|
||||
сгруппировать. Качество кодирования (`quality`) — не поле страницы, а
|
||||
настройка скачивания (`docs/plan-seo.md` §1, S1f).
|
||||
настройка скачивания (`docs/plan-seo.md` §4, S1f).
|
||||
5. **Написать `run`** поверх `core/*`; пиксельные операции — чистые функции в
|
||||
`core/`, без DOM.
|
||||
6. **Добавить страницу** в `registry/pages/<group>.ts`: `slug` с адресным
|
||||
@@ -221,8 +242,10 @@ i18n-подписи → тест в `registry-schema.test.ts`.
|
||||
«Реализовано», формат строки — в шапке раздела. Карта перечисляет **`slug`
|
||||
страниц** из `registry/pages/`, то есть адреса. Проверяет
|
||||
`web/src/lib/registry/tools-map-doc.test.ts`: slug без страницы и страница
|
||||
без записи в карте роняют тест. Обоснование и границы автоматизации —
|
||||
`docs/decisions.md`, раздел 4.
|
||||
без записи в карте роняют тест; разделы «Можно добавить» и «Идеи»
|
||||
проверяются в обратную сторону — реализованный slug в плане тоже роняет
|
||||
тест, поэтому при переезде инструмента из плана в реестр его вынимают
|
||||
оттуда. Обоснование и границы автоматизации — `docs/decisions.md`, раздел 4.
|
||||
|
||||
Ожидаемый минимум проверок: `pnpm --dir web check` и `pnpm --dir web test`; для
|
||||
инструмента, меняющего пользовательский поток, — e2e (см. раздел 3
|
||||
|
||||
+75
-65
@@ -1,36 +1,38 @@
|
||||
# Backlog
|
||||
|
||||
> Статус: **active**. Продуктовые, UX- и инфраструктурные задачи. Здесь только
|
||||
> **открытые** задачи: выполненные удаляются, а критичные для проекта факты из
|
||||
> них переносятся в тематический документ (архитектура, quality gates, lint,
|
||||
> e2e) по мере закрытия.
|
||||
>
|
||||
> «Что сознательно не вводим и почему» живёт в `docs/decisions.md`, архитектура
|
||||
> — в `docs/architecture.md`. Raw-находки UX-аудита — `docs/tools-audit.md`, их
|
||||
> разбивка на волны — `docs/plan-fields-audit-fixes.md`.
|
||||
>
|
||||
> Ids инструментов сверять с `web/src/lib/registry/`; полный список —
|
||||
> `docs/tools-map.md`.
|
||||
> Статус: **active**. Открытые задачи. Подробные планы, разбор решений и сырые
|
||||
> находки — `docs/plan-seo.md`, `docs/plan-platform.md`,
|
||||
> `docs/plan-fields-audit-fixes.md`, `docs/tools-audit.md`, `docs/decisions.md`.
|
||||
|
||||
## lint и прочие правила
|
||||
|
||||
- [ ] `tseslint.config(...)` deprecated: сигнатура
|
||||
`(...configs: InfiniteDepthConfigWithExtends[]): ConfigArray` помечена
|
||||
устаревшей, нужен переход на `defineConfig` из `typescript-eslint` —
|
||||
`web/eslint.config.js`.
|
||||
устаревшей, нужен переход на `defineConfig` из `eslint/config` (ESLint
|
||||
core, а не `typescript-eslint`) — `web/eslint.config.js`.
|
||||
- [ ] удалить мёртвые `tools.*.params` из root i18n после проверки потребителей;
|
||||
до этого действует pattern-based policy-исключение
|
||||
(`web/eslint-plugins/README.md`, раздел `dict-consistency`)
|
||||
|
||||
## Мелочи всякие
|
||||
|
||||
Порядок правок полей и раскладки — `docs/plan-fields-audit-fixes.md`, сами
|
||||
находки («что плохо», с датой аудита) — `docs/tools-audit.md`. Здесь — как это
|
||||
исправить; находка в аудите не отменяет задачу здесь.
|
||||
|
||||
- [ ] Пересмотреть тесты - исправить всякие хрупкие, которые проверяют
|
||||
количество инструментов, точное совпадение текстов, поиск по
|
||||
`img[alt="Result image"]`, svelte классам и тому подобные
|
||||
- [ ] split-into-parts-png - увеличить максимальные значения, плохо работает для
|
||||
нарезки ассетов
|
||||
- [ ] crop, resize (но скорее всего все инструменты) - при загрузке нового
|
||||
изображения сбрасывает размеры, настроенные пользователем
|
||||
- [ ] split-into-parts-png, resize (может еще какие-то) - возможность указывать
|
||||
размеры из заранее одобренного списка размеров - 16х16, 32х32, 64х64 как
|
||||
минимум, может еще какие-то размеры добавить, единый список из предыдущей
|
||||
задачи - (для инди разработчиков, стандартные тайлы часто делаются)
|
||||
- [ ] e2e: `i18n.spec.ts` и `text-and-verdicts.spec.ts` дублируют локаторы из
|
||||
`helpers/page.ts` вместо вызова — правило «спека зовёт helpers»
|
||||
(`docs/testing-strategy.md`, раздел 5)
|
||||
- [ ] мёртвый i18n-ключ `noParams` («нет параметров — результат готов»): его
|
||||
ничем не читают. Либо показывать подсказку для инструментов с пустой
|
||||
схемой (FA-E1), либо удалить ключ из обеих локалей
|
||||
- [ ] Пересмотреть тесты — убрать точные совпадения текстов и любые селекторы,
|
||||
привязанные к разметке или svelte-классам (сейчас остались:
|
||||
`tool-flows.spec.ts`, `schema-tool-strings.test.ts`)
|
||||
- [ ] нужен инструмент для удаления фона по выбранной точке - удалять внутри
|
||||
выделенной области
|
||||
- [ ] сделать конвертацию в смайлики\награды для твитча (6 размеров или выбор
|
||||
@@ -63,8 +65,10 @@
|
||||
строку для svg - форматирование или подсветку синтаксиса??? для bin\hex -
|
||||
форматирование по колонкам??? для hex\bytes\rgba - пример в placeholder
|
||||
- [ ] сделать загрузку картинки исходника по ctrl+v и drag-and-drop (dropzone)
|
||||
- [ ] square-mask-png - проверить работу - параметры неверно считаются. width -
|
||||
100, height - 100, x - 100, y - 100, обрезает 50% на 50% картинку
|
||||
- [ ] square-mask-png — перепроверить баг-репорт: он устарел, в схеме больше нет
|
||||
параметров `width`/`height`/`x`/`y` (теперь `widthPct`/`heightPct` 10..100
|
||||
и один `offset` в диапазоне −50..50, значения 100 недостижимы). Либо
|
||||
подтвердить, что баг исправлен, либо найти новый repro
|
||||
- [ ] прикрутить кастомный color picker, добавить прозрачность (range?)
|
||||
- [ ] проверить настройки range slider на crop-png - слишком большие значения
|
||||
- [ ] для range slider field - добавить поле для ввода цифр и кнопку сброса
|
||||
@@ -72,48 +76,45 @@
|
||||
- [ ] для keep Aspect field - пересчитывать второе поле, учитывать какое было
|
||||
изменено
|
||||
- [ ] обновить страницу kit или сделать storybook
|
||||
- [ ] Свести convert-страницы на один инструмент `convert` — реализация уже
|
||||
умеет «скопировать пиксели, декодировать вход, отдать в заданном формате»,
|
||||
а страницы различаются только форматом вывода. **Зависит от S1f**
|
||||
(`docs/plan-seo.md` §1): пока `quality` живёт в схеме страницы, у
|
||||
`convert-png-to-bmp` и `convert-png-to-jpg` разные наборы полей, и один
|
||||
инструмент на них не сойдётся. Качество — настройка кнопки Download
|
||||
(«Download as JPEG»), а не поле страницы; после его переезда пресеты схемы
|
||||
не нужны, страницы различаются только `output` и `params` шага. Модель
|
||||
страницы к этому готова: `Page.steps` — список инструментов, один
|
||||
инструмент может обслуживать много страниц (`docs/architecture.md`, раздел
|
||||
3).
|
||||
- [ ] Свести convert-страницы (`convert-png-to-jpg`, `convert-png-to-webp`,
|
||||
`png-to-bmp`) на один инструмент `convert` — зависит от S1f
|
||||
(`docs/plan-seo.md` §4). Мешает не только `quality` (он живёт в схеме
|
||||
инструмента), а сама модель: `output` с mime/ext — свойство `Tool`, а
|
||||
`PageStep` умеет только `{ id, params? }`, поэтому страницы на общий `id`
|
||||
получат одинаковый формат вывода. Нужно научить шаг переопределять формат
|
||||
вывода
|
||||
- [ ] придумать процесс редактирования и настройки pipeline - drag-n-drop,
|
||||
удаление, добавление инструментов. переход один инструмент - pipeline без
|
||||
перезагрузки
|
||||
- [ ] `CatalogToolbar`: фильтры используют сырой `<button>`, а не `ui/Button` —
|
||||
единый контрол для всей библиотеки
|
||||
(`web/src/lib/components/CatalogToolbar.svelte`).
|
||||
- [ ] Вынести `ToneVariant`/варианты в общий модуль: `const`-объект с тонами
|
||||
продублирован в `ui/Badge.svelte` и кнопках
|
||||
(`web/src/lib/components/ui/Badge.svelte`).
|
||||
- [ ] `PreviewTile`: canvas должен сам выбирать соотношение сторон в диапазоне
|
||||
16:9…9:16 вместо фиксированного
|
||||
- [ ] `SchemaTextResult` вердикт-бейдж рисует руками: свои классы
|
||||
`badge`/`badge-tone--{tone}` и свой CSS в `<style>` вместо
|
||||
`ui/Badge.svelte` — тон и разметка бейджа живут в двух местах
|
||||
(`web/src/lib/components/SchemaTextResult.svelte`).
|
||||
- [ ] `PreviewTile`: высота плитки задана только `min-height: clamp(...)`,
|
||||
поэтому она не следует за пропорциями результата — широкий результат
|
||||
уезжает в узкую полосу, а высокий упирается в `max-height: 100vh`. Нужно,
|
||||
чтобы соотношение сторон выбиралось по результату в диапазоне 16:9…9:16
|
||||
(`web/src/lib/components/layout/PreviewTile.svelte`).
|
||||
- [ ] `ThemeToggle`: свои анимации иконок и своя кнопка вместо `IconButton`
|
||||
(`web/src/lib/components/ui/ThemeToggle.svelte`).
|
||||
- [ ] Радиусы бордеров не совпадают с референсом: в `refs` радиусы были 0, а мы
|
||||
взяли ненулевой `--radius-m` для всех бордеров. Проверить токен против
|
||||
референсов; возможно он должен быть 0, а скругления оставить только там,
|
||||
где они реально есть в дизайне.
|
||||
- [ ] Радиусы бордеров: `--radius-m` ненулевой и применён ко всем бордерам
|
||||
(`app.css`, `SchemaToolView.svelte`). Решить, где скругления реально есть
|
||||
в дизайне, а где должен быть 0
|
||||
- [ ] `Footer`: версию брать из `package.json`, а не писать `v0.1.0` вручную
|
||||
(`web/src/lib/components/layout/Footer.svelte`).
|
||||
|
||||
## Переезд
|
||||
|
||||
- [ ] множественные числа (часть/части/частей; «N параметров», счётчики) —
|
||||
`tPlural()` и формат `PluralForms`; детали и критерии — в этом backlog
|
||||
функция `tPlural(path, n)` и форма `PluralForms` (обязательное `other`,
|
||||
опциональные `one`/`few`/`many`); первые потребители — `part`/`parts` в
|
||||
подписи результата и счётчики параметров. **Критерий приёмки:** у ключа с
|
||||
plurals есть все формы, для которых нужен перевод, `other` никогда не
|
||||
пустой, число подставляется в текст без склейки
|
||||
|
||||
## SEO/GEO
|
||||
|
||||
Общий план: `docs/plan-seo.md` (S1–S4). Старт возможен — пока только domain-free
|
||||
часть S1. Полное содержание этапов не дублируется здесь, только идентификаторы.
|
||||
|
||||
- [ ] переименование бренда + домен: чек-лист в локальном файле
|
||||
`plan-domain.local.md` (в git не входит, в клоне отсутствует; подробности
|
||||
и порядок — `docs/plan-seo.md` §6, R1/R2)
|
||||
@@ -127,16 +128,17 @@
|
||||
(`docs/plan-seo.md` §5)
|
||||
- [ ] sitemap.xml из реестра + `Sitemap:` в robots.txt (S1b/S1c)
|
||||
- [ ] Seo.svelte: canonical + OG-теги, per-tool OG-картинки (S1d/S1e)
|
||||
- [ ] контент страницы инструмента: seo-блок в словарях, H1/how-to/FAQ/related
|
||||
(S2)
|
||||
- [ ] контент страницы инструмента: seo-блок в словарях, how-to/FAQ/related (S2)
|
||||
- [ ] JSON-LD (SoftwareApplication/FAQPage/Breadcrumb) + llms.txt (S3)
|
||||
- [ ] GSC + приватная аналитика (Plausible/Umami/CF), дистрибуция (S4)
|
||||
- [ ] дистрибуция: закрепить репо, alternative.to, Product Hunt, Reddit/HN (S4c)
|
||||
- [ ] GSC: подключить домен, отправить sitemap, мониторинг индексации (S4a)
|
||||
- [ ] GA4: cookieless-конфигурация, отключить рекламные audiences и signals,
|
||||
`anonymize_ip` — сейчас в `web/src/app.html` голый `gtag('config')` (S4b)
|
||||
- [ ] GA4: отсечь попадания с локальных и preview-сборок — тег стоит в
|
||||
prerender-оболочке и грузится на каждой странице, нужен флаг окружения
|
||||
|
||||
## Платформа (API/PWA)
|
||||
|
||||
Общий план: `docs/plan-platform.md`; WASM-ядро, эталоны и CLI —
|
||||
`docs/roadmap.md` (фазы 2–8). Здесь только треки, которых нет в roadmap.
|
||||
|
||||
- [ ] PWA: manifest + service worker, оффлайн-режим, установка (после S1)
|
||||
- [ ] API: спайк серверного ядра (нативный Rust vs edge-wasm), ключи, free tier
|
||||
(после wasm-фаз 5–7)
|
||||
@@ -145,8 +147,15 @@
|
||||
|
||||
## Качество production UI
|
||||
|
||||
Политика проверок — `docs/quality-gates.md`, e2e — `docs/testing-strategy.md`.
|
||||
|
||||
- [ ] `/` и `/list-tools` выходят без `<title>`: `svelte:head` есть только на
|
||||
странице инструмента, а готовые ключи `home.defaultTitle`,
|
||||
`catalog.pageTitle`, `catalog.metaDescription` не читает ни один компонент
|
||||
(S1d)
|
||||
- [ ] **Баг e2e:** `tool-flows.spec.ts:179` создаёт контекст через
|
||||
`browser.newContext()`, который не наследует `use.baseURL` из
|
||||
`playwright.config.ts`, — относительный `goto('/tools/flip-png')` внутри
|
||||
`openTool` не резолвится, и тест «работает без Web Worker (fallback)»
|
||||
падает на всех движках. В CI это не видно: job `e2e` неблокирующий
|
||||
- [ ] Component-тесты `SchemaToolView`/`SchemaPreview` требуют отдельного
|
||||
решения: node Vitest не имеет DOM/canvas, а e2e уже покрывает поведение.
|
||||
- [ ] Ошибка декодирования изображения из `createImageBitmap` пока показывается
|
||||
@@ -154,9 +163,8 @@
|
||||
|
||||
## Идеи
|
||||
|
||||
Идеи 1–5 и 15 относятся к **пайплайну (цепочкам шагов)**, которого в коде пока
|
||||
нет: это фаза 11 `docs/roadmap.md`. Идеи 6–14 — к существующим инструментам и
|
||||
UI. Оценки (S/M/L) — оценка труда, не приоритет.
|
||||
Идеи 1–5 и 15 — про пайплайн (фаза 11 `docs/roadmap.md`). Идеи 6–14 и 16 — про
|
||||
существующие инструменты и UI. Оценки (S/M/L) — оценка труда, не приоритет.
|
||||
|
||||
1. **Менять первый инструмент цепочки** — оценка M. Сейчас базовый инструмент
|
||||
зафиксирован при открытии страницы; хочется заменить его на другой без
|
||||
@@ -171,8 +179,8 @@ UI. Оценки (S/M/L) — оценка труда, не приоритет.
|
||||
3. **Несколько цепочек: сохранение и загрузка** — оценка M. Именованные цепочки
|
||||
в localStorage (список, создать/переименовать/удалить), быстрое переключение.
|
||||
Формат экспортного JSON расширить опциональным полем имени; обратной
|
||||
совместимости со старыми выгрузками не делаем (см. задачу про `id`/`slug`
|
||||
выше — формат ещё не устоялся).
|
||||
совместимости со старыми выгрузками не делаем — формат не устоялся: он
|
||||
появится вместе с фазой 11 `docs/roadmap.md` и до её старта может измениться.
|
||||
|
||||
4. **Batch-обработка архивов выбранным chain** — оценка L. Вход — zip-архив
|
||||
картинок (распаковка в браузере), прогон текущего или выбранного сохранённого
|
||||
@@ -192,9 +200,11 @@ UI. Оценки (S/M/L) — оценка труда, не приоритет.
|
||||
(`OutputFormat`: mime/ext/qualityParamId) и кодирование по нему в
|
||||
`SchemaToolView.download()` — `docs/architecture.md`, раздел 3. Что осталось
|
||||
сверх S1f:
|
||||
- экспорт с лимитом размера → закрывает отложенную страницу
|
||||
`reduce-to-size-png` (целевой KB, бинарный поиск по k из квантования).
|
||||
Проработать вместе с достройкой download-флоу.
|
||||
- экспорт с лимитом размера → «сжатие до целевого КБ»: для lossy это quality
|
||||
в селекторе формата, для PNG — бинарный поиск по k из квантования. Нужна ли
|
||||
отдельная страница под этот интент — открытый вопрос с вордстатом, имя не
|
||||
выбрано (`docs/plan-seo.md` §5). Проработать вместе с достройкой
|
||||
download-флоу.
|
||||
|
||||
7. **Страница `watermark-image-png` (водяной знак картинкой)** — оценка M. Нужна
|
||||
overlay-механика: второй источник изображения (getOverlay/store), параметры
|
||||
@@ -259,7 +269,7 @@ UI. Оценки (S/M/L) — оценка труда, не приоритет.
|
||||
- частичные ошибки: один битый файл не должен ронять весь шаг — нужен
|
||||
per-file отчёт, а не один `ToolError` на шаг;
|
||||
- имена частей на каждом шаге и Download для набора (сейчас zip по
|
||||
`tool.id`, `SchemaToolView.download()`);
|
||||
`page.slug`, `SchemaToolView.download()`);
|
||||
- обратная сторона: распаковка zip в браузере, порядок файлов, поведение
|
||||
many → 1 → many в одной цепочке. Зависит от идеи 10 (остальные потребители
|
||||
1 → many) и идеи 4 (batch по архиву), `docs/roadmap.md` фаза 8
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
# Чек-лист ручного тестирования production UI
|
||||
|
||||
> Вспомогательный документ к e2e-тестам (`web/e2e/`, `pnpm --dir web test:e2e`)
|
||||
> и стратегии `docs/testing-strategy.md`. Автоматизированная часть закрывает
|
||||
> «нет падений/ошибок»; здесь — то, что руками, и что Playwright не покрывает.
|
||||
> Статус: **active**. Вспомогательный документ к e2e-тестам (`web/e2e/`,
|
||||
> `pnpm --dir web test:e2e`) и стратегии `docs/testing-strategy.md`.
|
||||
> Автоматизированная часть закрывает «нет падений/ошибок»; здесь — то, что
|
||||
> руками, и что Playwright не покрывает.
|
||||
>
|
||||
> Перед прогоном: `pnpm --dir web build`, поднять локально `web/` (dev или build
|
||||
> → `node scripts/serve-static.mjs`).
|
||||
@@ -23,7 +24,7 @@
|
||||
(авто-покрытие: `web/e2e/png-fixtures.spec.ts`).
|
||||
- [ ] Анимированный/APNG, если попадётся — не ломает конвейер.
|
||||
- [ ] Повреждённый/не-PNG файл — понятная ошибка, нет краша (авто-чека в
|
||||
`pipeline.spec.ts` и `png-fixtures.spec.ts`).
|
||||
`tool-flows.spec.ts` и `png-fixtures.spec.ts`).
|
||||
- [ ] PNG с битой CRC / 16-bit — как ведут себя анализаторы (ориентация, размер,
|
||||
прозрачность); CRC/16-bit авто-покрыты в `png-fixtures.spec.ts`.
|
||||
|
||||
@@ -72,8 +73,9 @@
|
||||
|
||||
## D. Производительность и отзывчивость
|
||||
|
||||
- [ ] Долгие авто-рераны на больших картинках (jpeg-artifacts, dithering,
|
||||
quantize на 4К) — UI не фризит, канселяция/дебаунс срабатывает.
|
||||
- [ ] Долгие авто-рераны на больших картинках (`jpeg-artifacts-png`,
|
||||
`dithering-png`, `quantize-png` на 4К) — UI не фризит, канселяция/дебаунс
|
||||
срабатывает.
|
||||
- [ ] Скролл страницы при длинных настройках (например, text-инструменты) — нет
|
||||
залипаний, панели не прыгают.
|
||||
- [ ] Вкладка не «съедает» память при 10+ повторах blur/sharpen на мегапикселе
|
||||
@@ -82,9 +84,9 @@
|
||||
## E. Визуальный слой (доделываем по ходу)
|
||||
|
||||
- [ ] Сетки/выравнивание на брейкпоинтах 640 / 800 / 1100 (mobile/tablet/
|
||||
desktop) на всех 4 маршрутах.
|
||||
- [ ] Тема light/dark: контраст вердиктов (Yes/No), статус «LIVE PREVIEW»,
|
||||
мета-инфо бликов не даёт.
|
||||
desktop) на всех маршрутах: `/`, `/list-tools`, `/kit`, любая `/tools/*`.
|
||||
- [ ] Тема light/dark: контраст вердиктов (Yes/No), подпись панели превью
|
||||
(«Предпросмотр»), мета-инфо бликов не даёт.
|
||||
- [ ] Иконки в каталоге на всех категориях — не «бьются» (missing icon).
|
||||
- [ ] Язык RU/EN на превью-страницах — переключение без регресса рендера.
|
||||
|
||||
|
||||
+18
-5
@@ -47,11 +47,11 @@
|
||||
которых 14 — ложные («30 дней» политики архива, «9 опций» описания поля, «4
|
||||
проекта» факт из `playwright.config.ts», цитата самого правила).
|
||||
|
||||
- Правило всё равно неполно: ловит «122 инструмента», но не «инструментов —
|
||||
122», не «более 100», не «≈120»; на каждое обходное движение нужна правка
|
||||
правила, а allowlist легитимных чисел становится вторым источником долга.
|
||||
Худший исход — не «шумно», а «вроде гейт, а толку нет»: срабатывания на пустом
|
||||
месте забейзелят, и через месяц правило перестанет ловить настоящие нарушения.
|
||||
- Правило всё равно неполно: ловит «M инструментов», но не «инструментов — M»,
|
||||
не «более 100», не «≈120»; на каждое обходное движение нужна правка правила, а
|
||||
allowlist легитимных чисел становится вторым источником долга. Худший исход —
|
||||
не «шумно», а «вроде гейт, а толку нет»: срабатывания на пустом месте
|
||||
забейзелят, и через месяц правило перестанет ловить настоящие нарушения.
|
||||
Видимое блокирующее правило, которое на деле ничего не проверяет, хуже
|
||||
отсутствия правила.
|
||||
- Соблюдение правила дешевле обхода: «список — в `docs/tools-map.md`» стоит
|
||||
@@ -82,6 +82,19 @@
|
||||
про парсер, а не молча перестанет что-либо проверять. Входит в обычный
|
||||
`pnpm --dir web test`; новых гейтов и конфигов не добавляет.
|
||||
|
||||
Граница автоматизации: парсер читает разделы «Реализовано», «Можно добавить» и
|
||||
«Идеи», но проверяет их в разных направлениях. Раздел 1 сверяется с реестром
|
||||
обеими сторонами (slug без страницы и страница без записи). Разделы 2 и 3
|
||||
сверяются только в одну сторону: там slug'и намеренно несуществующие, поэтому
|
||||
проверяется, что **ни один из них не является реальной страницей** —
|
||||
реализованный инструмент, оставшийся в плане, роняет тест. Проверять «у плана
|
||||
есть запись в реестре» нельзя по построению: список плановый, а не фактический.
|
||||
|
||||
Плановые разделы устроены прозой, поэтому их разбор другой: берутся все токены
|
||||
нужной формы из всего буллита, а не только из «головы» до тире. Следствие для
|
||||
авторов карты: реализованные страницы в планах называются прозой между
|
||||
буллитами, а не внутри них.
|
||||
|
||||
## 5. Visual snapshots — не вводим
|
||||
|
||||
`toHaveScreenshot` сравнивает страницу с PNG-baseline из прошлого коммита, а не
|
||||
|
||||
+123
-102
@@ -16,13 +16,20 @@
|
||||
> которые в бэклоге, здесь не дублируются: этот документ задаёт **порядок**,
|
||||
> backlog — **учёт** (`docs/backlog.md`).
|
||||
>
|
||||
> **Инструменты в этом документе названы по адресам страниц (slug).** В коде у
|
||||
> страницы `slug` (`registry/pages/`), у вызываемого инструмента — `id` без
|
||||
> png-интента (`crop-png` → `id: "crop"`, `png-to-hsl` → `to-hsl`), модель —
|
||||
> `docs/architecture.md`, раздел 3. Названия из `docs/tools-audit.md` вида
|
||||
> `color-wheel-generator`, `webp-to-png` в реестре отсутствуют (реальная
|
||||
> страница — `color-wheel-png`): при переносе пункта аудита в шаг сверять с
|
||||
> `PAGES`.
|
||||
> **В этом документе инструменты названы по `id`** (`registry/tools/`), а не по
|
||||
> адресу страницы: поля и раскладка принадлежат схеме инструмента, а страница
|
||||
> только ссылается на список шагов (`Page.steps`), где сейчас ровно один
|
||||
> инструмент, а в будущем может быть несколько. Поэтому «у страницы `crop-png`
|
||||
> есть поля x/y» неверно — поля есть у инструмента `crop`, а страница лишь
|
||||
> показывает его. `id` не несёт png-интента: страница `crop-png` вызывает
|
||||
> `id: "crop"`, страница `png-to-hsl` — `id: "to-hsl"` (модель —
|
||||
> `docs/architecture.md`, раздел 3).
|
||||
>
|
||||
> **Как переносить пункты аудита.** `docs/tools-audit.md` назван по страницам,
|
||||
> потому что это то, что видит пользователь. При переносе пункта в шаг мапить
|
||||
> slug → `id` через `page.steps[0].id` в `registry/pages/`. Названия аудита вида
|
||||
> `color-wheel-generator` и `webp-to-png` в реестре отсутствуют (реальная
|
||||
> страница — `color-wheel-png`, инструмент — `color-wheel`).
|
||||
|
||||
## 0. Ключевое решение: select → buttons
|
||||
|
||||
@@ -124,9 +131,9 @@ buttons: <V extends string>(s: Omit<ButtonsSpec<V>, "kind">): Field<V> => ({
|
||||
|
||||
- `web/src/lib/registry-schema.ts` — `NumberSpec.variant?: "seed"`
|
||||
- `web/src/lib/components/fields/schema/RangeControl.svelte` — условный рендер
|
||||
- `web/src/lib/registry/filters.ts` — `randomizePixels`, `addNoise`: поле seed →
|
||||
`{ ...field.number(...), spec: { ...spec, variant: "seed" } }` (или
|
||||
пересоздать через `field.seed()`)
|
||||
- `web/src/lib/registry/tools/filters.ts` — `randomizePixels`, `addNoiseTool`:
|
||||
поле seed → `{ ...field.number(...), spec: { ...spec, variant: "seed" } }`
|
||||
(или пересоздать через `field.seed()`)
|
||||
|
||||
---
|
||||
|
||||
@@ -137,64 +144,68 @@ buttons: <V extends string>(s: Omit<ButtonsSpec<V>, "kind">): Field<V> => ({
|
||||
|
||||
### FA-B1. geometry.ts
|
||||
|
||||
| Инструмент | Поле | Опции |
|
||||
| ----------------------- | -------- | ----------------------------------------- |
|
||||
| rotate-png | angle | 90° / 180° / 270° |
|
||||
| flip-png | axis | horizontal / vertical |
|
||||
| swap-orientation-png | target | portrait / landscape |
|
||||
| symmetric-copy-png | axis | vertical / horizontal |
|
||||
| symmetric-copy-png | keepSide | left / right / top / bottom |
|
||||
| change-aspect-ratio-png | ratio | 1:1 / 4:3 / 3:4 / 3:2 / 2:3 / 16:9 / 9:16 |
|
||||
| change-aspect-ratio-png | mode | crop / pad |
|
||||
| Инструмент | Поле | Опции |
|
||||
| ------------------- | -------- | ----------------------------------------- |
|
||||
| rotate | angle | 90° / 180° / 270° |
|
||||
| flip | axis | horizontal / vertical |
|
||||
| swap-orientation | target | portrait / landscape |
|
||||
| symmetric-copy | axis | vertical / horizontal |
|
||||
| symmetric-copy | keepSide | left / right / top / bottom |
|
||||
| change-aspect-ratio | ratio | 1:1 / 4:3 / 3:4 / 3:2 / 2:3 / 16:9 / 9:16 |
|
||||
| change-aspect-ratio | mode | crop / pad |
|
||||
|
||||
**Исключение:** `change-canvas-size-png` (anchor, 9 опций) — оставить
|
||||
**Исключение:** `change-canvas-size` (anchor, 9 опций) — оставить
|
||||
`field.select()`, т.к. 9 кнопок в ряд не поместятся. Аналогично `position9` (уже
|
||||
отдельный компонент `PositionControl`).
|
||||
|
||||
### FA-B2. color.ts
|
||||
|
||||
| Инструмент | Поле | Опции |
|
||||
| ------------------------ | --------- | ------------------------------------ |
|
||||
| dithering-png | pattern | floyd-steinberg / bayer |
|
||||
| extract-channel-png | channel | red / green / blue |
|
||||
| swap-channels-png | pair | r-g / r-b / g-b |
|
||||
| decrease-color-count-png | maxColors | 2 / 4 / 8 / 16 / 32 / 64 / 128 / 256 |
|
||||
| Инструмент | Поле | Опции |
|
||||
| -------------------- | --------- | ---------------------------------------------------- |
|
||||
| dithering | pattern | floyd-steinberg / bayer |
|
||||
| extract-channel | channel | red / green / blue |
|
||||
| swap-channels | pair | r-g / r-b / g-b |
|
||||
| decrease-color-count | maxColors | 2 / 4 / 8 / 16 / 32 / 44 / 64 / 96 / 128 / 192 / 256 |
|
||||
|
||||
**Исключение:** channel spaces (hsl/hsv/... component) — 3-6 опций, но это
|
||||
**Исключение:** инструменты разложения каналов (`to-hsl`, `to-hsv`, `to-hsi`,
|
||||
`to-cmyk`, `to-ycbcr`, `to-lab`, поле `component`) — 3-6 опций, но это
|
||||
динамические инструменты, оставить `select`.
|
||||
|
||||
### FA-B3. analyze.ts
|
||||
|
||||
| Инструмент | Поле | Опции |
|
||||
| ------------------------- | ---- | ------------------ |
|
||||
| show-transparent-png | mode | binary / highlight |
|
||||
| show-grayscale-pixels-png | mode | (тот же) |
|
||||
| show-color-pixels-png | mode | (тот же) |
|
||||
| light-pixel-mask-png | mode | (тот же) |
|
||||
| dark-pixel-mask-png | mode | (тот же) |
|
||||
| Инструмент | Поле | Опции |
|
||||
| --------------------- | ---- | ------------------ |
|
||||
| show-transparent | mode | binary / highlight |
|
||||
| show-grayscale-pixels | mode | (тот же) |
|
||||
| show-color-pixels | mode | (тот же) |
|
||||
| light-pixel-mask | mode | (тот же) |
|
||||
| dark-pixel-mask | mode | (тот же) |
|
||||
| unique-color-mask | mode | (тот же) |
|
||||
|
||||
Все используют общий `maskBaseFields` — правка в одном месте.
|
||||
Пять инструментов из шести берут `mode` из общего `maskBaseFields` — правка в
|
||||
одном месте. Исключение — `show-transparent`: он переопределяет `mode` своими
|
||||
опциями и дефолтом `highlight`, поэтому его `mode` правится отдельно.
|
||||
|
||||
### FA-B4. filters.ts
|
||||
|
||||
| Инструмент | Поле | Опции |
|
||||
| ------------- | ---- | ------------ |
|
||||
| add-noise-png | mode | mono / color |
|
||||
| Инструмент | Поле | Опции |
|
||||
| ---------- | ---- | ------------ |
|
||||
| add-noise | mode | mono / color |
|
||||
|
||||
### FA-B5. generate.ts
|
||||
|
||||
| Инструмент | Поле | Опции |
|
||||
| ------------------ | ------------ | --------------------- |
|
||||
| color-spectrum-png | direction | horizontal / vertical |
|
||||
| step-colors-png | layout | grid / strip |
|
||||
| complementary-png | layout | grid / strip |
|
||||
| triadic-png | layout | grid / strip |
|
||||
| tetradic-png | layout | grid / strip |
|
||||
| analogous-png | layout | grid / strip |
|
||||
| monochromatic-png | layout | grid / strip |
|
||||
| shades-png | layout | grid / strip |
|
||||
| sort-colors-png | layout | grid / strip |
|
||||
| mix-colors-png | (нет select) | — |
|
||||
| Инструмент | Поле | Опции |
|
||||
| -------------- | ------------ | --------------------- |
|
||||
| color-spectrum | direction | horizontal / vertical |
|
||||
| step-colors | layout | grid / strip |
|
||||
| complementary | layout | grid / strip |
|
||||
| triadic | layout | grid / strip |
|
||||
| tetradic | layout | grid / strip |
|
||||
| analogous | layout | grid / strip |
|
||||
| monochromatic | layout | grid / strip |
|
||||
| shades | layout | grid / strip |
|
||||
| sort-colors | layout | grid / strip |
|
||||
| mix-colors | (нет select) | — |
|
||||
|
||||
---
|
||||
|
||||
@@ -203,12 +214,12 @@ buttons: <V extends string>(s: Omit<ButtonsSpec<V>, "kind">): Field<V> => ({
|
||||
### FA-C1. Source-aware defaults для dimension-полей — реализовано
|
||||
|
||||
Source-aware defaults реализованы: общий resolver в `registry-schema`
|
||||
(`applySourceDefaults`). Для `resize-png` и `crop-png` дефолт после загрузки
|
||||
равен размерам текущего source; до загрузки используется положительный fallback
|
||||
`1×1`. Один-sided `0` у resize сохраняет режим auto. Проверено тестом
|
||||
(`applySourceDefaults`). Для `resize` и `crop` дефолт после загрузки равен
|
||||
размерам текущего source; до загрузки используется положительный fallback `1×1`.
|
||||
Один-sided `0` у resize сохраняет режим auto. Проверено тестом
|
||||
`registry.test.ts` («resize и crop получают размеры текущего source»).
|
||||
|
||||
### FA-C2. symmetric-copy-png: "keep side" не работает
|
||||
### FA-C2. symmetric-copy: "keep side" не работает
|
||||
|
||||
Баг в `run()`: `keepSide` не учитывает `axis`. При `axis: "vertical"` (удвоение
|
||||
ширины) работают только `left`/`right`, `top`/`bottom` не имеют смысла. Нужно:
|
||||
@@ -257,55 +268,63 @@ resolver.
|
||||
|
||||
### FA-C4. verify-is-png: неверное название — решение принято, шаг не выполнен
|
||||
|
||||
Проверяет текст, не PNG-файл. Варианты:
|
||||
Находка про **страницу**, а не про инструмент: страница `verify-is-png` обещает
|
||||
проверку PNG-файла, а проверяет текст (base64 / data-uri). Сам инструмент назван
|
||||
честно — `id: "verify-png"`, png-интент в нём означает входные данные. Адрес
|
||||
страницы — `slug` `verify-is-png` (`docs/architecture.md`, раздел 3), поэтому
|
||||
«неверное название» относится к `slug`, а не к `id`.
|
||||
|
||||
- `title` → "Verify PNG data", `description` → уточнить; `id` оставить — он же
|
||||
адрес страницы, переименование ломает ссылку
|
||||
- либо полное переименование `id` → `verify-is-png-data` с редиректом со старого
|
||||
URL (нужен вордстат, иначе страница выпадает из индекса)
|
||||
Варианты:
|
||||
|
||||
**Решение:** поменять title и description, id не трогать. Переименование `id`
|
||||
затрагивает адрес — с ним связана задача разведения `id`/`slug`
|
||||
(`docs/backlog.md`), поэтому в одиночку не делается.
|
||||
- `title` → «Verify PNG data», `description` → уточнить; `slug` и `id` не
|
||||
трогать
|
||||
- либо переименовать `slug` → `verify-png-data` с редиректом со старого URL
|
||||
(нужен вордстат, иначе страница выпадает из индекса)
|
||||
|
||||
**Решение:** поменять title и description, имя страницы не трогать.
|
||||
Переименование `slug` меняет адрес, имя файла, ключи `pages.*` и иконку, то есть
|
||||
это операция с SEO-последствиями, а не строчка в реестре — она не делается в
|
||||
одиночку, вместе с решением по `docs/plan-seo.md` §5.
|
||||
|
||||
---
|
||||
|
||||
## 4. Волна D — обязательные фиксы (новые параметры)
|
||||
|
||||
### FA-D1. remove-alpha-channel-png: выбор цвета фона
|
||||
### FA-D1. remove-alpha-channel: выбор цвета фона
|
||||
|
||||
Сейчас захардкожен `#ffffff`. Добавить `field.color({ default: "#ffffff" })`.
|
||||
|
||||
Файл: `alpha.ts`, схема `removeAlphaChannelSchema`.
|
||||
|
||||
### FA-D2. extract-alpha-mask-png: галочка «инвертировать»
|
||||
### FA-D2. extract-alpha-mask: галочка «инвертировать»
|
||||
|
||||
Добавить `field.checkbox({ label: "fields.invertMask", default: false })`. В
|
||||
`run()`: если `invert`, вызвать `invertAlpha()` после `extractAlphaMask()`.
|
||||
|
||||
Файл: `alpha.ts`.
|
||||
|
||||
### FA-D3. change-canvas-size-png: position9 вместо select
|
||||
### FA-D3. change-canvas-size: position9 вместо select
|
||||
|
||||
`anchor` сейчас `field.select()` с 9 опциями → `field.position9()`.
|
||||
|
||||
Файл: `geometry.ts`. Уже есть `PositionControl`.
|
||||
|
||||
### FA-D4. Generators: добавить height
|
||||
### FA-D4. Generate: добавить height
|
||||
|
||||
Инструменты без `size`/`height`:
|
||||
|
||||
- blend-two-png — добавить
|
||||
- blend-two — добавить
|
||||
`height: field.slider({ min: 1, max: 1024, default: 512 })`
|
||||
- step-colors-png — аналогично
|
||||
- complementary, triadic, tetradic, analogous, monochromatic, shades,
|
||||
sort-colors — все используют `paletteBaseSchema` (общий объект), добавить
|
||||
`height` туда
|
||||
- mix-colors-png — добавить height
|
||||
- step-colors — аналогично
|
||||
- complementary, triadic, tetradic, analogous, monochromatic, shades — все
|
||||
используют `paletteBaseSchema` (общий объект), добавить `height` туда
|
||||
- `sort-colors` — своя схема, `paletteBaseSchema` не использует, `height`
|
||||
добавлять вручную
|
||||
- mix-colors — добавить height
|
||||
|
||||
Файл: `generate.ts`.
|
||||
|
||||
### FA-D5. add-border-png: прозрачность цвета
|
||||
### FA-D5. add-border: прозрачность цвета
|
||||
|
||||
Текущий `field.color()` не поддерживает alpha. Пока что: оставить как есть
|
||||
(прозрачность не поддерживается нативным color picker). Отметить в аудите как
|
||||
@@ -322,24 +341,24 @@ blocked.
|
||||
|
||||
Файл: `SchemaToolView.svelte` — показать подсказку если `schema.fields` пуст.
|
||||
|
||||
### FA-E2. quantize-png: пресеты
|
||||
### FA-E2. quantize: пресеты
|
||||
|
||||
`field.slider` с max 64 → добавить пресеты (8, 16, 32, 64) как кнопки под
|
||||
слайдером. Либо `field.buttons()` с 4 опциями вместо слайдера.
|
||||
|
||||
### FA-E3. trim-empty-space-png: 0-254 → проценты
|
||||
### FA-E3. trim-empty-space: 0-254 → проценты
|
||||
|
||||
Текущий `min: 0, max: 254` — нестандартно. Добавить переключатель px/% (аналог
|
||||
шага FA-A1 — `variant: "alpha-threshold"` в NumberSpec).
|
||||
|
||||
### FA-E4. add-text-png: plate offset
|
||||
### FA-E4. add-text: plate offset
|
||||
|
||||
Plate снизу имеет большой отступ — добавить галочку "compact plate" или
|
||||
уменьшить дефолтный padding.
|
||||
|
||||
### FA-E5. emoji-to-png: выбор эмодзи
|
||||
### FA-E5. from-emoji: выбор эмодзи
|
||||
|
||||
Поле `text` → добавить группу часто используемых эмодзи как кнопки- пресеты над
|
||||
Поле `emoji` → добавить группу часто используемых эмодзи как кнопки-пресеты над
|
||||
полем ввода.
|
||||
|
||||
---
|
||||
@@ -365,44 +384,46 @@ Plate снизу имеет большой отступ — добавить г
|
||||
round-corners — px→%): нужен компонент переключателя единиц в слайдере.
|
||||
Требует правки `RangeControl` + всех схем — в волны A–E не входит, нужен
|
||||
отдельный этап.
|
||||
- **Smoothing checkbox** для масок (circle, square, star, wavy, round-corners,
|
||||
invert-alpha): нужен `field.checkbox("smoothing")` + правка core (shapes.ts,
|
||||
alpha). Отложен — требует визуального тестирования.
|
||||
- **Smoothing checkbox** для масок (circle-mask, square-mask, star-mask,
|
||||
wavy-mask, round-corners, invert-alpha): нужен `field.checkbox("smoothing")` +
|
||||
правка core (shapes.ts, alpha). Отложен — требует визуального тестирования.
|
||||
- **Vignette**: выбор центра + цвет + feather — 3 новых параметра. Требует
|
||||
normative решения по UI.
|
||||
- **Remove-background**: выбор точки удаления — либо параметр, либо отдельный
|
||||
инструмент.
|
||||
- **Precision/quality**: select в resize (bilinear/bicubic/nearest) — нужен ли
|
||||
выбор алгоритма?
|
||||
- **Метод масштабирования в `resize`**: поля в схеме нет (только `size` и
|
||||
`keepAspect`), поэтому это уже не правка селекта, а новое поле — нужен ли
|
||||
выбор алгоритма (bilinear/bicubic/nearest)?
|
||||
- **Placeholders**: "нет параметров" — какой текст/компонент показывать.
|
||||
|
||||
### 7.2 Операционные (тест-кейсы, описания)
|
||||
|
||||
- find-contour-png: расширять на толщину линии (требует правки core)
|
||||
- make-thicker-png: расширять картинку на толщину линии
|
||||
- make-thinner-png: уменьшать от краев, fix выступа
|
||||
- feather-edges-png: расширять картинку на толщину линии
|
||||
- clean-edges-png: найти тест-кейс, возможно увеличить максимум
|
||||
- despeckle-alpha-png: найти более заметный тест-кейс
|
||||
- close-holes-png: проверить поведение на больших полупрозрачных областях
|
||||
- auto-contrast-png: найти тестовый кейс
|
||||
- sharpen-png: добавить тест-кейс
|
||||
- center-by-alpha-png: найти тест-кейс
|
||||
- png-is-grayscale: улучшить видимость результата
|
||||
- find-contour: расширять на толщину линии (требует правки core)
|
||||
- make-thicker: расширять картинку на толщину линии
|
||||
- make-thinner: уменьшать от краев, fix выступа
|
||||
- feather-edges: расширять картинку на толщину линии
|
||||
- clean-edges: найти тест-кейс, возможно увеличить максимум
|
||||
- despeckle-alpha: найти более заметный тест-кейс
|
||||
- close-holes: проверить поведение на больших полупрозрачных областях
|
||||
- auto-contrast: найти тестовый кейс
|
||||
- sharpen: добавить тест-кейс
|
||||
- center-by-alpha: найти тест-кейс
|
||||
- is-grayscale: улучшить видимость результата
|
||||
|
||||
### 7.3 Дизайн-вопросы
|
||||
|
||||
- add-padding-png vs add-border-png: разница неочевидна, нужен review
|
||||
- change-aspect-ratio-png: не видно что меняется — рамка результата
|
||||
- watermark-tile-png: заполняет не полностью
|
||||
- sepia-png: нужна ли настройка силы эффекта?
|
||||
- posterize-png: настройка для каждого канала отдельно?
|
||||
- pixelate-png: разные алгоритмы?
|
||||
- swap-channels-png: обдумать flow через более простые инструменты
|
||||
- add-padding vs add-border: разница неочевидна, нужен review
|
||||
- change-aspect-ratio: не видно что меняется — рамка результата
|
||||
- watermark-tile: заполняет не полностью
|
||||
- sepia: нужна ли настройка силы эффекта?
|
||||
- posterize: настройка для каждого канала отдельно?
|
||||
- pixelate: разные алгоритмы?
|
||||
- swap-channels: обдумать flow через более простые инструменты
|
||||
|
||||
### 7.4 Аудит полей (компоненты)
|
||||
|
||||
- Range slider: поле ввода + кнопки +/- + кнопка сброса
|
||||
- Color picker: пипетка с картинки, прозрачность
|
||||
- Width/height с keep-aspect-ratio: единый компонент
|
||||
- Position offset: отдельные слайдеры x/y (уже сделано в `OffsetControl`)
|
||||
- Position offset: отдельные числовые поля X/Y (уже сделано в
|
||||
`fields/schema/OffsetControl.svelte`)
|
||||
|
||||
+23
-19
@@ -3,7 +3,7 @@
|
||||
> **Статус: active.** Этот план добавляет к `docs/roadmap.md` то, чего там нет:
|
||||
> **API** (фаза 9, возможная монетизация) и **PWA** (фаза 10, оффлайн +
|
||||
> установка), плюс шрифты (раздел 4 — частично относится к фазе 8, поэтому
|
||||
> решения-phase 8 здесь дублируются). WASM-ядро, эталоны и CLI — фазы 2–8
|
||||
> решения фазы 8 здесь дублируются). WASM-ядро, эталоны и CLI — фазы 2–8
|
||||
> `docs/roadmap.md`, они здесь не пересказываются. Оба трека стартуют после
|
||||
> wasm-фаз; PWA не ждёт API и может идти раньше.
|
||||
|
||||
@@ -11,19 +11,20 @@
|
||||
|
||||
- **Одно ядро — три потребителя.** Rust-крейт `core/` (roadmap фаза 4) даёт:
|
||||
wasm-пакет для сайта (фаза 5), нативный CLI (фаза 6), серверный слой API (фаза
|
||||
9). Единый контракт — JSON-пайплайн `{tool, params}` (заложен в roadmap §0,
|
||||
реализуется в фазе 11); эталонные файлы и harness (фазы 2, 3, 7) гарантируют
|
||||
одинаковый результат во всех рантаймах.
|
||||
9). Единый контракт — JSON-пайплайн `{id, params}` (заложен в roadmap §0 и
|
||||
совпадает с `Page.steps`, реализуется в фазе 11); эталонные файлы и harness
|
||||
(фазы 2, 3, 7) гарантируют одинаковый результат во всех рантаймах.
|
||||
- **API — нативный Rust на сервере, не wasm.** В браузере wasm нужен ради
|
||||
доставки и песочницы; на сервере крейт линкуется напрямую — быстрее и проще.
|
||||
Исключение — edge-воркеры (см. спайк ниже): там исполняется тот же
|
||||
wasm-артефакт фазы 5.
|
||||
- **PWA не трогает SEO-схему.** Сайт остаётся полностью prerendered-статикой;
|
||||
service worker добавляет оффлайн-слой поверх и не отвечает за HTML ботовам.
|
||||
- **Монетизация не ломает позиционирование.** Браузерные инструменты — бесплатны
|
||||
и без трекинга; API — отдельный продукт для автоматизации/бизнеса. Файлы в API
|
||||
обрабатываются в памяти: без хранения и логирования содержимого — продолжение
|
||||
«privacy»-истории.
|
||||
- **Монетизация не ломает позиционирование.** Браузерные инструменты —
|
||||
бесплатные, картинка обрабатывается в браузере и никуда не отправляется.
|
||||
Счётчик посещений GA4 этому не противоречит (границы — `docs/plan-seo.md` §0).
|
||||
API — отдельный продукт для автоматизации/бизнеса. Файлы в API обрабатываются
|
||||
в памяти: без хранения и логирования содержимого.
|
||||
|
||||
## 1. Фаза 9 — API (после фаз 5–7)
|
||||
|
||||
@@ -40,7 +41,7 @@
|
||||
запросам/размеру файла. Только после стабильного free tier.
|
||||
- [ ] **[A5]** Документация как лендинг: `/api` — индексируемая страница
|
||||
(туториал, примеры curl/JS), OpenAPI, ссылка из `llms.txt`. Dev-аудитория
|
||||
— источник бэклинков (план-seo S4c).
|
||||
— источник бэклинков (`docs/plan-seo.md`, S4c).
|
||||
- Гейт: один и тот же пайплайн даёт пиксельно идентичный результат в браузере,
|
||||
CLI и API (harness фазы 7 расширяется третьим рантаймом).
|
||||
|
||||
@@ -68,17 +69,17 @@
|
||||
фича — фаза 11 того же документа.
|
||||
- PWA — после S1 `plan-seo.md`; API — после фаз 5–7.
|
||||
- Синергия с дистрибуцией: «works offline, installable» и публичный API —
|
||||
аргументы для Product Hunt / alternative.to / GitHub (plan-seo S4c) и для
|
||||
GEO-текстов (`llms.txt`, описания категорий).
|
||||
аргументы для Product Hunt / alternative.to / GitHub (`docs/plan-seo.md`, S4c)
|
||||
и для GEO-текстов (`llms.txt`, описания категорий).
|
||||
|
||||
## 4. Шрифты: лицензирование и DOM-free рендер текста
|
||||
|
||||
Текст — один из canvas-зависимых классов, которые roadmap (фаза 8) держит в TS
|
||||
(там их четыре: текст, градиенты, штампы, `ctx.filter`); для текста выбран курс
|
||||
на реимплементацию, остальные классы не переносятся. Для CLI/API текст всё равно
|
||||
придётся рендерить в Rust, поэтому решение: **перевести text-инструменты в
|
||||
ядро** (в браузере canvas-рендер остаётся TS-фолбэком для A/B). Два вопроса —
|
||||
легальность шрифтов в дистрибутиве и рендер без DOM.
|
||||
(перечень — там же); для текста выбран курс на реимплементацию, остальные классы
|
||||
не переносятся. Для CLI/API текст всё равно придётся рендерить в Rust, поэтому
|
||||
решение: **перевести text-инструменты в ядро** (в браузере canvas-рендер
|
||||
остаётся TS-фолбэком для A/B). Два вопроса — легальность шрифтов в дистрибутиве
|
||||
и рендер без DOM.
|
||||
|
||||
**Когда начинать:** блока F1–F4 нет — они не привязаны к фазе roadmap, но F5/F6
|
||||
без них бессмысленны (нечего рендерить). Реализуется всё это в фазе 8, поэтому
|
||||
@@ -102,15 +103,18 @@
|
||||
`pnpm verify` по правилам `docs/quality-gates.md` (решение о fail-режиме —
|
||||
`docs/decisions.md`). Браузер грузит шрифты из бандла, не из ОС и не с
|
||||
`fonts.googleapis.com`-CDN (сторонний запрос = утечка IP пользователя,
|
||||
противоречит «no tracking»), — одинаковый результат у всех пользователей и
|
||||
во всех рантаймах.
|
||||
лишняя зависимость от чужой инфраструктуры), — одинаковый результат у всех
|
||||
пользователей и во всех рантаймах.
|
||||
- [ ] **[F4]** Кириллица: приоритет семействам с полным покрытием — Liberation
|
||||
Sans, PT Sans/PT Serif, Noto Sans, Inter, Roboto (OFL/Apache), DejaVu Sans
|
||||
(пермиссивная лицензия Bitstream Vera: редистрибуция и коммерческое
|
||||
использование разрешены) — закрывает и backlog-вопрос про «только с
|
||||
кириллицей».
|
||||
- **Стартовый набор** (все OFL/Apache, с кириллицей; состав не догма —
|
||||
расширяется по запросам, каждый проходит паспорт F3):
|
||||
расширяется по запросам, каждый проходит паспорт F3). Сейчас в бандле сайта
|
||||
лежат `@fontsource/ibm-plex-sans` и `@fontsource/ibm-plex-mono` (тоже OFL) — в
|
||||
наборе ниже их нет, и при введении паспорта F3 их судьбу нужно решить явно:
|
||||
оставить или заменить.
|
||||
- базовый sans: **Inter** (дефолт add-text), **PT Sans** / **Golos Text**
|
||||
(профессиональная русская типографика), **Noto Sans** (фолбэк покрытия);
|
||||
- serif: **PT Serif**, **Lora**, **Playfair Display** (display);
|
||||
|
||||
+58
-37
@@ -24,8 +24,9 @@
|
||||
> схему сайта они не меняют.
|
||||
>
|
||||
> **Слаги в этом документе:** `jpg-to-png`, `crop-jpeg` и подобное — интенты и
|
||||
> URL конкурента, а не id наших инструментов. Наши id — в `docs/tools-map.md`; в
|
||||
> тексте ниже реальные id инструментов приводятся в бэктиках.
|
||||
> URL конкурента, а не наши страницы. Наши страницы — в `docs/tools-map.md`
|
||||
> (карта перечисляет `slug`, не `id`); в тексте ниже названия инструментов
|
||||
> приводятся по этим слагам, потому что для SEO важны адреса.
|
||||
|
||||
## 0. Решения
|
||||
|
||||
@@ -72,9 +73,14 @@
|
||||
`registry/pages/`. Схему не менять без отдельного SEO-плана: после индексации
|
||||
переименование URL означает 301 и потерю позиций. Разделение инструмента и
|
||||
страницы (решение выше) адресов не меняет.
|
||||
- **Без GA и рекламных скриптов:** аналитика — self-hosted Plausible/Umami или
|
||||
Cloudflare Web Analytics. «No tracking» — часть позиционирования, её нельзя
|
||||
подрывать собственными скриптами.
|
||||
- **Аналитика — GA4, в режиме «только агрегаты».** GA4 уже стоит в проде
|
||||
(`web/src/app.html`, prerender-оболочка, значит на каждой странице). Решение —
|
||||
не выпиливать, а ограничить: cookieless-конфигурация, без рекламных audiences,
|
||||
signals и ремаркетинга. Граница приватности проходит по данным изображения: GA
|
||||
не видит ни файла, ни его содержимого, ни результата инструмента — только факт
|
||||
открытия страницы. **Рекламу и ремаркетинг не ставим** — они противоречат
|
||||
позиционированию, в отличие от счётчика посещений. Формулировку «без трекинга»
|
||||
на публичных страницах не использовать: она теперь неточна, см. §1 и S4b.
|
||||
- **Честные методы.** Никакого клонированного текста, дорвеев, PBN. Контент — по
|
||||
каждому инструменту, с ручной вычиткой.
|
||||
|
||||
@@ -105,10 +111,10 @@
|
||||
| `rel=canonical` | нет | S1 |
|
||||
| OG/Twitter-теги, OG-картинки | нет | S1 |
|
||||
| Канонический домен в абсолютных URL | нет, деплой на GitHub Pages с базой | S1 |
|
||||
| Контент страницы (H1-текст, how-to, FAQ, related) | только title/description | S2 |
|
||||
| Контент страницы (how-to, FAQ, related) | title, robots и отрендеренный H1 | S2 |
|
||||
| JSON-LD (SoftwareApplication, FAQPage, Breadcrumb) | нет | S3 |
|
||||
| `llms.txt`, хабы категорий с текстом | нет | S3 |
|
||||
| GSC + приватная аналитика | нет | S4 |
|
||||
| GSC + доводка GA до cookieless-конфигурации | GA4 стоит, GSC не подключён | S4 |
|
||||
|
||||
Строка `noindex` — первая не потому, что её сложнее всего, а потому, что она
|
||||
блокирует всё остальное: пока тег стоит, остальные строки таблицы не дают
|
||||
@@ -150,7 +156,7 @@
|
||||
ссылок на инструменты. Но это этап S3e и только после того, как написано ядро
|
||||
S2: транзакционная страница приносит конверсию, информационная — только
|
||||
трафик. Тонкие FAQ-страницы «про всё» не заводить — несколько сильных
|
||||
материалов с ссылками на 3–5 инструментов каждый.
|
||||
материалов с ссылками на несколько связанных инструментов.
|
||||
6. **Дистрибуция.** GitHub-репо (звёзды/форки = сигналы), alternative.to,
|
||||
Product Hunt, Reddit по интенту — реальный путь набрать первые бэклинки без
|
||||
бюджетов.
|
||||
@@ -175,16 +181,20 @@
|
||||
|
||||
- [ ] **[S1e]** OG-картинки per-tool: генерация PNG-карточек скриптом на
|
||||
существующем ядре (мы сами умеем рисовать PNG) — имя инструмента + бренд,
|
||||
файл `static/og/[slug].png`, ссылка из `Seo.svelte`. Относительные пути
|
||||
(`/og/crop-png.png`) работают без домена; при деплое на собственном домене
|
||||
файл `static/og/[slug].png`, ссылка из `Seo.svelte`. Путь к картинке
|
||||
строить через `resolve()` из `$app/paths` (как favicon в
|
||||
`+layout.svelte`), а не литералом `/og/…`: под `BASE_PATH` корневой путь
|
||||
ломается. Домен для этого не нужен; при деплое на собственном домене
|
||||
OG-картинки будут абсолютными и корректными.
|
||||
- [ ] **[S1f]** Download-кнопка с селектором формата PNG/JPG/WebP/BMP (+quality
|
||||
для lossy) для всех инструментов — механика `Tool.output` уже есть
|
||||
(`docs/backlog.md`, «Идеи» №6, UX-райз «Download»). Продуктовая основа
|
||||
«image tools»-позиционирования: без неё сайт остаётся PNG-сайтом. Вести
|
||||
параллельно с текущими quality-задачами. Побочно снимает `quality` из схем
|
||||
страниц — после этого 14 convert-страниц сводятся на один инструмент
|
||||
(`docs/backlog.md`).
|
||||
**конвертеров** — после этого convert-страницы сводятся на один инструмент
|
||||
(`docs/backlog.md`). Не из всех схем: у `jpeg-artifacts` `quality` —
|
||||
функциональное поле (сила артефактов), а `output` у инструмента нет, так
|
||||
что селектор формата его не покрывает.
|
||||
- [ ] **[S1g]** Поведение конвертеров после S1f: не предлагать в меню форматов
|
||||
формат исходника (disable) — чтобы `convert-png-to-jpg` не мог сохранить в
|
||||
png. Существующие страницы не трогать: `convert-png-to-jpg`,
|
||||
@@ -198,10 +208,12 @@
|
||||
`https://<юзер>/<repo>/…` на корневые URL (301). Делается одновременно с
|
||||
S1d (canonical), чтобы Google проиндексировал сразу с правильным доменом.
|
||||
- [ ] **[S1b]** Скрипт `web/scripts/gen-sitemap.mjs`: генерирует `sitemap.xml` в
|
||||
`build/` из `TOOLS` реестра (`web/src/lib/registry`) + статических
|
||||
страниц. Вызов из `pnpm build` после `vite build` (adapter-static пишет в
|
||||
`build/`, дописываем файл там же). Origin — из env (`PUBLIC_SITE_ORIGIN`),
|
||||
учёт опционального `BASE_PATH`.
|
||||
`build/` из `PAGES` реестра (`web/src/lib/registry`) + статических
|
||||
страниц. Взять адреса у страниц, а не у инструментов: страниц может быть
|
||||
несколько на один инструмент, у инструмента адреса нет. Вызов из
|
||||
`pnpm build` после `vite build` (adapter-static пишет в `build/`,
|
||||
дописываем файл там же). Origin — из env (`PUBLIC_SITE_ORIGIN`), учёт
|
||||
опционального `BASE_PATH`.
|
||||
- [ ] **[S1c]** `robots.txt`: `Sitemap:` + закрыть служебный `/kit`; после
|
||||
cleanup — вернуть минимальный allow-all.
|
||||
- [ ] **[S1d]** Компонент `Seo.svelte` (kit): canonical (абсолютный URL по
|
||||
@@ -211,9 +223,9 @@
|
||||
|
||||
> **Порядок S1:** S1e/S1f/S1g → покупка домена → S1a+S1b+S1c+S1d → **S1h**
|
||||
> (снятие `noindex`) → S2-контент. Гейты: `pnpm --dir web build` зелёный; в
|
||||
> `build/` лежит корректный `sitemap.xml` со всеми инструментами; canonical/OG
|
||||
> присутствуют на всех страницах; `noindex` отсутствует в собранном HTML
|
||||
> (`pnpm --dir web build` и проверка `build/tools/*/index.html`);
|
||||
> `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).
|
||||
@@ -229,9 +241,9 @@
|
||||
(`ol`), описание параметров (из схемы), FAQ, related tools (перелинковка
|
||||
внутри категории + 2–3 «соседних» интента).
|
||||
- [ ] **[S2c]** Хлебные крошки (Каталог → Категория → Инструмент) в UI.
|
||||
- [ ] **[S2d]** Первая волна контента — топ-30 инструментов ядра (приоритет по
|
||||
вордстату: transparent png, jpg→png, compress, resize, crop, rotate,
|
||||
circle crop, add text, pixelate, grayscale…).
|
||||
- [ ] **[S2d]** Первая волна контента — топ приоритетных инструментов ядра
|
||||
(приоритет по вордстату: transparent png, jpg→png, compress, resize, crop,
|
||||
rotate, circle crop, add text, pixelate, grayscale…); отбор — §5.
|
||||
- Гейты: на каждой странице инструмента — уникальный текст ≥ 300 слов, H1
|
||||
совпадает с интентом, FAQ ≥ 3, related ≥ 3; текст в статике (prerender), не в
|
||||
рантайме.
|
||||
@@ -266,8 +278,11 @@
|
||||
|
||||
- [ ] **[S4a]** Google Search Console: собственность на домен, отправка sitemap,
|
||||
мониторинг индексации и запросов. Бесплатно и обязательно.
|
||||
- [ ] **[S4b]** Аналитика без трекинга: self-hosted Plausible/Umami либо
|
||||
Cloudflare Web Analytics. GA/ЯМ не ставим (позиционирование).
|
||||
- [ ] **[S4b]** Довести GA4 до решения §0: cookieless-конфигурация, отключённые
|
||||
рекламные audiences и signals, `anonymize_ip`, запрет на передачу
|
||||
параметров с данными картинки. Плюс отсечь мусорные попадания:
|
||||
prerender-оболочка грузит тег и на локальных сборках, их нужно отсекать
|
||||
флагом окружения. GA/ЯМ как замена счётчику не рассматриваем.
|
||||
- [ ] **[S4c]** Дистрибуция: закрепить GitHub-репо (README со скриншотами),
|
||||
подача на alternative.to (страница против конкурента), Product Hunt,
|
||||
тематические Reddit/HN-треды. Бэклинки — главный дефицит для конкуренции.
|
||||
@@ -279,19 +294,23 @@
|
||||
|
||||
## 5. Приоритизация инструментов (первая волна контента)
|
||||
|
||||
Источник — `docs/tools-map.md` (наш состав и замер по конкуренту — §2). Ids ниже
|
||||
— **фактические id реестра**; первая волна — топ-интенты, где конкуренты
|
||||
ранжируются, а у нас уже есть инструмент и он не хуже:
|
||||
Источник — `docs/tools-map.md` (наш состав и замер по конкуренту — §2). Слаги
|
||||
ниже — **фактические `slug` страниц** из `docs/tools-map.md` §1; первая волна —
|
||||
топ-интенты, где конкуренты ранжируются, а у нас уже есть инструмент и он не
|
||||
хуже:
|
||||
|
||||
- прозрачность/фон: create-empty-png, remove-background-png, change-png-opacity,
|
||||
set-alpha-channel-png
|
||||
set-alpha-channel-png, round-corners-png
|
||||
- конвертация: convert-png-to-jpg, convert-png-to-webp, png-to-bmp, svg-to-png,
|
||||
png-to-base64
|
||||
- геометрия: crop-png, resize-png, rotate-png, round-corners-png, flip-png,
|
||||
change-aspect-ratio-png
|
||||
- цвет/фильтры: grayscale-png, invert-colors-png, pixelate-png, add-text-png,
|
||||
watermark-tile-png
|
||||
- генерация: linear-gradient-png, create-empty-png
|
||||
- геометрия: crop-png, resize-png, rotate-png, flip-png, change-aspect-ratio-png
|
||||
- цвет/фильтры: grayscale-png, invert-colors-png, pixelate-png
|
||||
- текст: add-text-png, watermark-tile-png
|
||||
- генерация: linear-gradient-png
|
||||
|
||||
> Группы здесь — по категории страницы в реестре (`registry/pages/`), а не по
|
||||
> смыслу: `round-corners-png` лежит в `alpha`, `add-text-png` и
|
||||
> `watermark-tile-png` — в `text`.
|
||||
|
||||
> **Открытый вопрос по интентам «из формата в PNG».** У конкурента есть
|
||||
> `jpg-to-png`, `webp-to-png`, `gif-to-png` как отдельные страницы; у нас таких
|
||||
@@ -380,9 +399,10 @@ with PNG, JPG, WebP» в тексте + селектор формата в Downl
|
||||
- [ ] **[R1]** Механический переезд бренда на новое имя. Список файлов и строк с
|
||||
хардкодом — в локальном плане (раздел «Где живёт бренд в коде»); здесь
|
||||
только SEO-ограничения. **НЕ трогать:** адреса страниц инструментов
|
||||
(`/tools/crop-png`), ключи localStorage (`easy-png-tools:*` — или
|
||||
мигрировать отдельной мелочью), историю `docs/archive`. Бренд должен стать
|
||||
i18n-константой, а не тремя независимыми хардкодами.
|
||||
(`/tools/crop-png`) и localStorage-ключи — `theme` и `locale` лежат без
|
||||
префикса бренда, поэтому переименование с ними не столкнётся (проверено в
|
||||
`theme.svelte.ts` и `i18n/locale.svelte.ts`); историю `docs/archive`.
|
||||
Бренд должен стать i18n-константой, а не тремя независимыми хардкодами.
|
||||
- [ ] **[R2]** Купить выбранный домен (кандидаты и пробы — в локальном плане) и
|
||||
перевести прод на него без `BASE_PATH` (§0). Домен нужен до S1-domain:
|
||||
canonical и sitemap генерятся от `PUBLIC_SITE_ORIGIN`.
|
||||
@@ -396,7 +416,8 @@ with PNG, JPG, WebP» в тексте + селектор формата в Downl
|
||||
- Клонирование всего каталога конкурента (низкокачественные дубли + нет ресурсов
|
||||
на контент) — вместо этого ядро + обоснованные хвосты.
|
||||
- RU-локаль в индексе до решения о структуре `/ru/` + hreflang.
|
||||
- GA/реклама/трекинг — противоречит позиционированию.
|
||||
- Реклама, ремаркетинг, pixels сторонних сетей — противоречит позиционированию.
|
||||
Счётчик посещений GA4 — не противоречит, условия ограничений — §0 и S4b.
|
||||
- Дорвеи, программные зеркала, покупные ссылки.
|
||||
- Массово-сгенерированные форматные клоны (`crop-jpeg`/`crop-webp` как копии
|
||||
`crop-png` с подстановкой формата в шаблонный текст) — doorway-паттерн, риск
|
||||
|
||||
@@ -98,8 +98,10 @@ download, темы или локализации. Изменения runtime fix
|
||||
pattern-based policy для временно неиспользуемых `tools.*.params`; это не
|
||||
молчаливый `allowPaths`-список.
|
||||
|
||||
`lint:all` может завершиться успешно при warnings — это не означает чистый
|
||||
baseline. Агент показывает число errors, warnings и token warnings отдельно.
|
||||
`lint:all` может завершиться успешно, напечатав unused-token сообщения: это
|
||||
единственный неблокирующий сигнал в нём. ESLint warnings, наоборот, роняют gate,
|
||||
поэтому «прошёл» ≠ «нечего чинить». Агент показывает число errors, warnings и
|
||||
token warnings раздельно.
|
||||
|
||||
## CI
|
||||
|
||||
|
||||
+6
-5
@@ -1,6 +1,6 @@
|
||||
# План развития: easy-png-tools
|
||||
|
||||
> **Статус:** фаза 1 выполнена — сайт живёт на типизированном реестре,
|
||||
> **Статус: active.** Фаза 1 выполнена — сайт живёт на типизированном реестре,
|
||||
> production UI — корневые маршруты. Текущий фокус: SEO S1-free
|
||||
> (`docs/plan-seo.md`) + открытые задачи (`docs/backlog.md`). Домен нужен для
|
||||
> SEO (S1-domain). Фазы 2–8 — будущие; 9–10 спланированы в
|
||||
@@ -19,16 +19,17 @@
|
||||
слои) — `docs/architecture.md`, разделы 2–3.
|
||||
|
||||
- **Набор утилит для изображений, всё в браузере** (SvelteKit +
|
||||
`adapter-static`), данные не покидают машину.
|
||||
`adapter-static`): файл картинки не покидает машину. Счётчик посещений GA4
|
||||
этому не противоречит — границы `docs/plan-seo.md`, §0.
|
||||
- **Первый и главный приоритет — полностью рабочий сайт**: максимум простых
|
||||
инструментов, UX/UI, пайплайны (несколько инструментов подряд), описания
|
||||
инструментов.
|
||||
- **Никакого wasm/Rust на этом этапе.** TS-реализация на canvas + `ImageData` —
|
||||
эталон алгоритмов, из которого позже генерируются эталонные файлы для проверки
|
||||
Rust-порта. Rust-ядро, WASM и CLI — отдельные фазы позже, когда сайт живёт.
|
||||
- **Пайплайн = JSON-список шагов** `{tool, params}`. Формат зафиксирован этим
|
||||
решением и станет общим для браузера, wasm и CLI; реализуется в фазе 11, не в
|
||||
фазе 1.
|
||||
- **Пайплайн = JSON-список шагов** `{id, params}` — формат шага совпадает с
|
||||
полем `Page.steps`. Формат зафиксирован этим решением и станет общим для
|
||||
браузера, wasm и CLI; реализуется в фазе 11, не в фазе 1.
|
||||
- Валидация будущего Rust-порта — эталонные файлы, сгенерированные из
|
||||
TS-реализации. Сравнение: **бит-в-бит** для чистых пиксельных операций,
|
||||
**perceptual diff** для canvas-зависимых (правила сравнения — раздел 3).
|
||||
|
||||
@@ -105,13 +105,25 @@ non-blocking E2E не считается заменой локальному smo
|
||||
|
||||
`web/playwright.config.ts` запускает четыре проекта:
|
||||
|
||||
| Проект | Движок | Спецификации |
|
||||
| ----------------- | -------- | ---------------------------------------------------------------------- |
|
||||
| `chromium` | Chromium | все e2e |
|
||||
| `firefox` | Firefox | `pipeline`, `png-fixtures`, `text-and-verdicts` |
|
||||
| `webkit` | WebKit | `pipeline`, `png-fixtures`, `text-and-verdicts` |
|
||||
| `mobile-chromium` | Chromium | `navigation`, `catalog`, `generators`, `pipeline`, 390×844, `hasTouch` |
|
||||
| Проект | Движок | Спецификации |
|
||||
| ----------------- | -------- | ------------------------------------------------------------------------ |
|
||||
| `chromium` | Chromium | все e2e |
|
||||
| `firefox` | Firefox | `tool-flows`, `png-fixtures`, `text-and-verdicts` |
|
||||
| `webkit` | WebKit | `tool-flows`, `png-fixtures`, `text-and-verdicts` |
|
||||
| `mobile-chromium` | Chromium | `navigation`, `catalog`, `generators`, `tool-flows`, 390×844, `hasTouch` |
|
||||
|
||||
- Название `tool-flows.spec.ts` описывает содержание: сквозной поток одного
|
||||
инструмента (загрузка → правка параметра → результат → download) на наборе
|
||||
представительных страниц. Пайплайна в коде нет (`docs/architecture.md`, раздел
|
||||
1), поэтому спеку нельзя называть `pipeline` — при её переименовании поправить
|
||||
`browserContractTests` и `mobileTests` в `web/playwright.config.ts` и
|
||||
`allowDefaultProject` в `web/eslint.config.js`.
|
||||
- Рядом живёт `tools-smoke.spec.ts` — дешёвый проход по выборке страниц («выдал
|
||||
результат без ошибок»): по одной-двум на категорию, а не по каждому
|
||||
инструменту. Остальные страницы закрыты точечными спеками (`generators`,
|
||||
`text-and-verdicts`, `tool-flows`). `tool-flows` отвечает за более глубокие
|
||||
проверки, `tools-smoke` — за дешёвый прогон подряд; дублировать их цели не
|
||||
стоит.
|
||||
- Новые engine-специфичные фичи сначала добавляются в `chromium`, потом
|
||||
решается, попадает ли spec в `browserContractTests`.
|
||||
- Расхождения декодеров не ужесточаются и не скрываются skip-ом: они описаны в
|
||||
|
||||
+29
-16
@@ -3,19 +3,24 @@
|
||||
> Статус: **active**. **Дата прохода: 2026-09-13** (инструменты и поля). Здесь
|
||||
> только «что плохо» — наблюдения, без порядка работ и без критериев приёмки.
|
||||
> Новый проход — новая дата сверху; закрытая находка не удаляется, а помечается
|
||||
> ссылкой на задачу в `docs/backlog.md`.
|
||||
> ссылкой на задачу в `docs/backlog.md` или на уже сделанное в коде.
|
||||
>
|
||||
> **Слаги сверены с реестром** на дату прохода (`docs/tools-map.md` §1).
|
||||
> Отдельно: `webp-to-png` и `color-wheel-generator` в коде отсутствуют (реальная
|
||||
> страница — `color-wheel-png`), а `png-info` — ещё не заведённая страница
|
||||
> (`docs/backlog.md`, «Идеи»). Такие пункты переименовываются по `PAGES`, а не
|
||||
> resurrect'ятся. Перед работой по любому пункту — найти соответствующую
|
||||
> страницу в `web/src/lib/registry/pages/` или `docs/tools-map.md`; ищи по
|
||||
> смыслу: категория в заголовке раздела + ключевое слово в пункте.
|
||||
>
|
||||
> **Заголовки разделов ниже — это id категорий из `web/src/lib/categories.ts`**,
|
||||
> по ним и ищи страницу в `registry/pages/<group>.ts`. Раздела `convert` в
|
||||
> аудите нет: конвертация проверялась вместе с остальными инструментами,
|
||||
> отдельного прохода по ней не было.
|
||||
>
|
||||
> Как исправлять — `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 уже фактические.
|
||||
|
||||
## везде
|
||||
|
||||
@@ -23,7 +28,7 @@
|
||||
- инструменты без параметров выглядят странно, пересмотреть, может добавить
|
||||
какую-то надпись "нет параметров"
|
||||
- удаление\маски и т.д. - надо добавить галочку сглаживание
|
||||
- производительность: почему flip заметно тормозит, но нет индикации работы
|
||||
- производительность: почему flip-png заметно тормозит, но нет индикации работы
|
||||
инструмента (также sharpen-png)
|
||||
- производительность: исследовать, есть ли смысл на инструментах геометрии
|
||||
выводить рамку результата до полноценной работы (например на skew \ rotate
|
||||
@@ -31,7 +36,9 @@
|
||||
|
||||
## аудит инструментов
|
||||
|
||||
### transparency
|
||||
### alpha
|
||||
|
||||
(в UI раздел называется «Transparency» / «Прозрачность»)
|
||||
|
||||
- add-stroke-png: добавить галочку "расширить на толщину линии"
|
||||
- find-contour-png: если толщина линии более 1px - круг боле с искажениями, надо
|
||||
@@ -64,7 +71,7 @@
|
||||
полупрозрачной областью - закрывает всё, по описанию должен закрывать только
|
||||
небольшие дырки
|
||||
|
||||
### colors
|
||||
### color
|
||||
|
||||
- two-colors-png: в range slider добавить описание что это luminance threshold
|
||||
- gamma-png: проверить правильность работы - яркое изображение выглядит не
|
||||
@@ -95,8 +102,10 @@
|
||||
- add-border-png: прозрачность цвета (аудит полей)
|
||||
- fit-on-background-png: цвет
|
||||
- change-canvas-size-png: использовать position9 вместо селекта
|
||||
- resize-png: по умолчанию размеры 0х0, надо поменять; выбор метода
|
||||
масштабирования
|
||||
- resize-png: по умолчанию размеры 0х0 — сделано, дефолт берётся из source
|
||||
(`defaultFromSource`), до загрузки 1×1. Выбор метода масштабирования — поля в
|
||||
схеме нет вовсе, это не правка селекта, а новое поле
|
||||
(`docs/plan-fields-audit-fixes.md` §7.1)
|
||||
- crop-png: offset - слишком большие значения
|
||||
- rotate-png: кнопки вместо select
|
||||
- flip-png: кнопки вместо select
|
||||
@@ -143,7 +152,9 @@
|
||||
- png-is-grayscale: результат плохо виден, нужно ли копирование на полный
|
||||
вердикт? может отдельные поля - 0\1, yes\no?
|
||||
|
||||
### generators
|
||||
### generate
|
||||
|
||||
(в UI раздел называется «Generate» / «Генерация»)
|
||||
|
||||
- color-spectrum-png: кнопки вместо select
|
||||
- draw-grid-png: выбор цвета фона
|
||||
@@ -166,7 +177,9 @@
|
||||
## аудит полей
|
||||
|
||||
- у полей plate, position9 и font-style вторая подпись дублирует первую
|
||||
- position offset: должны быть отдельные range slider на x и y
|
||||
- position offset: сделано — отдельные числовые поля X и Y в
|
||||
`fields/schema/OffsetControl.svelte` (не range slider); задача на px/% вместо
|
||||
них открыта, `docs/plan-fields-audit-fixes.md` §7.1
|
||||
- range slider: поле ввода, кнопки + и -, кнопка сброса на умолчание
|
||||
- range slider with presets: слайдер, как выше, добавочные кнопки с пресетами
|
||||
- color picker: пикер с картинки (как исходника, так и результата), прозрачность
|
||||
|
||||
+167
-152
@@ -1,187 +1,190 @@
|
||||
# Карта инструментов: реализовано · добавить · идеи
|
||||
|
||||
> Статус: **active**. Живой документ для сверки с каталогом.
|
||||
>
|
||||
> **Карта перечисляет `slug` страниц (`web/src/lib/registry/pages/`) — то есть
|
||||
> адреса вида `/tools/crop-png`:** это `slug: "crop-png"` у страницы, которая
|
||||
> вызывает инструмент `id: "crop"`. Внутренние имена инструментов png-интента не
|
||||
> держат, поэтому они от карты отделены; модель — `docs/architecture.md`,
|
||||
> раздел 3. Источник правды — реестр. Этот список навигационный: сверять перед
|
||||
> использованием. Формат записи: `slug — параметры через запятую`. Сравнение с
|
||||
> каталогом источника (объём, нишевые клоны серий Logo*/Icon*/Stamp*Signature*)
|
||||
> — замер в `docs/plan-seo.md`, §2; здесь он не дублируется.
|
||||
>
|
||||
> Статус: **active**. Навигационный список: что уже есть по адресам
|
||||
> `/tools/<slug>`, что можно добавить, что отложено. Источник правды — реестр
|
||||
> (`web/src/lib/registry/`), модель страницы и инструмента —
|
||||
> `docs/architecture.md`, раздел 3; сверяться с реестром перед использованием.
|
||||
> Количество инструментов здесь не фиксируется — оно выводится из раздела 1 и из
|
||||
> `TOOLS` в `registry/index.ts`, и дублировать его цифрой значит заставлять себя
|
||||
> обновлять документ при каждом добавлении (`AGENTS.md`).
|
||||
> `PAGES` (`AGENTS.md`).
|
||||
>
|
||||
> **Как читать строку.** `slug` — адрес страницы и имя скачиваемого файла;
|
||||
> страница вызывает инструмент через `steps[0].id`, и у `id` нет png-интента
|
||||
> (`crop-png` → `id: "crop"`, `png-to-hsl` → `id: "to-hsl"`). Имена полей — это
|
||||
> id полей из `schema.fields` инструмента; составные поля (`size`, `pair`,
|
||||
> `offset`, `style`, `plate`, `gradient`) в скобках раскрыты, но не выдуманы как
|
||||
> отдельные поля. Сравнение с каталогом источника (объём, нишевые клоны серий
|
||||
> Logo\*/Icon\*/Stamp\*) — замер в `docs/plan-seo.md` §2; здесь он не
|
||||
> дублируется.
|
||||
>
|
||||
> **Формат строки (проверяется `web/src/lib/registry/tools-map-doc.test.ts`):**
|
||||
> всё **до первого тире** `—` — это slug страниц через `/` или `,`; всё после
|
||||
> тире — свободное описание параметров и не разбирается. Второй slug нельзя
|
||||
> оставлять после тире, иначе он не попадёт в разбор и страница будет считаться
|
||||
> неописанной.
|
||||
|
||||
---
|
||||
|
||||
## 1. Реализовано
|
||||
|
||||
Ids приведены к фактическим в `web/src/lib/registry/pages/`.
|
||||
Slugs приведены к фактическим в `web/src/lib/registry/pages/`. Разделы идут по
|
||||
файлу реестра; поля — по схеме инструмента, который страница вызывает.
|
||||
|
||||
**Формат строки (проверяется тестом
|
||||
`web/src/lib/registry/tools-map-doc.test.ts`):** в буллите всё **до первого
|
||||
тире** `—` — это slug страниц через `/` или `,`; всё после тире — свободное
|
||||
описание параметров и не разбирается. Второй slug нельзя оставлять после тире,
|
||||
иначе он не попадёт в разбор и страница будет считаться неописанной.
|
||||
|
||||
### Конвертация (`convert.ts`)
|
||||
### Конвертация (`convert`)
|
||||
|
||||
Отдельных «из формата в PNG» инструментов **нет**: входной формат выбирается
|
||||
загрузкой файла, любой инструмент принимает PNG/JPG/WebP/GIF/BMP. Все конвертеры
|
||||
исходят из PNG.
|
||||
|
||||
- svg-to-png — width результата (текстовый вход: SVG-код)
|
||||
- png-to-bmp — 24-бит, фон вместо альфы
|
||||
- convert-png-to-jpg — background, quality
|
||||
- convert-png-to-webp — quality
|
||||
- png-to-base64 / base64-to-png — строка
|
||||
- png-to-data-uri / data-uri-to-png — строка
|
||||
- png-to-hex / hex-to-png — rrggbbaa по строкам / tokens + width
|
||||
- png-to-bytes / bytes-to-png — десятичные RGBA-байты по строкам / tokens +
|
||||
width
|
||||
- png-to-rgb-values / rgb-values-to-png — rgba(r,g,b,a) по пикселям / числа +
|
||||
width
|
||||
- `convert-png-to-jpg` — background, quality
|
||||
- `convert-png-to-webp` — quality
|
||||
- `png-to-bmp` — без параметров (24-бит, фон вместо альфы)
|
||||
- `svg-to-png` — width (ширина результата; вход — SVG-код)
|
||||
- `png-to-base64`, `base64-to-png` — без параметров (строка)
|
||||
- `png-to-data-uri`, `data-uri-to-png` — без параметров (строка)
|
||||
- `png-to-hex`, `hex-to-png` — вход `width`; rrggbbaa по строкам / tokens
|
||||
- `png-to-bytes`, `bytes-to-png` — вход `width`; десятичные RGBA-байты по
|
||||
строкам / tokens
|
||||
- `png-to-rgb-values`, `rgb-values-to-png` — вход `width`; rgba(r,g,b,a) по
|
||||
пикселям / числа
|
||||
|
||||
### Прозрачность (`alpha.ts`)
|
||||
### Прозрачность (`alpha`)
|
||||
|
||||
- change-png-opacity — percent
|
||||
- set-alpha-channel-png — percent
|
||||
- remove-alpha-channel-png — без параметров (фон `#ffffff` захардкожен, см.
|
||||
`docs/plan-fields-audit-fixes.md` D1)
|
||||
- extract-alpha-mask-png — без параметров
|
||||
- invert-alpha-png — без параметров
|
||||
- remove-background-png — color, tolerance, outerOnly, smooth
|
||||
- remove-color-from-png — targetColor, tolerance
|
||||
- add-stroke-png — color, thickness
|
||||
- find-contour-png — color, thickness
|
||||
- make-thicker-png / make-thinner-png — radius
|
||||
- harden-alpha-png — threshold
|
||||
- feather-edges-png — radius (размытие только альфы)
|
||||
- clean-edges-png — radius (defringe: RGB от ближайшего непрозрачного)
|
||||
- despeckle-alpha-png / close-holes-png — radius
|
||||
- center-by-alpha-png — без параметров
|
||||
- round-corners-png — radius
|
||||
- circle-mask-png — size (диаметр, % меньшей стороны), offsetX, offsetY
|
||||
- square-mask-png — widthPct, heightPct, offsetX, offsetY
|
||||
- star-mask-png — points, innerRadius, size, rotation, offsetX, offsetY
|
||||
- wavy-mask-png — size, amplitude, waves, phase, offsetX, offsetY
|
||||
- `add-stroke-png` — color, thickness
|
||||
- `find-contour-png` — color, thickness
|
||||
- `remove-color-from-png` — targetColor, tolerance
|
||||
- `remove-background-png` — color, tolerance, outerOnly, smooth
|
||||
- `make-thicker-png`, `make-thinner-png` — radius
|
||||
- `harden-alpha-png` — threshold
|
||||
- `feather-edges-png` — radius (размытие только альфы)
|
||||
- `clean-edges-png` — radius (defringe: RGB от ближайшего непрозрачного)
|
||||
- `despeckle-alpha-png`, `close-holes-png` — radius
|
||||
- `round-corners-png` — radius (% от половины меньшей стороны)
|
||||
- `invert-alpha-png` — без параметров
|
||||
- `extract-alpha-mask-png` — без параметров
|
||||
- `set-alpha-channel-png` — percent
|
||||
- `remove-alpha-channel-png` — без параметров (фон `#ffffff` захардкожен,
|
||||
`docs/plan-fields-audit-fixes.md` FA-D1)
|
||||
- `circle-mask-png` — size (диаметр, % меньшей стороны), offset (x/y)
|
||||
- `square-mask-png` — widthPct, heightPct, offset (x/y)
|
||||
- `star-mask-png` — points, innerRadius, size, rotation, offset (x/y)
|
||||
- `wavy-mask-png` — size, amplitude, waves, phase, offset (x/y)
|
||||
|
||||
### Цвет (`color.ts`)
|
||||
### Цвет (`color`)
|
||||
|
||||
- grayscale-png / invert-colors-png / sepia-png / auto-contrast-png — без
|
||||
- `grayscale-png`, `invert-colors-png`, `sepia-png`, `auto-contrast-png` — без
|
||||
параметров
|
||||
- adjust-brightness-contrast-png — brightness, contrast
|
||||
- change-png-hue — degrees
|
||||
- extract-channel-png — channel (r/g/b)
|
||||
- swap-channels-png — pair (r-g/r-b/g-b)
|
||||
- black-and-white-png — threshold
|
||||
- posterize-png — levels
|
||||
- two-colors-png — lightColor, darkColor, threshold
|
||||
- temperature-png — percent
|
||||
- gamma-png — value
|
||||
- tint-png — color, strength
|
||||
- quantize-png — colors (k, median-cut)
|
||||
- decrease-color-count-png — maxColors (пресеты 2…256)
|
||||
- custom-palette-png — colors (hex через запятую, ближайший цвет)
|
||||
- dithering-png — colors (k), pattern (Floyd–Steinberg / Bayer 4×4)
|
||||
- `adjust-brightness-contrast-png` — brightness, contrast
|
||||
- `change-png-opacity` — percent
|
||||
- `change-png-hue` — degrees
|
||||
- `temperature-png` — percent
|
||||
- `gamma-png` — value
|
||||
- `tint-png` — color, strength
|
||||
- `two-colors-png` — pair (светлый/тёмный), threshold
|
||||
- `black-and-white-png` — threshold
|
||||
- `posterize-png` — levels
|
||||
- `quantize-png` — colors (k, median-cut)
|
||||
- `decrease-color-count-png` — maxColors (2…256)
|
||||
- `custom-palette-png` — colors (hex через запятую, ближайший цвет)
|
||||
- `dithering-png` — colors (k), pattern (Floyd–Steinberg / Bayer 4×4)
|
||||
- `extract-channel-png` — channel (r/g/b)
|
||||
- `swap-channels-png` — pair (r-g/r-b/g-b)
|
||||
|
||||
### Разложение каналов (`color.ts`)
|
||||
### Разложение каналов (`color`)
|
||||
|
||||
- png-to-hsl, png-to-hsv, png-to-hsi, png-to-cmyk, png-to-ycbcr, png-to-lab —
|
||||
component (h/s/l и т.п.), display (`gray` | `space-as-rgb`)
|
||||
- `png-to-hsl`, `png-to-hsv`, `png-to-hsi`, `png-to-cmyk`, `png-to-ycbcr`,
|
||||
`png-to-lab` — component (h/s/l и т.п.), display (`gray` | `space-as-rgb`)
|
||||
|
||||
### Геометрия (`geometry.ts`)
|
||||
### Геометрия (`geometry`)
|
||||
|
||||
- resize-png — width/height (размеры source по умолчанию; 0=авто для одной
|
||||
стороны), keepAspect
|
||||
- crop-png — x, y, width/height (полная область source по умолчанию)
|
||||
- rotate-png — angle (90/180/270)
|
||||
- flip-png — axis (h/v)
|
||||
- skew-png — degX, degY
|
||||
- rotate-free-png — angle
|
||||
- zoom-png — scale
|
||||
- shift-png — offsetX, offsetY, color фона
|
||||
- add-padding-png — padding, transparent, color
|
||||
- add-border-png — thickness, color
|
||||
- fit-on-background-png — width, height, transparent, color
|
||||
- tile-png — columns, rows
|
||||
- split-into-parts-png — columns, rows (мультифайловый вывод, zip)
|
||||
- trim-empty-space-png — threshold (альфа)
|
||||
- change-canvas-size-png — width, height, anchor (3×3)
|
||||
- change-aspect-ratio-png — ratio (пресеты), mode (crop/pad)
|
||||
- swap-orientation-png — target (portrait/landscape)
|
||||
- symmetric-copy-png — axis, keepSide
|
||||
- `resize-png` — size (по умолчанию размер source, 0 по одной стороне = авто),
|
||||
keepAspect
|
||||
- `crop-png` — x, y, size (по умолчанию вся область source)
|
||||
- `rotate-png` — angle (90/180/270)
|
||||
- `rotate-free-png` — angle
|
||||
- `flip-png` — axis (h/v)
|
||||
- `skew-png` — degX, degY
|
||||
- `zoom-png` — scale
|
||||
- `shift-png` — offsetX, offsetY, color
|
||||
- `add-padding-png` — padding, transparent, color
|
||||
- `add-border-png` — thickness, color
|
||||
- `fit-on-background-png` — size, transparent, color
|
||||
- `tile-png` — columns, rows
|
||||
- `split-into-parts-png` — columns, rows (мультифайловый вывод, zip)
|
||||
- `trim-empty-space-png` — threshold (по альфе)
|
||||
- `change-canvas-size-png` — size, anchor (3×3)
|
||||
- `change-aspect-ratio-png` — ratio (пресеты), mode (crop/pad)
|
||||
- `swap-orientation-png` — target (portrait/landscape)
|
||||
- `symmetric-copy-png` — axis, keepSide
|
||||
- `center-by-alpha-png` — без параметров
|
||||
|
||||
### Фильтры (`filters.ts`)
|
||||
### Фильтры (`filters`)
|
||||
|
||||
- blur-png — radius
|
||||
- sharpen-png — strength
|
||||
- vignette-png — strength
|
||||
- jpeg-artifacts-png — quality (имитация пережатия jpg/webp)
|
||||
- pixelate-png — blockSize (закрывает и их Color Blocks)
|
||||
- randomize-pixels-png — blockSize, seed
|
||||
- add-noise-png — amount, mode (mono/color), seed
|
||||
- silhouette-png — color, threshold
|
||||
- `blur-png` — radius
|
||||
- `sharpen-png` — strength
|
||||
- `vignette-png` — strength
|
||||
- `jpeg-artifacts-png` — quality (имитация пережатия jpg/webp; поле
|
||||
функциональное, у инструмента нет `output` — `docs/plan-seo.md` S1f)
|
||||
- `pixelate-png` — blockSize
|
||||
- `randomize-pixels-png` — blockSize, seed
|
||||
- `add-noise-png` — amount, mode (mono/color), seed
|
||||
- `silhouette-png` — color, threshold
|
||||
|
||||
### Анализ и вердикты (`analyze.ts`)
|
||||
### Анализ и вердикты (`analyze`)
|
||||
|
||||
- png-file-size — без параметров (DOM-инструмент: кодирует в PNG и отдаёт
|
||||
- `png-file-size` — без параметров (DOM-инструмент: кодирует в PNG и отдаёт
|
||||
размер)
|
||||
- png-is-transparent / png-is-grayscale / png-orientation — текстовый вердикт
|
||||
- verify-is-png — текстовый источник (base64/data-uri), вердикт по сигнатуре
|
||||
- `png-is-transparent`, `png-is-grayscale`, `png-orientation` — без параметров,
|
||||
текстовый вердикт
|
||||
- `verify-is-png` — без параметров (вход — текст: base64/data-uri), вердикт по
|
||||
сигнатуре
|
||||
|
||||
### Маски по свойствам пикселей (`analyze.ts`)
|
||||
### Маски по свойствам пикселей (`analyze`)
|
||||
|
||||
- show-transparent-png — color, opacity (подсветка прозрачных/полупрозрачных)
|
||||
- show-grayscale-pixels-png — tolerance, mode (binary/highlight),
|
||||
highlightColor, highlightOpacity
|
||||
- show-color-pixels-png — tolerance, mode, highlightColor, highlightOpacity
|
||||
- light-pixel-mask-png — threshold, mode, highlightColor, highlightOpacity
|
||||
- dark-pixel-mask-png — threshold, mode, highlightColor, highlightOpacity
|
||||
- unique-color-mask-png — rarity (макс. повторов), mode, highlightColor,
|
||||
highlightOpacity
|
||||
- extract-color-from-png — color, tolerance (оставить близкие, остальное
|
||||
прозрачным)
|
||||
- `show-transparent-png` — mode, color, opacity
|
||||
- `show-grayscale-pixels-png` — mode, tolerance, color, opacity
|
||||
- `show-color-pixels-png` — mode, tolerance, color, opacity
|
||||
- `light-pixel-mask-png` — mode, threshold, color, opacity
|
||||
- `dark-pixel-mask-png` — mode, threshold, color, opacity
|
||||
- `unique-color-mask-png` — mode, rarity (макс. повторов), color, opacity
|
||||
- `extract-color-from-png` — color, tolerance
|
||||
|
||||
### Генерация (`generate.ts`)
|
||||
### Генерация (`generate`)
|
||||
|
||||
- create-empty-png — width, height, transparent, color
|
||||
- single-color-png — width, height, color
|
||||
- random-noise-png — width, height, seed
|
||||
- linear-gradient-png — width, height, fromColor, toColor, direction
|
||||
- color-spectrum-png — width, height, direction, saturation, lightness
|
||||
- random-colors-png — width, height, blockSize, seed
|
||||
- draw-grid-png — width, height, cols, rows, lineWidth, color, transparentBg
|
||||
- placeholder-png — width, height, backgroundColor, color, showText
|
||||
- blend-two-png — pair (from/to), width
|
||||
- step-colors-png — pair (from/to), steps, width
|
||||
- emoji-to-png — emoji, size
|
||||
- text-to-png — text, fontSize, font, bold, color, transparentBg,
|
||||
- `create-empty-png` — size, transparent, color
|
||||
- `single-color-png` — size, color
|
||||
- `random-noise-png` — size, seed
|
||||
- `linear-gradient-png` — size, gradient (от/к, направление)
|
||||
- `color-spectrum-png` — size, direction, saturation, lightness
|
||||
- `random-colors-png` — size, blockSize, seed
|
||||
- `draw-grid-png` — size, cols, rows, lineWidth, color, transparentBg
|
||||
- `placeholder-png` — size, backgroundColor, color, showText
|
||||
- `blend-two-png` — pair (от/к), width
|
||||
- `step-colors-png` — pair (от/к), steps, width, layout (grid/strip)
|
||||
- `color-wheel-png` — size, lightness
|
||||
- `mix-colors-png` — colors, width
|
||||
- `sort-colors-png` — colors, order (hue/brightness/saturation), width, layout
|
||||
- `emoji-to-png` — emoji, size
|
||||
- `text-to-png` — text, style (шрифт/размер/цвет/жирность), transparentBg,
|
||||
backgroundColor, padding
|
||||
- color-wheel-png — size, lightness
|
||||
- complementary-png / triadic-png / tetradic-png / analogous-png /
|
||||
monochromatic-png / shades-png — общая база палитры: baseColor, width, layout;
|
||||
`shades-png` дополнительно count, depth
|
||||
- mix-colors-png — colors, width
|
||||
- sort-colors-png — colors, порядок (hue/brightness/saturation), width
|
||||
- `complementary-png`, `triadic-png`, `tetradic-png`, `analogous-png`,
|
||||
`monochromatic-png`, `shades-png` — baseColor, width, layout; у `analogous`
|
||||
ещё spread и count, у `monochromatic` — count и range, у `shades` — count и
|
||||
depth
|
||||
|
||||
### Текст (`text.ts`)
|
||||
### Текст (`text`)
|
||||
|
||||
- add-text-png — text, fontSize, color, font, bold, position (3×3), margin,
|
||||
plate, plateColor, plateOpacity
|
||||
- date-stamp-png — format, fontSize, color, font, bold, position, margin, plate,
|
||||
plateColor, plateOpacity
|
||||
- watermark-tile-png — text, fontSize, color, opacity, angle, stepX, stepY,
|
||||
font, bold
|
||||
- `add-text-png` — text, style (шрифт/размер/цвет/жирность), position (3×3),
|
||||
margin, plate (плашка, её цвет и прозрачность)
|
||||
- `date-stamp-png` — format, style, position, margin, plate
|
||||
- `watermark-tile-png` — text, style, opacity, angle, stepX, stepY
|
||||
|
||||
---
|
||||
|
||||
## 2. Можно добавить — из onlinepngtools
|
||||
|
||||
Всё из этого списка **в реестре отсутствует**.
|
||||
Всё из этого списка **в реестре отсутствует** — это план, а не факт. Список
|
||||
проверяется `web/src/lib/registry/tools-map-doc.test.ts` в обратную сторону:
|
||||
реализованный slug, оставшийся здесь, роняет тест.
|
||||
|
||||
### Разложение каналов — остаток
|
||||
|
||||
@@ -216,8 +219,11 @@ Ids приведены к фактическим в `web/src/lib/registry/pages/
|
||||
### Сжатие и качество
|
||||
|
||||
- compress, reduce-to-size, pick-a-color, extract-barcode — **не реализованы**;
|
||||
заведены в `docs/backlog.md` (reduce-to-size зависит от UX-райза «Download»).
|
||||
Пипетка для выбора цвета уже есть в превью, отдельная страница не планируется.
|
||||
заведены в `docs/backlog.md`. Сжатие до целевого размера — прежде всего
|
||||
селектор формата в Download (S1f, `docs/plan-seo.md` §4): для lossy это
|
||||
quality-ручка, а для PNG — квантование палитры. Нужен ли отдельный инструмент
|
||||
под этот интент, решается по вордстату, имя страницы пока не выбрано. Пипетка
|
||||
для выбора цвета уже есть в превью, отдельная страница не планируется.
|
||||
Извлечение штрихкодов — HARD, вне планов.
|
||||
|
||||
---
|
||||
@@ -227,12 +233,21 @@ Ids приведены к фактическим в `web/src/lib/registry/pages/
|
||||
Развёрнутые описания этих идей живут в `docs/backlog.md`, раздел «Идеи»; здесь
|
||||
только список.
|
||||
|
||||
Буллиты этого раздела, как и раздела 2, перечисляют **несуществующие** страницы:
|
||||
уже реализованные в них не называются, иначе тест решит, что slug пора
|
||||
переносить в раздел 1. Реализованные примеры пишутся прозой между буллитами, как
|
||||
ниже.
|
||||
|
||||
- **Region-инструменты** — нужен selection-компонент на превью: censor-region,
|
||||
erase-region, pixelate-area, blur-area, sharpen-area, reverse-colors-area.
|
||||
Один раз делаем selection — получаем шесть инструментов.
|
||||
- **Мультифайловый выход** — механизм «результат = набор файлов» (zip)
|
||||
реализован на `split-into-parts-png`. Остальные 1→many: gif-to-frames,
|
||||
separate-colors, multiply-grid-as-files — подключаются по мере нужды.
|
||||
- **Мультифайловый выход** — остальные потребители механизма «результат = набор
|
||||
файлов»: gif-to-frames, separate-colors, multiply-grid-as-files — подключаются
|
||||
по мере нужды.
|
||||
|
||||
Механизм уже реализован на `split-into-parts-png` (`docs/architecture.md`,
|
||||
раздел 7), он в разделе 1.
|
||||
|
||||
- **Анимационные эффекты** — slow-reveal, fade-in/out, disappearing, scrolling:
|
||||
это видео/GIF на выходе, а не PNG. Отдельное решение о формате результата.
|
||||
- **HARD-хвост** — glitch-art, extract-signature, handwritten→digital,
|
||||
@@ -240,6 +255,6 @@ Ids приведены к фактическим в `web/src/lib/registry/pages/
|
||||
- **Нишевые серии** (logo/icon/stamp/signature — замер в шапке документа) —
|
||||
сознательно не копируем: это обычные операции над конкретным контентом, у нас
|
||||
они доступны через базовые инструменты + цепочки.
|
||||
- **Входные «из формата в PNG»** (jpg-to-png, webp-to-png) — решение и его SEO-
|
||||
последствия описаны в разделе 1 выше; отдельные страницы под эти интенты —
|
||||
предмет решения в `docs/plan-seo.md` §5.
|
||||
- **Входные «из формата в PNG»** (jpg-to-png, webp-to-png) — решение и его
|
||||
SEO-последствия описаны в `docs/plan-seo.md` §5; отдельные страницы под эти
|
||||
интенты — предмет решения там же.
|
||||
|
||||
@@ -175,8 +175,9 @@ report-only (`warn`), baseline — 0 срабатываний.
|
||||
- **Неиспользуемые токены**: выводится **варнинг** (определены, но нигде не
|
||||
используются) — выход он не меняет. Проверка видит только ссылки `var(--x)`,
|
||||
поэтому чтение токена из JS (`getPropertyValue`) она не учтёт; решение
|
||||
оставить проверку неблокирующей зафиксировано в `docs/quality-gates.md`.
|
||||
Провалами (exit 1) считаются parity, hct-авторство и резолв `var()`.
|
||||
оставить проверку неблокирующей — `docs/decisions.md`, раздел 6 (как оно
|
||||
выглядит в гейте — `docs/quality-gates.md`). Провалами (exit 1) считаются
|
||||
parity, hct-авторство и резолв `var()`.
|
||||
|
||||
## Тесты кастомных линт-правил
|
||||
|
||||
@@ -187,6 +188,9 @@ report-only (`warn`), baseline — 0 срабатываний.
|
||||
`no-token-definition-in-svelte`) через `RuleTester` со строковыми кейсами;
|
||||
`no-undefined-in-svelte` — через `Linter` API, т.к. читает словарь токенов из
|
||||
`__fixtures__/src/app.css` (не из реального `src/app.css`).
|
||||
- `__tests__/interface-props.test.ts` и
|
||||
`__tests__/no-string-union-alias.test.ts` — конвенции плагина `conventions`
|
||||
(интерфейс пропсов, запрет union-алиасов).
|
||||
- Хелпер `__tests__/helpers.ts` собирает `Linter` с `cwd = __fixtures__` —
|
||||
`process.cwd()` не трогается, реальные `src/` не читаются.
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ export default tseslint.config(
|
||||
"e2e/helpers/page.ts",
|
||||
"e2e/navigation.spec.ts",
|
||||
"e2e/catalog.spec.ts",
|
||||
"e2e/pipeline.spec.ts",
|
||||
"e2e/tool-flows.spec.ts",
|
||||
"e2e/text-and-verdicts.spec.ts",
|
||||
"e2e/tools-smoke.spec.ts",
|
||||
"e2e/generators.spec.ts",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { defineConfig } from "playwright/test";
|
||||
|
||||
const browserContractTests = [
|
||||
"**/pipeline.spec.ts",
|
||||
"**/tool-flows.spec.ts",
|
||||
"**/png-fixtures.spec.ts",
|
||||
"**/text-and-verdicts.spec.ts",
|
||||
];
|
||||
@@ -10,7 +10,7 @@ const mobileTests = [
|
||||
"**/navigation.spec.ts",
|
||||
"**/catalog.spec.ts",
|
||||
"**/generators.spec.ts",
|
||||
"**/pipeline.spec.ts",
|
||||
"**/tool-flows.spec.ts",
|
||||
];
|
||||
|
||||
export default defineConfig({
|
||||
|
||||
@@ -8,30 +8,35 @@ const MAP_PATH = fileURLToPath(
|
||||
);
|
||||
|
||||
const SECTION_START = /^## 1\.\s/;
|
||||
const SECTION_END = /^## 2\.\s/;
|
||||
const SECTION_PLANNED = /^## 2\.\s/;
|
||||
|
||||
// Все slug страниц содержат дефис, поэтому формат строгий: это отсекает слова
|
||||
// описания вроде `median-cut` из «colors (k, median-cut)».
|
||||
const ID_SHAPE = /^[a-z][a-z0-9]*(-[a-z0-9]+)+$/;
|
||||
const ID_TOKEN = /[a-z][a-z0-9]*(?:-[a-z0-9]+)+/g;
|
||||
|
||||
type Mention = { id: string; line: number };
|
||||
|
||||
/**
|
||||
* Буллиты раздела «1. Реализовано» вместе с номером первой строки.
|
||||
* Перенесённые строки приклеиваются к буллету: иначе slug, оказавшийся на
|
||||
* следующей строке, молча выпадал бы из разбора и страница считалась бы
|
||||
* неописанной.
|
||||
* Буллиты раздела вместе с номером первой строки. Перенесённые строки
|
||||
* приклеиваются к буллету: иначе slug, оказавшийся на следующей строке, молча
|
||||
* выпадал бы из разбора и страница считалась бы неописанной.
|
||||
*/
|
||||
function implementedBullets(
|
||||
function sectionBullets(
|
||||
markdown: string,
|
||||
startPattern: RegExp,
|
||||
endPattern: RegExp | null,
|
||||
): { text: string; line: number }[] {
|
||||
const lines = markdown.split("\n");
|
||||
const start = lines.findIndex((l) => SECTION_START.test(l));
|
||||
const end = lines.findIndex((l) => SECTION_END.test(l));
|
||||
const start = lines.findIndex((l) => startPattern.test(l));
|
||||
const end =
|
||||
endPattern === null
|
||||
? lines.length
|
||||
: lines.findIndex((l) => endPattern.test(l));
|
||||
if (start === -1 || end === -1 || end <= start) {
|
||||
throw new Error(
|
||||
"docs/tools-map.md: не найден раздел «1. Реализовано» или «2.» — " +
|
||||
"тест разбирает только реализованные инструменты",
|
||||
`docs/tools-map.md: не найден раздел по ${startPattern} — тест не может ` +
|
||||
"определить границы разделов карты",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -47,7 +52,7 @@ function implementedBullets(
|
||||
!/^#{1,6}\s/.test(text) &&
|
||||
!/^[-*]\s/.test(text);
|
||||
if (isBullet) {
|
||||
bullets.push({ text: text.slice(2), line: start + i + 1 });
|
||||
bullets.push({ text: text.slice(2), line: i + 1 });
|
||||
} else if (isContinuation) {
|
||||
previous.text += ` ${text}`;
|
||||
}
|
||||
@@ -61,7 +66,11 @@ function implementedBullets(
|
||||
*/
|
||||
function mentionedIds(markdown: string): Mention[] {
|
||||
const found: Mention[] = [];
|
||||
for (const { text, line } of implementedBullets(markdown)) {
|
||||
for (const { text, line } of sectionBullets(
|
||||
markdown,
|
||||
SECTION_START,
|
||||
SECTION_PLANNED,
|
||||
)) {
|
||||
const head = text.split("—")[0];
|
||||
for (const raw of head.split(/[/,]/)) {
|
||||
const id = raw.trim().replace(/`/g, "");
|
||||
@@ -71,14 +80,35 @@ function mentionedIds(markdown: string): Mention[] {
|
||||
return found;
|
||||
}
|
||||
|
||||
/**
|
||||
* Плановые разделы «2. Можно добавить» и «3. Идеи» устроены прозой: slug стоит
|
||||
* где угодно в буллите, после тире и в скобках. Поэтому берём все токены
|
||||
* нужной формы из всего текста буллита, а не из «головы» до тире.
|
||||
*/
|
||||
function plannedIds(markdown: string): Mention[] {
|
||||
const found: Mention[] = [];
|
||||
for (const { text, line } of sectionBullets(
|
||||
markdown,
|
||||
SECTION_PLANNED,
|
||||
null,
|
||||
)) {
|
||||
for (const id of text.replace(/`/g, "").match(ID_TOKEN) ?? []) {
|
||||
found.push({ id, line });
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
describe("docs/tools-map.md ↔ страницы реестра", () => {
|
||||
const markdown = readFileSync(MAP_PATH, "utf8");
|
||||
const mentioned = mentionedIds(markdown);
|
||||
const planned = plannedIds(markdown);
|
||||
const registrySlugs = new Set(PAGES.map((page) => page.slug));
|
||||
|
||||
it("разбирает непустой список slug (парсер не сломался молча)", () => {
|
||||
expect(implementedBullets(markdown).length).toBeGreaterThan(0);
|
||||
expect(mentioned.length).toBeGreaterThan(0);
|
||||
expect(new Set(mentioned.map((m) => m.id)).size).toBeGreaterThan(50);
|
||||
expect(new Set(planned.map((m) => m.id)).size).toBeGreaterThan(10);
|
||||
});
|
||||
|
||||
it("в карте нет slug, которых нет в реестре", () => {
|
||||
@@ -116,4 +146,18 @@ describe("docs/tools-map.md ↔ страницы реестра", () => {
|
||||
}
|
||||
expect(dupes, `повторяющиеся slug:\n${dupes.join("\n")}`).toEqual([]);
|
||||
});
|
||||
|
||||
it("плановый slug уже не реализован (перенос из «2./3.» в «1.»)", () => {
|
||||
const stale = planned.filter((m) => registrySlugs.has(m.id));
|
||||
expect(
|
||||
stale,
|
||||
stale
|
||||
.map(
|
||||
(m) =>
|
||||
`${m.id} (docs/tools-map.md:${m.line}) — страница уже есть в реестре, ` +
|
||||
"перенеси её из планового раздела в «1. Реализовано»",
|
||||
)
|
||||
.join("\n"),
|
||||
).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user