mirror of
https://github.com/Ku6epXBOCTuK/easy-png-tools.git
synced 2026-11-13 17:08:20 +00:00
docs: update to slug - id changes
This commit is contained in:
@@ -70,11 +70,12 @@ pnpm build # static production export → web/build/
|
|||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
Everything is built around the **tool registry** (`web/src/lib/registry/`): each
|
Everything is built around the **two-layer registry** (`web/src/lib/registry/`):
|
||||||
tool is a self-contained entry
|
a tool is a self-contained implementation `{id, schema, input, result, run}`
|
||||||
`{id, title, description, category, schema, run}`. Pages, forms, and pipelines
|
(`registry/tools/`), and a page is `{slug, title, description, category, steps}`
|
||||||
are generated from it, and all execution goes through a single
|
(`registry/pages/`) that names the address and the tools to run — one tool can
|
||||||
`executor.execute` entry point.
|
back several pages. Pages, forms, and pipelines 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
|
Core image processing lives in `web/src/lib/core/` and operates directly on
|
||||||
`ImageData`, with no browser API dependencies.
|
`ImageData`, with no browser API dependencies.
|
||||||
|
|||||||
+4
-4
@@ -56,10 +56,10 @@
|
|||||||
Единственный документ в архиве со статусом `active` —
|
Единственный документ в архиве со статусом `active` —
|
||||||
`docs/archive/README.md`: он описывает процедуру архивирования, а не архивный
|
`docs/archive/README.md`: он описывает процедуру архивирования, а не архивный
|
||||||
материал, поэтому не подлежит 30-дневному удалению.
|
материал, поэтому не подлежит 30-дневному удалению.
|
||||||
- Ссылаясь на инструмент по id, сверять id с реестром (`web/src/lib/registry/`):
|
- Ссылаясь на инструмент, сверяться с реестром: внутреннее имя (`id`) — в
|
||||||
исторические названия в старых планах не действуют. Сейчас `id` записи — это
|
`web/src/lib/registry/tools/`, адрес и имя страницы (`slug`) — в
|
||||||
одновременно адрес страницы `/tools/<id>`, отдельного поля `slug` нет
|
`web/src/lib/registry/pages/`; исторические названия в старых планах не
|
||||||
(`docs/architecture.md`, раздел 3).
|
действуют (`docs/architecture.md`, раздел 3).
|
||||||
- Поиск по `docs/` задевает `docs/archive/` (объём больше, чем у активных
|
- Поиск по `docs/` задевает `docs/archive/` (объём больше, чем у активных
|
||||||
документов вместе). Для правок production-кода архив не нужен: grep по
|
документов вместе). Для правок production-кода архив не нужен: grep по
|
||||||
`docs/*.md` без `archive/`.
|
`docs/*.md` без `archive/`.
|
||||||
|
|||||||
+98
-75
@@ -9,11 +9,13 @@
|
|||||||
|
|
||||||
```txt
|
```txt
|
||||||
web/src/lib/
|
web/src/lib/
|
||||||
registry/ # источник истины: записи ToolEntry
|
registry/ # источник истины: инструменты и страницы
|
||||||
|
registry/tools/ # Tool: реализации (id), без адресов и текстов
|
||||||
|
registry/pages/ # Page: адрес, тексты, категория, список шагов
|
||||||
registry-schema.ts # декларативная схема параметров + санитизация дефолтов
|
registry-schema.ts # декларативная схема параметров + санитизация дефолтов
|
||||||
catalog.ts # группировка и счётчики для /list-tools
|
catalog.ts # группировка страниц и счётчики для /list-tools
|
||||||
categories.ts # id/названия категорий
|
categories.ts # id/названия категорий
|
||||||
tool-icons.ts # id → иконка
|
tool-icons.ts # slug страницы → иконка
|
||||||
core/ # чистый TS: операции над ImageData, без DOM
|
core/ # чистый TS: операции над ImageData, без DOM
|
||||||
executor/ # единственная точка исполнения инструментов
|
executor/ # единственная точка исполнения инструментов
|
||||||
i18n/ # словари ru/en, t(), списки для i18n-правил линтера
|
i18n/ # словари ru/en, t(), списки для i18n-правил линтера
|
||||||
@@ -23,7 +25,7 @@ web/src/lib/
|
|||||||
web/src/routes/
|
web/src/routes/
|
||||||
+page.svelte # главная
|
+page.svelte # главная
|
||||||
list-tools/ # каталог
|
list-tools/ # каталог
|
||||||
tools/[id]/ # страница инструмента (адрес = id записи)
|
tools/[slug]/ # страница инструмента (адрес = slug страницы)
|
||||||
kit/ # витрина компонентов (production, но исключена из i18n-гейта)
|
kit/ # витрина компонентов (production, но исключена из i18n-гейта)
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -34,15 +36,18 @@ web/src/routes/
|
|||||||
|
|
||||||
### Чего в коде нет
|
### Чего в коде нет
|
||||||
|
|
||||||
Страница инструмента работает ровно с одним инструментом: загрузил файл →
|
Страница исполняет ровно один шаг: загрузил файл → применила инструмент →
|
||||||
применил одну запись реестра → скачал результат. Отсутствуют:
|
скачала результат. Отсутствуют:
|
||||||
|
|
||||||
- **Пайплайн / цепочки шагов** — модуля нет, состояния нет, localStorage для
|
- **Пайплайн / цепочки шагов** — модуля нет, состояния нет, localStorage для
|
||||||
него нет. `docs/roadmap.md` описывает его как отдельную фазу, а не как
|
него нет. Модель страницы уже хранит список `steps`, но исполняет первый шаг,
|
||||||
поставленное. Под будущую фазу зарезервированы `components/StepCard.svelte`
|
а `registry.test.ts` требует, чтобы шаг был ровно один: композиция формы из
|
||||||
|
полей двух инструментов — отдельная работа, описанная как фаза 11
|
||||||
|
`docs/roadmap.md`. Под неё зарезервированы `components/StepCard.svelte`
|
||||||
(сейчас импортируется только витриной `/kit`) и ключи `chain.*`,
|
(сейчас импортируется только витриной `/kit`) и ключи `chain.*`,
|
||||||
`errors.badPipelineShape`, `errors.noSteps`.
|
`errors.badPipelineShape`, `errors.noSteps`.
|
||||||
- **Разделения `id` и `slug`** — раздел 3.
|
- **Разделения страниц, которые делят один инструмент** — сводит 14
|
||||||
|
convert-страниц на один инструмент; задача в `docs/backlog.md`.
|
||||||
|
|
||||||
Задачи по обоим пунктам — в `docs/backlog.md`; архитектурные решения по
|
Задачи по обоим пунктам — в `docs/backlog.md`; архитектурные решения по
|
||||||
пайплайну — `docs/roadmap.md`.
|
пайплайну — `docs/roadmap.md`.
|
||||||
@@ -50,15 +55,15 @@ web/src/routes/
|
|||||||
## 2. Поток данных
|
## 2. Поток данных
|
||||||
|
|
||||||
```txt
|
```txt
|
||||||
registry/*.ts ──(TOOLS: ToolEntry[])
|
registry/{tools,pages}/*.ts
|
||||||
│
|
│
|
||||||
├─► catalog.ts ──► routes/list-tools (каталог, поиск, фильтры)
|
├─► catalog.ts ──► routes/list-tools (каталог страниц, поиск, фильтры)
|
||||||
│
|
│
|
||||||
└─► routes/tools/[id]/+page.svelte
|
└─► routes/tools/[slug]/+page.svelte
|
||||||
│ getTool(id) → запись реестра
|
│ getPageBySlug(slug) → Page, getTool(steps[0].id) → Tool
|
||||||
▼
|
▼
|
||||||
SchemaToolView.svelte оркестратор страницы
|
SchemaToolView.svelte оркестратор страницы
|
||||||
│ schema = tool.schema
|
│ schema = tool.schema, тексты и имя файла — от page
|
||||||
├─► SchemaFields.svelte поля из schema.fields (FIELDS: kind → контрол)
|
├─► SchemaFields.svelte поля из schema.fields (FIELDS: kind → контрол)
|
||||||
├─► SchemaSourceTile/SchemaTextSource.svelte вход
|
├─► SchemaSourceTile/SchemaTextSource.svelte вход
|
||||||
└─► SchemaPreview + schema-preview-model.ts вид превью
|
└─► SchemaPreview + schema-preview-model.ts вид превью
|
||||||
@@ -71,14 +76,15 @@ registry/*.ts ──(TOOLS: ToolEntry[])
|
|||||||
│
|
│
|
||||||
▼
|
▼
|
||||||
SchemaResultTile / SchemaTextResult + SchemaActions (download/copy)
|
SchemaResultTile / SchemaTextResult + SchemaActions (download/copy)
|
||||||
│ кодирование по ToolEntry.output (mime/ext/quality)
|
│ кодирование по Tool.output (mime/ext/quality)
|
||||||
└─► zip.ts, если result === "files"
|
└─► zip.ts, если result === "files"
|
||||||
```
|
```
|
||||||
|
|
||||||
Ключевые решения:
|
Ключевые решения:
|
||||||
|
|
||||||
- **Реестр — единственный источник истины.** Страница и форма строятся из
|
- **Реестр — единственный источник истины.** Страница и форма строятся из
|
||||||
записи; новый инструмент = новая запись + функция `run`.
|
записей; новый инструмент = новая запись `Tool` + функция `run` и новая
|
||||||
|
страница `Page` на него.
|
||||||
- **Исполнение идёт только через `executor.execute`.** Компоненты не зовут
|
- **Исполнение идёт только через `executor.execute`.** Компоненты не зовут
|
||||||
`tool.run` напрямую — иначе нельзя перенести тяжёлое в worker/wasm без правки
|
`tool.run` напрямую — иначе нельзя перенести тяжёлое в worker/wasm без правки
|
||||||
вызывающего кода.
|
вызывающего кода.
|
||||||
@@ -87,39 +93,50 @@ registry/*.ts ──(TOOLS: ToolEntry[])
|
|||||||
- **Модель превью отделена от вёрстки.** `schema-preview-model.ts` — чистая
|
- **Модель превью отделена от вёрстки.** `schema-preview-model.ts` — чистая
|
||||||
функция, покрыта unit-тестами; `SchemaPreview.svelte` только рисует её вывод.
|
функция, покрыта unit-тестами; `SchemaPreview.svelte` только рисует её вывод.
|
||||||
|
|
||||||
## 3. Запись реестра
|
## 3. Инструмент и страница
|
||||||
|
|
||||||
`ToolEntry` (`web/src/lib/registry/types.ts`):
|
Реестр двухслойный: реализация и то, что видит пользователь, лежат в разных
|
||||||
|
файлах. `Tool` (`web/src/lib/registry/types.ts`, записи в
|
||||||
|
`registry/tools/<group>.ts`):
|
||||||
|
|
||||||
| Поле | Смысл |
|
| Поле | Смысл |
|
||||||
| ------------- | ------------------------------------------------------------------------------------------------ |
|
| ---------- | ------------------------------------------------------------------------------ |
|
||||||
| `id` | идентичность инструмента **и** публичный адрес `/tools/<id>` **и** база имени скачиваемого файла |
|
| `id` | внутреннее имя инструмента, уникально; ключ `tools.<id>.*` и `toolId` в worker |
|
||||||
| `title` | EN-строка реестра, переводится через `tools.*.title` |
|
| `schema` | `toolSchema({ fields, layout?, meta? })` |
|
||||||
| `description` | EN-строка реестра, переводится через `tools.*.description` |
|
| `input` | `"image" \| "text" \| "none"` — что ждёт инструмент на входе |
|
||||||
| `category` | id из `categories.ts` |
|
| `result` | `"image" \| "text" \| "verdict" \| "files"` — что отдаёт |
|
||||||
| `schema` | `toolSchema({ fields, layout?, meta? })` |
|
| `domOnly` | инструмент требует DOM (canvas); превью-executor запускает его напрямую |
|
||||||
| `input` | `"image" \| "text" \| "none"` — что ждёт инструмент на входе |
|
| `output` | mime/ext/quality для скачивания (не у всех) |
|
||||||
| `result` | `"image" \| "text" \| "verdict" \| "files"` — что отдаёт |
|
| `run(ctx)` | единственный метод: `ctx` → `ToolResult` |
|
||||||
| `domOnly` | инструмент требует DOM (canvas); превью-executor запускает его напрямую, минуя worker |
|
|
||||||
| `output` | mime/ext/quality для download (не у всех инструментов) |
|
|
||||||
| `icon` | id иконки из `tool-icons.ts` |
|
|
||||||
| `run(ctx)` | единственный метод: `ctx` → `ToolResult` |
|
|
||||||
|
|
||||||
Отдельного поля `slug` **нет**: адрес страницы, ключ i18n (`tools.<id>.*`) и имя
|
`Page` (записи в `registry/pages/<group>.ts`):
|
||||||
файла скачивания — это один и тот же `id`. Инструмент работает с любым
|
|
||||||
поддержанным форматом, но страница держит png-интент, поэтому `-png` живёт
|
| Поле | Смысл |
|
||||||
внутри `id` (`id: "crop-png"` → `/tools/crop-png` → `crop-png.png`). Следствие:
|
| ------------- | ---------------------------------------------------------------------- |
|
||||||
переименование `id` меняет адрес страницы, ключи `tools.*` и имя файла.
|
| `slug` | уникальный адрес `/tools/<slug>`, имя скачиваемого файла и ключ иконки |
|
||||||
|
| `title` | EN-строка страницы, переводится через `pages.<slug>.title` |
|
||||||
|
| `description` | EN-строка страницы, переводится через `pages.<slug>.description` |
|
||||||
|
| `category` | id из `categories.ts`; по нему каталог группирует страницы |
|
||||||
|
| `steps` | упорядоченный список шагов `{ id, params? }` — ссылки на инструменты |
|
||||||
|
|
||||||
|
Кто по чему адресуется: `slug` — маршрут, имя файла, `TOOL_ICONS`, поиск по
|
||||||
|
каталогу; `id` — ключ `tools.<id>.*` (подписи полей, `options`, `results`),
|
||||||
|
`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` не трогает адреса, а переименование `slug` меняет адрес, имя
|
||||||
|
файла, ключ `pages.*`, иконку и запись в `docs/tools-map.md`.
|
||||||
|
|
||||||
Хелперы `genTool` / `imgTool` / `textGen` из `types.ts` задают `run` для типовых
|
Хелперы `genTool` / `imgTool` / `textGen` из `types.ts` задают `run` для типовых
|
||||||
случаев (приведение типа результата), чтобы запись не повторяла обвязку. `TOOLS`
|
случаев (приведение типа результата), чтобы запись не повторяла обвязку. `TOOLS`
|
||||||
собирается в `registry/index.ts` из восьми групп; `getTool(id)` — единственный
|
и `PAGES` собираются в `registry/index.ts` из восьми групп; `getTool(id)` и
|
||||||
способ достать запись, `registry.test.ts` проверяет уникальность `id`.
|
`getPageBySlug(slug)` — способы достать запись, `registry.test.ts` проверяет
|
||||||
|
уникальность обоих имён, существование инструмента под каждым шагом и то, что
|
||||||
Разделение `id` (внутреннее имя) и `slug` (адрес) — решённое, но **не
|
шаг ровно один.
|
||||||
реализованное** решение: задача и порядок волн в `docs/backlog.md`, раздел
|
|
||||||
«Мелочи всякие». До её выполнения этот документ описывает текущее состояние, а
|
|
||||||
id и slug совпадают.
|
|
||||||
|
|
||||||
## 4. Схема параметров
|
## 4. Схема параметров
|
||||||
|
|
||||||
@@ -154,52 +171,58 @@ i18n-подписи → тест в `registry-schema.test.ts`.
|
|||||||
|
|
||||||
Словари — `web/src/lib/i18n/` (`en.ts`, `ru.ts`, общий `dict.ts` с
|
Словари — `web/src/lib/i18n/` (`en.ts`, `ru.ts`, общий `dict.ts` с
|
||||||
`LOCALES`/`BASE_LOCALE`). Пользовательский текст живёт только в словарях и
|
`LOCALES`/`BASE_LOCALE`). Пользовательский текст живёт только в словарях и
|
||||||
читается через `t()`; строки реестра (`title`/`description`) — источник для
|
читается через `t()`. Секции разделены по слоям: `pages.<slug>.title` и
|
||||||
EN-ключей `tools.*` и словарь-переводчик. Инварианты паритета ключей,
|
`pages.<slug>.description` — тексты страницы (в базовой локали секция пустая, EN
|
||||||
плейсхолдеров и запрет хардкода проверяются линтером, детали —
|
берётся из реестра), `tools.<id>.options/params/results` — подписи полей и
|
||||||
`web/eslint-plugins/README.md`.
|
тексты вердиктов инструмента, `fields.*`/`groups.*` — общие подписи схемы.
|
||||||
|
Инварианты паритета ключей, плейсхолдеров и запрет хардкода проверяются
|
||||||
|
линтером, детали — `web/eslint-plugins/README.md`.
|
||||||
|
|
||||||
## 7. Точки расширения
|
## 7. Точки расширения
|
||||||
|
|
||||||
| Задача | Где править |
|
| Задача | Где править |
|
||||||
| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||||
| Новый инструмент | запись в `registry/<group>.ts` + i18n-ключи + тесты (рецепт ниже) |
|
| Новый инструмент | `registry/tools/<group>.ts` + страница в `registry/pages/<group>.ts` + i18n + тесты (рецепт ниже) |
|
||||||
| Смена `id` | запись реестра + i18n-ключи `tools.*` + `tool-icons.ts` + хардкод id в тестах, `/kit` и e2e; меняется адрес страницы — операция с SEO-последствиями, не часть обычной задачи |
|
| Новая страница на готовом инструменте | запись `Page` со своим `slug` и текстами; новый код не нужен |
|
||||||
| Новый вид поля | `registry-schema.ts` + контрол + `FIELDS` (раздел 4) |
|
| Смена `id` | `registry/tools/*` + ключи `tools.*` в словарях + хардкод id в тестах; адреса, имена файлов, иконки и карта не меняются |
|
||||||
| Новый маршрут/страница | `web/src/routes/**` + ссылка в каталоге |
|
| Смена `slug` | `registry/pages/*` + `pages.*` в словарях + `tool-icons.ts` + `docs/tools-map.md` + хардкод slug в тестах и e2e; меняется адрес страницы — операция с SEO-последствиями, не часть обычной задачи |
|
||||||
| Новый визуальный элемент | компонент в `components/**` по образцу `ui/` или `layout/` |
|
| Новый вид поля | `registry-schema.ts` + контрол + `FIELDS` (раздел 4) |
|
||||||
| Новый дизайн-токен | `web/src/app.css` (только там) + потребитель сразу |
|
| Новый маршрут/страница | `web/src/routes/**` + ссылка в каталоге |
|
||||||
| Тяжёлая операция → worker/wasm | `executor/**`, без правок компонентов |
|
| Новый визуальный элемент | компонент в `components/**` по образцу `ui/` или `layout/` |
|
||||||
| Мультифайловый результат | `result: "files"` + `zip.ts` |
|
| Новый дизайн-токен | `web/src/app.css` (только там) + потребитель сразу |
|
||||||
|
| Тяжёлая операция → worker/wasm | `executor/**`, без правок компонентов |
|
||||||
|
| Мультифайловый результат | `result: "files"` + `zip.ts` |
|
||||||
|
|
||||||
## 8. Рецепт: добавить инструмент
|
## 8. Рецепт: добавить инструмент
|
||||||
|
|
||||||
1. **Найти соседей.** Поиск по ключу в `registry/<group>.ts` и по всему
|
1. **Найти соседей.** Поиск по ключу в `registry/tools/<group>.ts` и по всему
|
||||||
`web/src/` — не заводить дублирующую константу, тип или утилиту.
|
`web/src/` — не заводить дублирующую константу, тип или утилиту.
|
||||||
2. **Выбрать группу** (`geometry`/`alpha`/`color`/`convert`/`filters`/
|
2. **Выбрать группу** (`geometry`/`alpha`/`color`/`convert`/`filters`/
|
||||||
`generate`/`text`/`analyze`) и положить запись в её экспорт; при новой
|
`generate`/`text`/`analyze`) и положить инструмент в её экспорт; при новой
|
||||||
категории — сначала `categories.ts`.
|
категории — сначала `categories.ts`.
|
||||||
3. **Задать `id`.** Он же адрес страницы, он же ключ i18n, он же база имени
|
3. **Задать `id`** по правилу раздела 3: без png-интента (`crop`, `to-jpg`,
|
||||||
скачиваемого файла. Страница держит png-интент, поэтому суффикс `-png` входит
|
`from-base64`, `is-grayscale`).
|
||||||
в `id` (`crop-png`, `resize-png`) — это осознанно, отдельного поля `slug` в
|
|
||||||
реестре нет (раздел 3).
|
|
||||||
4. **Описать схему** через `field.*`, задать `layout`, если поля нужно
|
4. **Описать схему** через `field.*`, задать `layout`, если поля нужно
|
||||||
сгруппировать.
|
сгруппировать. Качество кодирования (`quality`) — не поле страницы, а
|
||||||
|
настройка скачивания (`docs/plan-seo.md` §1, S1f).
|
||||||
5. **Написать `run`** поверх `core/*`; пиксельные операции — чистые функции в
|
5. **Написать `run`** поверх `core/*`; пиксельные операции — чистые функции в
|
||||||
`core/`, без DOM.
|
`core/`, без DOM.
|
||||||
6. **Проверить обязательные контракты** для новой записи: она попадает в
|
6. **Добавить страницу** в `registry/pages/<group>.ts`: `slug` с адресным
|
||||||
`TOOLS`, страница `/tools/<id>` prerender-ится, `input`/`result` согласованы
|
png-интентом (`crop-png`), тексты EN, `category`, `steps: [{ id }]`.
|
||||||
с тем, что делает `run`.
|
7. **Проверить обязательные контракты**: инструмент попадает в `TOOLS`, страница
|
||||||
7. **Добавить i18n-ключи во все локали** сразу (ключ — по `id`).
|
в `PAGES`, `/tools/<slug>` prerender-ится, `input`/`result` согласованы с
|
||||||
8. **Тесты:** unit — в `registry/*.test.ts` или рядом с `core`-функцией;
|
тем, что делает `run`.
|
||||||
|
8. **Добавить i18n-ключи во все локали** сразу: `pages.<slug>.title` и
|
||||||
|
`pages.<slug>.description`, при наличии опций и вердиктов — `tools.<id>`.
|
||||||
|
9. **Тесты:** unit — в `registry/registry.test.ts` или рядом с `core`-функцией;
|
||||||
e2e-smoke — существующий spec по типу `input`/`result` (правила —
|
e2e-smoke — существующий spec по типу `input`/`result` (правила —
|
||||||
`docs/testing-strategy.md`).
|
`docs/testing-strategy.md`).
|
||||||
9. **Обновить `docs/tools-map.md`:** новая страница обязательна в разделе
|
10. **Обновить `docs/tools-map.md`:** новая страница обязательна в разделе
|
||||||
«Реализовано», формат строки — в шапке раздела. Карта перечисляет **`id`**
|
«Реализовано», формат строки — в шапке раздела. Карта перечисляет **`slug`
|
||||||
реестра, которые одновременно являются адресами страниц. Проверяет
|
страниц** из `registry/pages/`, то есть адреса. Проверяет
|
||||||
`web/src/lib/registry/tools-map-doc.test.ts`: id без инструмента и инструмент
|
`web/src/lib/registry/tools-map-doc.test.ts`: slug без страницы и страница
|
||||||
без записи в карте роняют тест. Обоснование и границы автоматизации —
|
без записи в карте роняют тест. Обоснование и границы автоматизации —
|
||||||
`docs/decisions.md`, раздел 4.
|
`docs/decisions.md`, раздел 4.
|
||||||
|
|
||||||
Ожидаемый минимум проверок: `pnpm --dir web check` и `pnpm --dir web test`; для
|
Ожидаемый минимум проверок: `pnpm --dir web check` и `pnpm --dir web test`; для
|
||||||
инструмента, меняющего пользовательский поток, — e2e (см. раздел 3
|
инструмента, меняющего пользовательский поток, — e2e (см. раздел 3
|
||||||
|
|||||||
+18
-29
@@ -72,28 +72,17 @@
|
|||||||
- [ ] для keep Aspect field - пересчитывать второе поле, учитывать какое было
|
- [ ] для keep Aspect field - пересчитывать второе поле, учитывать какое было
|
||||||
изменено
|
изменено
|
||||||
- [ ] обновить страницу kit или сделать storybook
|
- [ ] обновить страницу kit или сделать storybook
|
||||||
- [ ] Слаги инструментов (`*-png`) — **развести `id` и адрес страницы.**
|
- [ ] Свести convert-страницы на один инструмент `convert` — реализация уже
|
||||||
Инструменты работают с любым форматом, но называются `crop-png`,
|
умеет «скопировать пиксели, декодировать вход, отдать в заданном формате»,
|
||||||
`resize-png`; имя в коде, пайплайнах и i18n должно быть `crop`, `resize`,
|
а страницы различаются только форматом вывода. **Зависит от S1f**
|
||||||
а адрес и имя скачиваемого файла — прежними (`/tools/crop-png` →
|
(`docs/plan-seo.md` §1): пока `quality` живёт в схеме страницы, у
|
||||||
`crop-png.png`). Решение и его SEO-последствия: `docs/plan-seo.md` §0;
|
`convert-png-to-bmp` и `convert-png-to-jpg` разные наборы полей, и один
|
||||||
текущее состояние (поля `slug` нет, роут — `tools/[id]`) —
|
инструмент на них не сойдётся. Качество — настройка кнопки Download
|
||||||
`docs/architecture.md`, раздел 3. Порядок работ: 1. добавить `slug` в
|
(«Download as JPEG»), а не поле страницы; после его переезда пресеты схемы
|
||||||
`ToolEntry` + `toolSlug()` / `getToolBySlug()` в `registry/index.ts`,
|
не нужны, страницы различаются только `output` и `params` шага. Модель
|
||||||
проверку уникальности `slug` в `registry.test.ts`; 2. перевести
|
страницы к этому готова: `Page.steps` — список инструментов, один
|
||||||
`routes/tools/[id]` → `[slug]` (`entries()`, `load()`, `+page.svelte`),
|
инструмент может обслуживать много страниц (`docs/architecture.md`, раздел
|
||||||
`ToolCard`, крошку в `+layout.svelte` и имя файла download в
|
3).
|
||||||
`SchemaToolView` на `toolSlug(tool)`; старые адреса продолжают работать,
|
|
||||||
редиректы не нужны; 3. переименовать `id` волнами по группам реестра
|
|
||||||
(`convert`, `alpha`, `color`, `geometry`, `filters`, `analyze`,
|
|
||||||
`generate`, `text`); в каждой волне — `registry/<group>.ts`, ключи
|
|
||||||
`tools.*` в `en.ts`/`ru.ts`, `tool-icons.ts`, захардкоженные id в тестах и
|
|
||||||
e2e (`smoke-i18n.test.ts`, `kit/+page.svelte`); 4. `docs/tools-map.md` при
|
|
||||||
этом **не меняется**: карта перечисляет id, а они остаются прежними как
|
|
||||||
адреса. Обратной совместимости не делаем: формат пайплайна ещё не
|
|
||||||
устоялся, старые экспорты и записи в localStorage после переименования
|
|
||||||
просто перестают открываться — это осознанно, миграция усложнила бы код
|
|
||||||
ради пары пользователей.
|
|
||||||
- [ ] придумать процесс редактирования и настройки pipeline - drag-n-drop,
|
- [ ] придумать процесс редактирования и настройки pipeline - drag-n-drop,
|
||||||
удаление, добавление инструментов. переход один инструмент - pipeline без
|
удаление, добавление инструментов. переход один инструмент - pipeline без
|
||||||
перезагрузки
|
перезагрузки
|
||||||
@@ -193,21 +182,21 @@ UI. Оценки (S/M/L) — оценка труда, не приоритет.
|
|||||||
|
|
||||||
5. **Избранные инструменты (fav tools)** — оценка S/M. Тоггл-звёздочка на
|
5. **Избранные инструменты (fav tools)** — оценка S/M. Тоггл-звёздочка на
|
||||||
карточках каталога, результатах поиска и в шапке страницы инструмента; список
|
карточках каталога, результатах поиска и в шапке страницы инструмента; список
|
||||||
id в localStorage. Избранное показывается отдельной секцией сверху каталога и
|
slug в localStorage. Избранное показывается отдельной секцией сверху каталога
|
||||||
поднимается в выдаче поиска (бонус к popularity при скоринге).
|
и поднимается в выдаче поиска (бонус к popularity при скоринге).
|
||||||
|
|
||||||
6. **Конвертация формата — НЕ отдельный инструмент, а выбор в кнопке Download**
|
6. **Конвертация формата — НЕ отдельный инструмент, а выбор в кнопке Download**
|
||||||
— оценка M/L. Решение и продуктовое обоснование: `docs/plan-seo.md` §0 и
|
— оценка M/L. Решение и продуктовое обоснование: `docs/plan-seo.md` §0 и
|
||||||
задача **S1f**; почему селектор не заменяет существующие convert-страницы —
|
задача **S1f**; почему селектор не заменяет существующие convert-страницы —
|
||||||
`docs/plan-seo.md` §5. Механическая часть уже есть: `ToolEntry.output`
|
`docs/plan-seo.md` §5. Механическая часть уже есть: `Tool.output`
|
||||||
(`OutputFormat`: mime/ext/qualityParamId) и кодирование по нему в
|
(`OutputFormat`: mime/ext/qualityParamId) и кодирование по нему в
|
||||||
`SchemaToolView.download()` — `docs/architecture.md`, раздел 3. Что осталось
|
`SchemaToolView.download()` — `docs/architecture.md`, раздел 3. Что осталось
|
||||||
сверх S1f:
|
сверх S1f:
|
||||||
- экспорт с лимитом размера → закрывает отложенный `reduce-to-size-png`
|
- экспорт с лимитом размера → закрывает отложенную страницу
|
||||||
(целевой KB, бинарный поиск по k из квантования). Проработать вместе с
|
`reduce-to-size-png` (целевой KB, бинарный поиск по k из квантования).
|
||||||
достройкой download-флоу.
|
Проработать вместе с достройкой download-флоу.
|
||||||
|
|
||||||
7. **`watermark-image-png` (водяной знак картинкой)** — оценка M. Нужна
|
7. **Страница `watermark-image-png` (водяной знак картинкой)** — оценка M. Нужна
|
||||||
overlay-механика: второй источник изображения (getOverlay/store), параметры
|
overlay-механика: второй источник изображения (getOverlay/store), параметры
|
||||||
scale/opacity/position/margin/aspect. Решается вместе с вопросом, как второй
|
scale/opacity/position/margin/aspect. Решается вместе с вопросом, как второй
|
||||||
источник картинки вписывается в schema-driven preview (`SchemaToolView`).
|
источник картинки вписывается в schema-driven preview (`SchemaToolView`).
|
||||||
|
|||||||
+22
-5
@@ -64,16 +64,18 @@
|
|||||||
|
|
||||||
Правило про числа не закрывает более опасный класс расхождений: документ не
|
Правило про числа не закрывает более опасный класс расхождений: документ не
|
||||||
устарел числом, а **перечисляет то, чего нет в коде**. В разделе «Реализовано»
|
устарел числом, а **перечисляет то, чего нет в коде**. В разделе «Реализовано»
|
||||||
карты лежали несуществующие id (`png-info`, `color-wheel-generator`,
|
карты лежали несуществующие страницы (`png-info`, `color-wheel-generator`,
|
||||||
`jpg-to-png`, `watermark-image-png`), которые агент принял бы за готовые
|
`jpg-to-png`, `watermark-image-png`), которые агент принял бы за готовые
|
||||||
инструменты. Счёт при этом был верный — число такой класс не ловит.
|
инструменты. Счёт при этом был верный — число такой класс не ловит.
|
||||||
|
|
||||||
Сверка множеств отвечает на вопрос однозначно и без allowlist, поэтому
|
Сверка множеств отвечает на вопрос однозначно и без allowlist, поэтому
|
||||||
автоматизирована: `web/src/lib/registry/tools-map-doc.test.ts` парсит раздел
|
автоматизирована: `web/src/lib/registry/tools-map-doc.test.ts` парсит раздел
|
||||||
«Реализовано» и сравнивает id с `TOOLS` в обе стороны (id без инструмента и
|
«Реализовано» и сравнивает slug с `PAGES` в обе стороны (slug без страницы и
|
||||||
инструмент без записи), плюс ловит повтор id между буллетами. Формат строки
|
страница без записи), плюс ловит повтор slug между буллетами. Формат строки
|
||||||
карты — всё до первого тире, id через `/` или `,` — закреплён в самом документе
|
карты — всё до первого тире, slug через `/` или `,` — закреплён в самом
|
||||||
и в тесте.
|
документе и в тесте. Сверяются именно страницы, а не инструменты: карта
|
||||||
|
описывает то, что видит пользователь, а один инструмент может обслуживать
|
||||||
|
несколько страниц (см. `docs/architecture.md`, раздел 3).
|
||||||
|
|
||||||
Чтобы сломанный парсер не выглядел как успех, у теста есть отдельная проверка
|
Чтобы сломанный парсер не выглядел как успех, у теста есть отдельная проверка
|
||||||
«разбор не пустой»: если формат карты снова поменяется, тест упадёт с сообщением
|
«разбор не пустой»: если формат карты снова поменяется, тест упадёт с сообщением
|
||||||
@@ -153,3 +155,18 @@ PR. Актуальные длительности — в логе прогона
|
|||||||
5. красный `e2e` только из-за ресурсов раннера фиксируется как ограничение
|
5. красный `e2e` только из-за ресурсов раннера фиксируется как ограничение
|
||||||
окружения: сначала проверяются один проект и один spec
|
окружения: сначала проверяются один проект и один spec
|
||||||
(`docs/agent-workflow.md`, раздел 3), а не увеличиваются таймауты.
|
(`docs/agent-workflow.md`, раздел 3), а не увеличиваются таймауты.
|
||||||
|
|
||||||
|
## 11. Тексты страниц: в базовой локали секция `pages` пустая
|
||||||
|
|
||||||
|
Заголовки и описания страниц переводятся (`pages.<slug>.*`), а EN-строка лежит в
|
||||||
|
реестре страниц — дублировать её в `en.ts` нельзя, иначе при правке реестра
|
||||||
|
английский текст разъедется со страницей, а `dict-consistency` этого не увидит.
|
||||||
|
Поэтому `en.ts` объявляет `pages: {}` — пустую секцию, а линтер правила
|
||||||
|
`i18n/dict-consistency` не считает это нарушением: в базовой локали отсутствие
|
||||||
|
`pages.*.title/description` разрешено опцией `baseLocaleFallback`, а полнота
|
||||||
|
перевода для остальных локалей проверяется строго и юнит-тестом
|
||||||
|
(`tools-i18n.test.ts`).
|
||||||
|
|
||||||
|
Альтернатива — `allowPaths: ["pages"]` — была бы слабее: она разрешила бы пустую
|
||||||
|
секцию `pages` в любой локали, то есть пропущенный перевод страницы прошёл бы
|
||||||
|
линтер молча. Пустая секция в EN и есть то, что делает правило точным.
|
||||||
|
|||||||
@@ -16,10 +16,13 @@
|
|||||||
> которые в бэклоге, здесь не дублируются: этот документ задаёт **порядок**,
|
> которые в бэклоге, здесь не дублируются: этот документ задаёт **порядок**,
|
||||||
> backlog — **учёт** (`docs/backlog.md`).
|
> backlog — **учёт** (`docs/backlog.md`).
|
||||||
>
|
>
|
||||||
> **Ids инструментов сверены с реестром** (`web/src/lib/registry/`) для шагов
|
> **Инструменты в этом документе названы по адресам страниц (slug).** В коде у
|
||||||
> FA-B и волн C–E. Названия из `docs/tools-audit.md` вида `png-to-hsl`,
|
> страницы `slug` (`registry/pages/`), у вызываемого инструмента — `id` без
|
||||||
> `color-wheel-generator`, `webp-to-png` в реестре отсутствуют (реальные —
|
> png-интента (`crop-png` → `id: "crop"`, `png-to-hsl` → `to-hsl`), модель —
|
||||||
> `hsl`, `color-wheel-png`): при переносе пункта аудита в шаг id сверять.
|
> `docs/architecture.md`, раздел 3. Названия из `docs/tools-audit.md` вида
|
||||||
|
> `color-wheel-generator`, `webp-to-png` в реестре отсутствуют (реальная
|
||||||
|
> страница — `color-wheel-png`): при переносе пункта аудита в шаг сверять с
|
||||||
|
> `PAGES`.
|
||||||
|
|
||||||
## 0. Ключевое решение: select → buttons
|
## 0. Ключевое решение: select → buttons
|
||||||
|
|
||||||
|
|||||||
+35
-29
@@ -1,11 +1,11 @@
|
|||||||
# План: SEO + GEO — индексация и конкуренция в image tools
|
# План: SEO + GEO — индексация и конкуренция в image tools
|
||||||
|
|
||||||
> **Статус: active.** Адреса страниц инструментов — `/tools/<id>` (`crop-png` и
|
> **Статус: active.** Адреса страниц инструментов — `/tools/<slug>` (`crop-png`
|
||||||
> подобные), каталог — `/list-tools`; отдельного поля `slug` в реестре пока нет,
|
> и подобные), каталог — `/list-tools`; реестр двухслойный: страница `slug`
|
||||||
> `id` и есть адрес.
|
> ссылается на инструмент по `id` (`docs/architecture.md`, раздел 3).
|
||||||
>
|
>
|
||||||
> **`noindex, nofollow` выставлен на каждой странице инструмента**
|
> **`noindex, nofollow` выставлен на каждой странице инструмента**
|
||||||
> (`web/src/routes/tools/[id]/+page.svelte`) — осознанно, до появления
|
> (`web/src/routes/tools/[slug]/+page.svelte`) — осознанно, до появления
|
||||||
> уникального контента (S2). Пока он стоит, сайт не индексируется вообще, и
|
> уникального контента (S2). Пока он стоит, сайт не индексируется вообще, и
|
||||||
> **S1b–S1e (sitemap, `robots.txt` с `Sitemap:`, canonical, OG) не дают
|
> **S1b–S1e (sitemap, `robots.txt` с `Sitemap:`, canonical, OG) не дают
|
||||||
> эффекта**: их имеет смысл делать вместе со снятием `noindex`, а не вместо
|
> эффекта**: их имеет смысл делать вместе со снятием `noindex`, а не вместо
|
||||||
@@ -40,22 +40,24 @@
|
|||||||
- **Форматы: формат = выбор в UI, не отдельные страницы.** Download-кнопка
|
- **Форматы: формат = выбор в UI, не отдельные страницы.** Download-кнопка
|
||||||
каждого инструмента — с селектором PNG/JPG/WebP/BMP (+quality для lossy). Райз
|
каждого инструмента — с селектором PNG/JPG/WebP/BMP (+quality для lossy). Райз
|
||||||
«выбор формата/качества в Download» вытащен из backlog №7 в S1f: механика уже
|
«выбор формата/качества в Download» вытащен из backlog №7 в S1f: механика уже
|
||||||
есть (`ToolEntry.output`), нужна как продуктовая основа «image
|
есть (`Tool.output`), нужна как продуктовая основа «image
|
||||||
tools»-позиционирования. В направлении «из формата в PNG» отдельных
|
tools»-позиционирования. В направлении «из формата в PNG» отдельных
|
||||||
инструментов нет (загрузка принимает любой формат) — см. §5.
|
инструментов нет (загрузка принимает любой формат) — см. §5.
|
||||||
- **Адреса страниц `-png` сохраняются, внутренние имена — без.** Инструмент
|
- **Адреса страниц `-png` сохраняются, внутренние имена — без.** Инструмент
|
||||||
работает с любым форматом, поэтому его `id` не должен врать (`crop`, а не
|
работает с любым форматом, поэтому его `id` не должен врать (`crop`, а не
|
||||||
`crop-png`); png-интент держит адрес страницы и имя скачиваемого файла.
|
`crop-png`); png-интент держат адрес страницы и имя скачиваемого файла.
|
||||||
Требуется поле `slug` в `ToolEntry` + переход `routes/tools/[id]` → `[slug]`.
|
**Реализовано:** реестр разделён на инструменты (`registry/tools/`, уникальный
|
||||||
**Сейчас не реализовано:** в реестре `id: "crop-png"`, поля `slug` нет, роут —
|
`id`) и страницы (`registry/pages/`, уникальный `slug`), роут —
|
||||||
`tools/[id]/`; текущее состояние описано в `docs/architecture.md`, раздел 3.
|
`tools/[slug]/`; модель — `docs/architecture.md`, раздел 3. **Следствие для
|
||||||
**Следствие для SEO: редиректы не нужны, canonical и sitemap не меняются,
|
SEO: редиректы не понадобились, canonical и sitemap не меняются, адреса те
|
||||||
адреса остаются теми же** — поэтому это безопасная правка, которую можно
|
же** — поэтому это была безопасная правка до индексации, а не после. Побочная
|
||||||
делать до индексации, а не после. Побочная выгода: широкие интенты («crop
|
выгода: широкие интенты («crop image online») переносятся теми же страницами
|
||||||
image online») переносятся теми же страницами контентом — H1 «Crop image»,
|
контентом — H1 «Crop image», текст «works with PNG, JPG, WebP»; URL-алиасы без
|
||||||
текст «works with PNG, JPG, WebP»; URL-алиасы без `-png` по-прежнему не
|
`-png` по-прежнему не создаются (канонический беспорядок). Побочный эффект
|
||||||
создаются (канонический беспорядок). Порядок работ — `docs/backlog.md`, задача
|
модели: страница может ссылаться на один инструмент, а инструмент —
|
||||||
«Слаги инструментов (`*-png`) — развести `id` и адрес страницы».
|
обслуживать много страниц, поэтому следующие шаги (свести convert-страницы на
|
||||||
|
один `convert`, селектор формата в Download — S1f) не требуют новой миграции
|
||||||
|
реестра. Задача на консолидацию — `docs/backlog.md`.
|
||||||
- **Индексируемая локаль — EN** (базовая; конкурент англоязычный). RU-версия —
|
- **Индексируемая локаль — EN** (базовая; конкурент англоязычный). RU-версия —
|
||||||
отдельно и позже: префикс `/ru/`, prerender обеих локалей, hreflang
|
отдельно и позже: префикс `/ru/`, prerender обеих локалей, hreflang
|
||||||
(сознательно отложено). Пока язык переключается на клиенте (localStorage) —
|
(сознательно отложено). Пока язык переключается на клиенте (localStorage) —
|
||||||
@@ -65,11 +67,11 @@
|
|||||||
canonical и «корневость» страниц. Свой домен + корень. Текущий деплой — GitHub
|
canonical и «корневость» страниц. Свой домен + корень. Текущий деплой — GitHub
|
||||||
Pages (`.github/workflows/deploy.yml`); перенос на свой домен выполняется
|
Pages (`.github/workflows/deploy.yml`); перенос на свой домен выполняется
|
||||||
сменой origin в CI и `PUBLIC_SITE_ORIGIN`.
|
сменой origin в CI и `PUBLIC_SITE_ORIGIN`.
|
||||||
- **URL-схема инструментов:** короткие плоские адреса — `/tools/<id>` для
|
- **URL-схема инструментов:** короткие плоские адреса — `/tools/<slug>` для
|
||||||
инструмента, `/list-tools` для каталога, где `<id>` берётся из записи реестра.
|
страницы, `/list-tools` для каталога, где `<slug>` берётся из записи
|
||||||
Схему не менять без отдельного SEO-плана: после индексации переименование URL
|
`registry/pages/`. Схему не менять без отдельного SEO-плана: после индексации
|
||||||
означает 301 и потерю позиций. Разделение `id`/`slug` (решение выше) адресов
|
переименование URL означает 301 и потерю позиций. Разделение инструмента и
|
||||||
не меняет.
|
страницы (решение выше) адресов не меняет.
|
||||||
- **Без GA и рекламных скриптов:** аналитика — self-hosted Plausible/Umami или
|
- **Без GA и рекламных скриптов:** аналитика — self-hosted Plausible/Umami или
|
||||||
Cloudflare Web Analytics. «No tracking» — часть позиционирования, её нельзя
|
Cloudflare Web Analytics. «No tracking» — часть позиционирования, её нельзя
|
||||||
подрывать собственными скриптами.
|
подрывать собственными скриптами.
|
||||||
@@ -83,9 +85,10 @@
|
|||||||
- SvelteKit + `adapter-static`, `prerender = true` на весь сайт
|
- SvelteKit + `adapter-static`, `prerender = true` на весь сайт
|
||||||
(`web/src/routes/+layout.ts`) — весь контент в статичном HTML, ботам не нужен
|
(`web/src/routes/+layout.ts`) — весь контент в статичном HTML, ботам не нужен
|
||||||
JS. Это главный технический козырь.
|
JS. Это главный технический козырь.
|
||||||
- Инструменты в `web/src/lib/registry` с «SEO-шными» id (`crop-png`,
|
- Страницы в `web/src/lib/registry/pages` со «SEO-шными» slug (`crop-png`,
|
||||||
`add-border-png`, …) — URL уже повторяют паттерн конкурента. Актуальный
|
`add-border-png`, …) — URL уже повторяют паттерн конкурента, при этом
|
||||||
состав: `docs/tools-map.md`.
|
внутренние имена инструментов png-интента не держат. Актуальный состав:
|
||||||
|
`docs/tools-map.md`.
|
||||||
- На корневых страницах инструментов есть базовые `<title>` и `robots`; полный
|
- На корневых страницах инструментов есть базовые `<title>` и `robots`; полный
|
||||||
SEO-слой (canonical, OG, content и JSON-LD) остаётся S1/S2.
|
SEO-слой (canonical, OG, content и JSON-LD) остаётся S1/S2.
|
||||||
- Каталог со ссылками на все инструменты (внутренняя перелинковка) и карта
|
- Каталог со ссылками на все инструменты (внутренняя перелинковка) и карта
|
||||||
@@ -160,7 +163,7 @@
|
|||||||
**domain-required** (после покупки и DNS-настройки).
|
**domain-required** (после покупки и DNS-настройки).
|
||||||
|
|
||||||
- [ ] **[S1h]** Снять `noindex, nofollow` со страниц инструментов
|
- [ ] **[S1h]** Снять `noindex, nofollow` со страниц инструментов
|
||||||
(`web/src/routes/tools/[id]/+page.svelte`) — заменить на
|
(`web/src/routes/tools/[slug]/+page.svelte`) — заменить на
|
||||||
`<meta name="robots" content="index, follow" />`. **Это первым
|
`<meta name="robots" content="index, follow" />`. **Это первым
|
||||||
обязательным шагом S1:** пока тег стоит, остальные задачи S1 готовят
|
обязательным шагом S1:** пока тег стоит, остальные задачи S1 готовят
|
||||||
инфраструктуру, но не приводят трафик. Порядок: S1h ставится после
|
инфраструктуру, но не приводят трафик. Порядок: S1h ставится после
|
||||||
@@ -172,14 +175,16 @@
|
|||||||
|
|
||||||
- [ ] **[S1e]** OG-картинки per-tool: генерация PNG-карточек скриптом на
|
- [ ] **[S1e]** OG-картинки per-tool: генерация PNG-карточек скриптом на
|
||||||
существующем ядре (мы сами умеем рисовать PNG) — имя инструмента + бренд,
|
существующем ядре (мы сами умеем рисовать PNG) — имя инструмента + бренд,
|
||||||
файл `static/og/[id].png`, ссылка из `Seo.svelte`. Относительные пути
|
файл `static/og/[slug].png`, ссылка из `Seo.svelte`. Относительные пути
|
||||||
(`/og/crop-png.png`) работают без домена; при деплое на собственном домене
|
(`/og/crop-png.png`) работают без домена; при деплое на собственном домене
|
||||||
OG-картинки будут абсолютными и корректными.
|
OG-картинки будут абсолютными и корректными.
|
||||||
- [ ] **[S1f]** Download-кнопка с селектором формата PNG/JPG/WebP/BMP (+quality
|
- [ ] **[S1f]** Download-кнопка с селектором формата PNG/JPG/WebP/BMP (+quality
|
||||||
для lossy) для всех инструментов — механика `ToolEntry.output` уже есть
|
для lossy) для всех инструментов — механика `Tool.output` уже есть
|
||||||
(`docs/backlog.md`, «Идеи» №6, UX-райз «Download»). Продуктовая основа
|
(`docs/backlog.md`, «Идеи» №6, UX-райз «Download»). Продуктовая основа
|
||||||
«image tools»-позиционирования: без неё сайт остаётся PNG-сайтом. Вести
|
«image tools»-позиционирования: без неё сайт остаётся PNG-сайтом. Вести
|
||||||
параллельно с текущими quality-задачами.
|
параллельно с текущими quality-задачами. Побочно снимает `quality` из схем
|
||||||
|
страниц — после этого 14 convert-страниц сводятся на один инструмент
|
||||||
|
(`docs/backlog.md`).
|
||||||
- [ ] **[S1g]** Поведение конвертеров после S1f: не предлагать в меню форматов
|
- [ ] **[S1g]** Поведение конвертеров после S1f: не предлагать в меню форматов
|
||||||
формат исходника (disable) — чтобы `convert-png-to-jpg` не мог сохранить в
|
формат исходника (disable) — чтобы `convert-png-to-jpg` не мог сохранить в
|
||||||
png. Существующие страницы не трогать: `convert-png-to-jpg`,
|
png. Существующие страницы не трогать: `convert-png-to-jpg`,
|
||||||
@@ -350,7 +355,8 @@ with PNG, JPG, WebP» в тексте + селектор формата в Downl
|
|||||||
**один будущий инструмент `/convert-image`**: сначала пустой **core-tool** в
|
**один будущий инструмент `/convert-image`**: сначала пустой **core-tool** в
|
||||||
`core/` — функция ре-энкода («копия с перекодированием»: декодирует любой
|
`core/` — функция ре-энкода («копия с перекодированием»: декодирует любой
|
||||||
поддержанный исходник в ImageData и кодирует в выбранный формат; поверх неё
|
поддержанный исходник в ImageData и кодирует в выбранный формат; поверх неё
|
||||||
S1f строит селектор формата). `ToolEntry` и страницу добавлять **только вместе
|
S1f строит селектор формата). Инструмент и страницу добавлять **только вместе
|
||||||
|
|
||||||
с собственным текстом** («Convert any image to PNG, JPG or WebP in your
|
с собственным текстом** («Convert any image to PNG, JPG or WebP in your
|
||||||
browser») — core в коде не обязывает к странице. Этот же шаблон потом
|
browser») — core в коде не обязывает к странице. Этот же шаблон потом
|
||||||
закрывает форматные волны (jpg→webp и т.п.) без новых страниц.
|
закрывает форматные волны (jpg→webp и т.п.) без новых страниц.
|
||||||
|
|||||||
+4
-2
@@ -132,9 +132,11 @@ Manifest + service worker, оффлайн-режим («работает без
|
|||||||
исполнителе и реестре, поэтому может идти параллельно с SEO. Номер 11 выбран
|
исполнителе и реестре, поэтому может идти параллельно с SEO. Номер 11 выбран
|
||||||
чтобы не сдвигать уже используемые ссылки на фазы 2–10.
|
чтобы не сдвигать уже используемые ссылки на фазы 2–10.
|
||||||
|
|
||||||
- **Формат шага** — `{tool, params}`, список шагов применяется последовательно,
|
- **Формат шага** — `{id, params}`, список шагов применяется последовательно,
|
||||||
результат предыдущего идёт в следующий. Формат зафиксирован в §0 как общий для
|
результат предыдущего идёт в следующий. Формат зафиксирован в §0 как общий для
|
||||||
браузера, wasm и CLI, поэтому версионируется с первого дня.
|
браузера, wasm и CLI, поэтому версионируется с первого дня. Он уже совпадает с
|
||||||
|
полем `Page.steps` (`{ id, params? }`), так что страница может быть готовой
|
||||||
|
цепочкой без новой модели данных; исполняется пока первый шаг.
|
||||||
- **Первый шаг фазы:** модуль состояния цепочки + сохранение в localStorage +
|
- **Первый шаг фазы:** модуль состояния цепочки + сохранение в localStorage +
|
||||||
экспорт/импорт JSON (ошибки разбора уже описаны ключами
|
экспорт/импорт JSON (ошибки разбора уже описаны ключами
|
||||||
`errors.badPipelineShape` / `errors.pipelineVersion` / `errors.noSteps`).
|
`errors.badPipelineShape` / `errors.pipelineVersion` / `errors.noSteps`).
|
||||||
|
|||||||
+10
-11
@@ -2,13 +2,12 @@
|
|||||||
|
|
||||||
> Статус: **active**. Живой документ для сверки с каталогом.
|
> Статус: **active**. Живой документ для сверки с каталогом.
|
||||||
>
|
>
|
||||||
> **Карта перечисляет `id` записи реестра (`web/src/lib/registry/`), которые
|
> **Карта перечисляет `slug` страниц (`web/src/lib/registry/pages/`) — то есть
|
||||||
> одновременно являются адресами страниц:** `/tools/crop-png` — это
|
> адреса вида `/tools/crop-png`:** это `slug: "crop-png"` у страницы, которая
|
||||||
> `id: "crop-png"`. Отдельного поля `slug` в `ToolEntry` нет, поэтому id и адрес
|
> вызывает инструмент `id: "crop"`. Внутренние имена инструментов png-интента не
|
||||||
> сейчас совпадают; их разделение — решённая, но не реализованная задача
|
> держат, поэтому они от карты отделены; модель — `docs/architecture.md`,
|
||||||
> (`docs/backlog.md`, раздел «Мелочи всякие», и `docs/architecture.md`, раздел
|
> раздел 3. Источник правды — реестр. Этот список навигационный: сверять перед
|
||||||
> 3). Источник правды — реестр. Этот список навигационный: сверять перед
|
> использованием. Формат записи: `slug — параметры через запятую`. Сравнение с
|
||||||
> использованием. Формат записи: `id — параметры через запятую`. Сравнение с
|
|
||||||
> каталогом источника (объём, нишевые клоны серий Logo*/Icon*/Stamp*Signature*)
|
> каталогом источника (объём, нишевые клоны серий Logo*/Icon*/Stamp*Signature*)
|
||||||
> — замер в `docs/plan-seo.md`, §2; здесь он не дублируется.
|
> — замер в `docs/plan-seo.md`, §2; здесь он не дублируется.
|
||||||
>
|
>
|
||||||
@@ -20,13 +19,13 @@
|
|||||||
|
|
||||||
## 1. Реализовано
|
## 1. Реализовано
|
||||||
|
|
||||||
Ids приведены к фактическим в `web/src/lib/registry/`.
|
Ids приведены к фактическим в `web/src/lib/registry/pages/`.
|
||||||
|
|
||||||
**Формат строки (проверяется тестом
|
**Формат строки (проверяется тестом
|
||||||
`web/src/lib/registry/tools-map-doc.test.ts`):** в буллите всё **до первого
|
`web/src/lib/registry/tools-map-doc.test.ts`):** в буллите всё **до первого
|
||||||
тире** `—` — это id инструментов через `/` или `,`; всё после тире — свободное
|
тире** `—` — это slug страниц через `/` или `,`; всё после тире — свободное
|
||||||
описание параметров и не разбирается. Второй id нельзя оставлять после тире,
|
описание параметров и не разбирается. Второй slug нельзя оставлять после тире,
|
||||||
иначе он не попадёт в разбор и инструмент будет считаться неописанным.
|
иначе он не попадёт в разбор и страница будет считаться неописанной.
|
||||||
|
|
||||||
### Конвертация (`convert.ts`)
|
### Конвертация (`convert.ts`)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user