12 KiB
Архитектура
Статус: active. Описание текущего кода: границы модулей, поток данных от реестра до скачивания, точки расширения. Канонический источник истины — сам код; этот документ объясняет, куда смотреть и что с чем связано, и не дублирует сигнатуры.
1. Слои
web/src/lib/
registry/ # источник истины: записи ToolEntry
registry-schema.ts # декларативная схема параметров + санитизация дефолтов
catalog.ts # группировка и счётчики для /list-tools
categories.ts # id/названия категорий
tool-icons.ts # id → иконка
core/ # чистый TS: операции над ImageData, без DOM
executor/ # единственная точка исполнения инструментов
i18n/ # словари ru/en, t(), списки для i18n-правил линтера
components/ # UI (fields/, layout/, ui/, schema-компоненты)
zip.ts # сборка мультифайлового результата (1 → many)
theme.svelte.ts # light/dark + localStorage
web/src/routes/
+page.svelte # главная
list-tools/ # каталог
tools/[id]/ # страница инструмента
kit/ # витрина компонентов (production, но исключена из i18n-гейта)
Правило зависимостей: components/** и routes/** знают про registry/**,
executor/** и core/**; core/** не знает ни о ком и не зависит от DOM;
registry/** описывает данные и знает только core/** и registry-schema.ts.
Нарушение этого порядка — повод не заводить импорт, а инвертировать зависимость.
2. Поток данных
registry/*.ts ──(TOOLS: ToolEntry[])
│
├─► catalog.ts ──► routes/list-tools (каталог, поиск, фильтры)
│
└─► routes/tools/[id]/+page.svelte
│ getTool(id) → запись реестра
▼
SchemaToolView.svelte оркестратор страницы
│ schema = tool.schema
├─► SchemaFields.svelte поля из schema.fields (FIELDS: kind → контрол)
├─► SchemaSourceTile/SchemaTextSource.svelte вход
└─► SchemaPreview + schema-preview-model.ts вид превью
│ buildSchemaPreviewModel(inputMode, resultKind, …) → source/result/hasResult
▼
executor.execute(ctx) единственная точка исполнения
│ сериализация параметров → worker → worker-handler → tool.run(ctx)
▼
core/* пиксели
│
▼
SchemaResultTile / SchemaTextResult + SchemaActions (download/copy)
│ кодирование по ToolEntry.output (mime/ext/quality)
└─► zip.ts, если result === "files"
Ключевые решения:
- Реестр — единственный источник истины. Страница, форма и пайплайн строятся
из записи; новый инструмент = новая запись + функция
run. - Исполнение идёт только через
executor.execute. Компоненты не зовутtool.runнапрямую — иначе нельзя перенести тяжёлое в worker/wasm без правки вызывающего кода. - Форма параметров не знает про инструмент. Она рендерится из
registry-schema.ts, поэтому добавление вида поля не трогает страницы. - Модель превью отделена от вёрстки.
schema-preview-model.ts— чистая функция, покрыта unit-тестами;SchemaPreview.svelteтолько рисует её вывод.
3. Запись реестра
ToolEntry (web/src/lib/registry/types.ts):
| Поле | Смысл |
|---|---|
id |
уникальный ключ; он же URL-anchor и ключ в пайплайне |
title |
EN-строка реестра, переводится через tools.*.title |
description |
EN-строка реестра, переводится через tools.*.description |
category |
id из categories.ts |
schema |
toolSchema({ fields, layout?, meta? }) |
input |
"image" | "text" | "none" — что ждёт инструмент на входе |
result |
"image" | "text" | "verdict" | "files" — что отдаёт |
output |
mime/ext/quality для download (не у всех инструментов) |
run(ctx) |
чистая функция: ctx → ToolResult |
Хелперы genTool / imgTool / textGen из types.ts задают run для типовых
случаев (приведение типа результата), чтобы запись не повторяла обвязку. TOOLS
собирается в registry/index.ts из восьми групп; getTool(id) — единственный
способ достать запись по id.
4. Схема параметров
registry-schema.ts — единственное место, где описаны виды полей.
- Виды поля (
fieldSpecs):number,slider,color,select,text,checkbox,dimension,color-pair,colors,offset,position9,font-style,plate,gradient. - Фабрика
field.*создаётField<T>; UI-контрол подбирается вSchemaFields.svelteпоspec.kindчерез картуFIELDS. schema.layoutгруппирует поля (resolveLayoutGroups); раскладка разделов — единственное место, где живёт порядок и группировка.- Значения по умолчанию даёт
defaultSchemaParams;applySourceDefaultsподставляет размеры текущего source;sanitizeSchemaParamsприводит мусор к допустимому виду. Правка поведения полей идёт здесь, а не в компоненте.
Новый вид поля требует: спека + фабрика field.* → запись в fieldSpecs →
контрол в components/fields/schema/ → ключ в FIELDS → дефолт и санитизация →
i18n-подписи → тест в registry-schema.test.ts.
5. Исполнение
executor/executor.ts — фабрика createExecutor(factories?) плюс готовый
экспорт execute. Контракт асинхронный (Promise), параметры сериализуются,
исполнение уходит в executor.worker.ts; worker-handler.ts разбирает
сообщение и зовёт run. Если worker недоступен — прямой вызов.
Отсюда следует: подключение wasm позже означает замену реализации внутри исполнителя, а не правку компонентов.
6. i18n
Словари — web/src/lib/i18n/ (en.ts, ru.ts, общий dict.ts с
LOCALES/BASE_LOCALE). Пользовательский текст живёт только в словарях и
читается через t(); строки реестра (title/description) — источник для
EN-ключей tools.* и словарь-переводчик. Инварианты паритета ключей,
плейсхолдеров и запрет хардкода проверяются линтером, детали —
web/eslint-plugins/README.md.
7. Точки расширения
| Задача | Где править |
|---|---|
| Новый инструмент | запись в registry/<group>.ts + i18n-ключи + тесты (рецепт ниже) |
| Новый вид поля | registry-schema.ts + контрол + FIELDS (раздел 4) |
| Новый маршрут/страница | web/src/routes/** + ссылка в каталоге |
| Новый визуальный элемент | компонент в components/** по образцу ui/ или layout/ |
| Новый дизайн-токен | web/src/app.css (только там) + потребитель сразу |
| Тяжёлая операция → worker/wasm | executor/**, без правок компонентов |
| Мультифайловый результат | result: "files" + zip.ts |
8. Рецепт: добавить инструмент
- Найти соседей. Поиск по ключу в
registry/<group>.tsи по всемуweb/src/— не заводить дублирующую константу, тип или утилиту. - Выбрать группу (
geometry/alpha/color/convert/filters/generate/text/analyze) и положить запись в её экспорт; при новой категории — сначалаcategories.ts. - Описать схему через
field.*, задатьlayout, если поля нужно сгруппировать. - Написать
runповерхcore/*; пиксельные операции — чистые функции вcore/, без DOM. - Проверить обязательные контракты для нового id: entry попадает в
TOOLS, страница/tools/<id>prerender-ится,input/resultсогласованы с тем, что делаетrun. - Добавить i18n-ключи во все локали сразу.
- Тесты: unit — в
registry/*.test.tsили рядом сcore-функцией; e2e-smoke — существующий spec по типуinput/result(правила —docs/testing-strategy.md). - Обновить
docs/tools-map.md: новый инструмент обязателен в разделе «Реализовано», формат строки — в шапке раздела. Проверяетweb/src/lib/registry/tools-map-doc.test.ts: отсутствующий в карте id и несуществующий в реестре id роняют тест. Обоснование и границы автоматизации —docs/decisions.md, раздел 4.
Ожидаемый минимум проверок: pnpm --dir web check и pnpm --dir web test; для
инструмента, меняющего пользовательский поток, — e2e (см. раздел 3
docs/quality-gates.md).