# Аудит архитектуры (2026-10) > Статус: **draft**. Сырые находки read-only аудита на оценку разработчика. > Подтверждённые пункты переезжают в `docs/backlog.md` или тематический > документ, отклонённые удаляются; после разбора файл — в `docs/archive/`. > > Примечание: `$effect` с `goto()` в `SchemaToolView.svelte:529-546` намеренно > исключён — бесшовная смена маршрута при структурном изменении цепочки > (`docs/architecture.md`, раздел 1) является требованием, не дефектом. Проверено и чисто (не является находкой): - Слоистость соблюдена: обратных импортов `core→registry/components`, `registry→components/routes/executor` нет. - Прямых вызовов `tool.run` в обход `executor.execute` нет. - DOM-API в `core/**` только в санкционированных `io.ts`/`domText.ts`. - Устаревшего Svelte 4 синтаксиса нет (`export let`, `$:`, `on:`, ``, `$store`, `createEventDispatcher` — 0 совпадений). - `console.error` в lib/routes отсутствует; worker-протокол кодирует ошибки; скачивание/encode — единый источник `core/io.ts`. ## High ### H1. `SchemaToolView.svelte` — god-component — СДЕЛАНО (2026-10) Компонент уменьшен со 1115 до ~660 строк (оркестратор: derived-связки, init, handleFiles, эффекты, workspace-разметка). Декомпозиция: - **H1a:** aligned-раскладка вынесена в `SchemaAlignedLayout.svelte` (view-only: разметка + scoped-стили, сниппет `stepCard` передаётся пропсом). - **H1b:** runner вынесен в `schema-tool-runner.svelte.ts` (`createSchemaToolRunner(ctx)`); идиома «`$state`-объект + модульные функции» зафиксирована в `docs/architecture.md`, раздел 9. - **H1c:** стейт шагов вынесен в `schema-tool-state.svelte.ts` (`createSchemaToolState(ctx)`: steps, touchedByStep/lastAxis, все мутации, DnD; `toolSchemaOf` — модульный экспорт). ### H2. Race condition превью — СДЕЛАНО (2026-10) `SchemaToolView.svelte`: добавлен generation-counter `runGen`; устаревший прогон `runChain` после `await` выходит без записи результатов, ошибок и сброса `running` (`finally` сбрасывает только актуальный); `init()` инкрементом инвалидирует прогоны прошлого хоста. ### H3. `registry-schema.ts` — 723 строки, 45 экспортов, 5 ответственностей `web/src/lib/registry-schema.ts` смешивает: (а) типы 15 видов полей (:14-233), (б) builder-DSL `field.*` (:273-360), (в) layout engine `resolveLayoutGroups` (:380-406), (г) семантику значений `defaultSchemaParams`/`sanitizeSchemaParams` (:503-770), (д) aspect-lock геометрию `withAspectLock`/`clampSourceAwareMaxes` (:434-489). Каждая из (в)-(д) — отдельный модуль. ## Med ### M1. Маски через `$effect` вместо `$derived.by` — СДЕЛАНО (2026-10) `SchemaToolView.svelte`: эффект вычисления `maskResults` заменён на `$derived.by`; ручной сброс `maskResults = {}` в `init()` удалён — derived пересчитывается сам по смене `steps`/`source`/`maskOnKeys`. ### M2. `init()` не сбрасывал часть состояния — СДЕЛАНО (2026-10) `SchemaToolView.svelte`: `init()` теперь очищает `touchedByStep` и `lastAxis` — метки от ключей шагов прошлого хоста больше не протекают в новую цепочку. Переработка самого паттерна `$effect(() => init())` — часть H1, отдельно не делалась. ### M3. `SchemaPreview.svelte` — интерфейс из ~40 пропсов `web/src/lib/components/schema/SchemaPreview.svelte:22-103`: весь стейт оркестратора проброшен вниз по одному (`stepResults`, `stepMaskable`, `stepMaskOn`, `stepMasks`, `format`, `quality`, `limitKb`, `alphaLoss` + 11 колбэков `on*`). Нужен группирующий view-model-объект или разбиение на подкомпоненты. ### M4. `runMask` обходит санитизацию параметров `SchemaToolView.svelte:591`: основной путь `run()` идёт через `execute` → `sanitizeSchemaParams` (`executor/executor.ts:153`), а mask-превью передаёт `step.params` как есть. Значения вне диапазона/невалидный hex дойдут до core-функций без клампинга. (Сам обход executor для `domOnly`-маски каноничен по `docs/architecture.md`; рассинхрон санитизации — нет.) ### M5. UI-метаданные дропдауна скачивания в core-слое `web/src/lib/core/io.ts:10-47`: `OUTPUT_FORMATS`/`OutputFormatOption` содержат `label` и `settings.quality` — конфигурацию презентационного дропдауна кнопки Download. Core по канону «не знает ни о ком». ### M6. Доменная политика сжатия в IO-модуле `web/src/lib/core/io.ts:191-210`: `fitWithinBytes` — стратегия «бинарный поиск по quality / по числу цветов квантизации» поверх `quantizeImage`, `findMaxColorsWithin`, `findQualityWithin`. Это доменная логика, не ввод-вывод (у файла 19 экспортов). ### M7. `clamp` продублирован 5 раз — СДЕЛАНО (2026-10) Создан `web/src/lib/core/math.ts` (`clamp`, `clampInt`, `clamp01`, `clampByte` — round-семантика `clampByte` и trunc-семантика `clampInt` сохранены раздельно); локальные копии удалены из `core/alpha.ts`, `core/background.ts`, `core/color.ts`, `core/convolution.ts`, `core/geometry.ts`, `core/palette.ts`, `core/pixel-fx.ts`. Вариант с опциональными границами в `registry-schema.ts` переведён на общий `clamp` через `?? -Infinity / ?? Infinity`. Копия в `palette.test.ts` оставлена — тесты независимы. ### M8. Два параллельных hex-парсера `parseHex` (`core/alpha.ts:147`, tuple, бросает `ToolError`) и `hexToRgb` (`core/palette.ts:8`, объект `{r,g,b}`). Половина core-модулей использует первый (`affine.ts:31`, `geometry.ts:20`, `morphology.ts:123,150`), другая — второй (`masks.ts:25,98`, `pixel-fx.ts:195`, `registry/tools/generate.ts:36`). ### M9. Гард `localStorage` продублирован 4 раза — СДЕЛАНО (2026-10) Создан `web/src/lib/storage.ts` (`storage()` + `safeSetItem`); локальные гарды удалены из `chains.svelte.ts`, `favorites.svelte.ts`, `i18n/locale.svelte.ts`, `theme.svelte.ts`. ### M10. Логика темы дублируется между `app.html` и `theme.svelte.ts` — ОТКЛОНЕНО `web/src/app.html:10-16` повторяет `systemTheme()` (`theme.svelte.ts:11-16`) и захардкоживает ключ `"theme"` (`theme.svelte.ts:3`). Решение (2026-10): оставить как есть. Inline-скрипт обязан выполниться синхронно до парсинга контента, чтобы тема не мигала, — импортировать `theme.svelte.ts` из него нельзя в принципе (модули async, `app.html` не проходит сборку TS). Дублирование неизбежно. Остаточный риск drift'а (принят, без фикса): ключ `"theme"`, значения `"dark"/"light"` и атрибут `dataset.theme` продублированы; при exception inline ставит `"light"`, а `initTheme()` при невалидном attr берёт `systemTheme()` (на практике безразлично — inline всегда выставляет attr). Если риск когда-то материализуется, дешёвая защита — unit-тест (~20 строк), читающий `app.html` и пиннющий контракт: ключ `"theme"`, `dataset.theme`, значения `dark`/`light`. ## Low ### L1. Флаг `running` общий для `run()` и `download()` `SchemaToolView.svelte:124,433,469`: параллельный auto-run и клик по download гоняют один `$state`; индикация и `disabled` ведут себя некорректно. ### L2. Дублирование dismiss-логики поповеров `SchemaDownload.svelte:62-76` и `ToolPickerButton.svelte:25-39` — побуквенно одинаковые ~15 строк: pointerdown-outside + Escape + подписка на `window` в `$effect`. Кандидат на shared action (`clickOutside`/`popoverDismiss`). Сами эффекты корректны. ### L3. Несогласованный API `Toggle` `SchemaToolView.svelte:811` — `bind:checked={aligned}`; `SchemaPreview.svelte:114-118` тот же компонент — `checked` + `onchange`. Компонент (`ui/Toggle.svelte:7`) поддерживает оба; стоит выбрать один паттерн. ### L4. Непроверяемые `as`-касты схемы `SchemaToolView.svelte:97,211` (`tool.schema as ToolSchema>`) и паттерн `spec as XSpec` в 14+ field-компонентах (`CheckboxControl.svelte:13`, `ColorControl.svelte:14`, `GradientControl.svelte:16` и др.); двойной каст `as unknown as WorkerLike` в `executor/executor.ts:35`. Касты отключают проверку соответствия spec kind'у. ### L5. Хардкод slug-ов вне registry `tool-icons.ts:54-142` — карта slug→icon без типизации по slug-union (переименование slug ломает иконки молча); `kit/+page.svelte:40` — захардкожены `["linear-gradient-png","resize-png","quantize-png"]` (kit — витрина, не production). ### L6. Дублирование CSS-значения превью-тайла `clamp(var(--space-brand), 30vh, 60vh)` в `PreviewTile.svelte:96` и `SchemaResultTile.svelte:129`; магические `30vh/60vh` не в токенах. ### L7. `localStorage.setItem` без try/catch вне app.html — СДЕЛАНО (2026-10) Все `setItem` в `theme.svelte.ts`, `chains.svelte.ts`, `favorites.svelte.ts`, `locale.svelte.ts` переведены на `safeSetItem` из `web/src/lib/storage.ts` (best-effort persistence: бросок в privacy-режиме проглатывается). ### L8. Переросшие файлы групп реестра и core `registry/tools/generate.ts` — 969 строк (~21 инструмент), `geometry.ts` — 753 (~18), `alpha.ts` — 681 (~20): конвенция «один файл на категорию», но навигация страдает. `core/geometry.ts` — 436 строк, смешаны поддомены (тайлинг, ресайн, contentBounds, ratio). `core/palette.ts` — 23 экспорта, смешаны color math и генерация `PixelImage` (`renderSwatches`/`renderWheel`/ `renderBlend`).