Files
ss-tools/frontend/ai-native-design-audit.md
busya ec6421de35 rename ss-tools to superset-tools across the entire project
- Replace all occurrences of 'ss-tools' with 'superset-tools' in 104 files
- Rename git bundle file ss-tools.bundle → superset-tools.bundle
- Update .gitignore pattern accordingly
- Preserve variable names (hasSsTools etc.) and code identifiers
2026-06-16 11:15:19 +03:00

46 KiB
Raw Permalink Blame History

Frontend Design Audit: superset-tools

Дата: 2026-06-02 (ревизия 3 — PR 0-4 выполнены) Цель: Аудит фронтенд кода на предмет "плавающего" дизайна, выработка AI-native правил Объём: ~120 файлов, 9 UI-атомов (+EmptyState), 55+ компонентов, 10 моделей (+DashboardHubModel), 9 сторов, все страницы, tailwind.config.js, guardrail-скрипт


Содержание

  1. Методология
  2. Архитектурный ландшафт
  3. Найденные проблемы
  4. Причины плавающего дизайна
  5. AI-Native Design Rules
  6. Приоритетный план исправлений
  7. Статус исправлений
  8. Следующие шаги для AI-First
  9. Приложение: Карта файлов

1. Методология

Сканированные области

frontend/
├── tailwind.config.js          ✅ конфиг темы
├── src/
│   ├── app.css                 ✅ 3 строки (tailwind directives)
│   ├── routes/                 ✅ все 35+ страниц
│   ├── lib/
│   │   ├── ui/                 ✅ 8 атомарных компонентов
│   │   ├── components/         ✅ 30+ доменных компонентов
│   │   ├── stores/             ✅ 9 сторов (8 legacy + 1 runes)
│   │   ├── models/             ✅ 9 моделей
│   │   ├── api/                ✅ клиентский слой
│   │   └── utils/              ✅ утилиты
│   └── components/             ✅ 30+ компонентов старого стиля

Критерии оценки

Каждый файл проверялся по 7 осям:

  1. Design tokens — использует ли семантические цвета из tailwind.config.js или сырые классы
  2. Component reuse — использует ли $lib/ui атомы или дублирует стили
  3. State architecture — есть ли Screen Model для сложных страниц
  4. Svelte 5 compliance — runes-only или legacy паттерны
  5. TypeScript — типизирован или JSDoc/any
  6. UX contracts — полнота семантических аннотаций
  7. Module size — не превышает ли 400 строк (INV_7)

2. Архитектурный ландшафт

2.1 Tailwind Config (tailwind.config.js)

Определены семантические цвета, которые должны быть единым источником истины:

theme: {
  extend: {
    colors: {
      primary: {
        DEFAULT: '#2563eb', // blue-600
        hover: '#1d4ed8',   // blue-700
        ring: '#3b82f6',    // blue-500
        light: '#eff6ff',   // blue-50
      },
      secondary: {
        DEFAULT: '#f3f4f6', // gray-100
        hover: '#e5e7eb',   // gray-200
        text: '#111827',    // gray-900
        ring: '#6b7280',    // gray-500
      },
      destructive: {
        DEFAULT: '#dc2626', // red-600
        hover: '#b91c1c',   // red-700
        ring: '#ef4444',    // red-500
        light: '#fef2f2',   // red-50
      },
      ghost: {
        hover: '#f3f4f6',   // gray-100
        text: '#374151',    // gray-700
        ring: '#6b7280',    // gray-500
      },
      terminal: { /* dark palette for log viewer */ },
      log: { /* debug, info, warning, error */ },
      source: { /* plugin, api, git, system */ },
    },
    width: {
      sidebar: '240px',
      'sidebar-collapsed': '64px',
    },
    fontFamily: {
      mono: ['"JetBrains Mono"', '"Fira Code"', 'monospace'],
    },
  },
}

2.2 UI-атомы (src/lib/ui/)

Компонент Описание Использует семантические токены
Button.svelte primary/secondary/danger/ghost variants bg-primary, bg-destructive
Card.svelte Container with title, padding variants border-gray-200 bg-white
Input.svelte Text input with label + error border-gray-300, focus:ring-primary-ring
Select.svelte Dropdown with label border-gray-300
PageHeader.svelte Title + subtitle + actions slot text-gray-900
Icon.svelte SVG icon set цвета не использует (currentColor)
HelpTooltip.svelte Tooltip helper
LanguageSwitcher.svelte Lang toggle

Вывод: Только Button системно использует семантические токены. Остальные атомы используют сырые Tailwind-классы. Это источник "плавания" — если поменять primary цвет в конфиге, Button изменится, а Card/Input/Select — нет.

2.3 Состояние: 3 параллельных подхода

┌────────────────────────────────────────────────────────────────┐
│                    STATE MANAGEMENT MAP                         │
├────────────────────────────────────────────────────────────────┤
│                                                                │
│  LEGACY STORES (svelte/store writable)                         │
│  ├── sidebar.ts                — sidebar state + localStorage  │
│  ├── taskDrawer.ts             — task drawer visibility         │
│  ├── datasetReviewSession.ts   — dataset review session         │
│  ├── environmentContext.ts     — env context + selection        │
│  ├── health.ts                 — health check state             │
│  ├── activity.ts               — derived activity               │
│  ├── assistantChat.ts          — assistant chat session         │
│  └── translationRun.ts         — translation run tracking       │
│                                                                │
│  MODERN STORES (.svelte.ts with $state)                        │
│  ├── maintenance.svelte.ts     — maintenance + WebSocket        │
│                                                                │
│  SCREEN MODELS (.svelte.ts class with $state)                  │
│  ├── MigrationModel.svelte.ts  — migration wizard state        │
│  ├── GitManagerModel.svelte.ts  — git manager state            │
│  ├── GitStatusModel.svelte.ts   — git status per dashboard     │
│  ├── GitConfigModel.svelte.ts   — git configuration            │
│  ├── BranchModel.svelte.ts      — branch management            │
│  ├── DeploymentModel.svelte.ts   — deployment state            │
│  ├── CommitModel.svelte.ts       — commit history state        │
│  ├── MigrationSettingsModel.svelte.ts — migration settings     │
│  └── MappingsModel.svelte.ts     — db mapping state            │
│                                                                │
└────────────────────────────────────────────────────────────────┘

Проблема: AI-агент видит 3 разных паттерна и не знает, какой выбрать для нового кода.

2.4 Структура компонентов: двойная иерархия

src/lib/ui/               # Атомарные UI-компоненты (8 файлов)
├── Button.svelte
├── Card.svelte
├── Input.svelte
├── Select.svelte
├── PageHeader.svelte
├── Icon.svelte
├── HelpTooltip.svelte
└── LanguageSwitcher.svelte

src/lib/components/        # Доменные компоненты (30+ файлов)
├── layout/
├── translate/
├── reports/
├── dataset-review/
├── ui/                    # SearchableMultiSelect, MultiSelect
├── health/
├── llm/
└── assistant/

src/components/            # Старые компоненты (30+ файлов)
├── auth/
├── git/
├── tasks/
├── tools/
├── backups/
├── storage/
├── llm/
├── EnvSelector.svelte
├── DashboardGrid.svelte
├── MappingTable.svelte
├── TaskRunner.svelte
├── TaskHistory.svelte
├── Toast.svelte
├── Navbar.svelte
├── Footer.svelte
├── StartupEnvironmentWizard.svelte
└── ...

Проблема: src/components/ — наследие, но непонятно, почему часть компонентов там, а часть в lib/components/. Для AI-агента это создаёт неопределённость.

2.5 Карта страниц и их состояния

Страница Строк $state атомов Model? Использует $lib/ui? Типизация
dashboards/+page.svelte 2765 ~30+ нет частично lang="ts"
datasets/+page.svelte 472 ~15+ нет нет lang="ts"
migration/+page.svelte 637 ~5 (в модели) MigrationModel Button, Card, PageHeader lang="ts"
settings/+page.svelte 290 ~5 нет нет lang="ts"
validation-tasks/+page.svelte 519 ~10 нет нет JSDoc
reports/+page.svelte 204 ~6 нет PageHeader без типов
dashboards/[id]/+page.svelte 582 ~15 частично (GitStatusModel) нет lang="ts"
datasets/review/+page.svelte
translate/+page.svelte

3. Найденные проблемы

3.1 🔴 Критические

🔴 Проблема 1: Трёхголовая архитектура состояния

Описание: В проекте одновременно существуют 3 несвязанных подхода к управлению состоянием.

Доказательство:

// ПАТТЕРН 1: Legacy writable store (src/lib/stores/sidebar.ts)
import { writable, type Writable } from 'svelte/store';
export const sidebarStore: Writable<SidebarState> = writable(initialState);

// ПАТТЕРН 2: Rune-based store (src/lib/stores/maintenance.svelte.ts)
export function createMaintenanceStore(): MaintenanceStore {
    let activeEvents = $state<unknown[]>([]);
    return { get activeEvents() { return activeEvents; }, ... };
}

// ПАТТЕРН 3: Screen Model class (src/lib/models/MigrationModel.svelte.ts)
export class MigrationModel {
    currentStep: number = $state(1);
}

Последствия:

  • AI-агент не может определить, какой паттерн использовать
  • Разный DX для тестирования: Model тестируется без DOM, Store — через get()

🔴 Проблема 2: Нет Screen Model для сложных страниц

Описание: Страницы с 10+ $state атомами и cross-widget инвариантами не выделены в Model.

Доказательство — dashboards/+page.svelte (2765 строк):

<!-- ❌ ВСЁ В ОДНОМ ФАЙЛЕ: ~30 атомов, ~50 функций -->
<script lang="ts">
  let allDashboards = $state([]);
  let dashboards = $state([]);
  let isLoading = $state(true);
  let error = $state(null);
  let currentPage = $state(1);
  let searchQuery = $state("");
  // ... ещё 20+ атомов, 50+ функций, Git-операции, всё инлайн
</script>

Последствия:

  • Невозможно протестировать unit-тестами без DOM
  • AI-агент теряет контекст к 20-й функции
  • Нарушение INV_7 (>400 строк)

🔴 Проблема 3: Мёртвые ссылки — навигация на несуществующие роуты

Описание: Ссылки формировались как сырые строки в 40+ местах. Если роут переименован или не создан — 404 в рантайме.

Найденные битые роуты:

Ссылка Откуда Статус
/dashboards/{id}/validation?env_id= dashboards/[id]/+page.svelte, dashboards/health/+page.svelte СОЗДАН
/validation/{taskId} ScheduleAtAGlance.svelte ИСПРАВЛЕН

Исправление (2026-06-02):

  • Создан $lib/routes.ts — централизованный реестр с type-safe builder-функциями
  • 47 raw goto/href мигрированы на ROUTES.*() (21 файл)
  • Добавлен link-integrity тест (46 тестов + сканирование raw-строк)
  • Создан роут /dashboards/{id}/validation/
  • Починена ссылка /validation/{taskId}/validation-tasks/{taskId}
// Было (размазано):  goto(`/dashboards/${id}/validation?env_id=${envId}`);
// Стало (SSOT):      goto(ROUTES.dashboards.validation(id, envId));

Результат: 698 тестов, 0 raw goto/href, 0 битых роутов.

Новые AI-Native правила (Rules 9-10, см. раздел 5.2):

  • Rule 9: Все навигационные ссылки — только через ROUTES.*()
  • Rule 10: Link-integrity тест обязателен в CI

🔴 Проблема 4: Размытие дизайн-токенов

Описание: Семантические цвета из tailwind.config.js используются непоследовательно. Рядом с bg-primary соседствуют bg-indigo-50, bg-gradient-to-br from-slate-50 via-white to-sky-50, bg-red-100.

Доказательство — сравнение паттернов в dashboards/+page.svelte:

<!-- ✅ Семантический токен (редко) -->
<button class="bg-primary text-white hover:bg-primary-hover">

<!-- ❌ Дрейф: indigo вместо primary -->
<button class="inline-flex items-center justify-center rounded-lg border border-indigo-300 bg-indigo-50 px-4 py-2 text-sm font-medium text-indigo-700 transition-colors hover:bg-indigo-100">

<!-- ❌ Дрейф: сырой red вместо destructive -->
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4 flex items-center justify-between">

<!-- ❌ Рядом destructive (правильно) и сырой red (неправильно) -->
<button class="px-4 py-2 bg-destructive text-white rounded hover:bg-destructive-hover transition-colors">

<!-- ❌ sky-50, gradient — нет в токенах -->
<div class="relative overflow-hidden rounded-3xl border border-slate-200 bg-gradient-to-br from-slate-50 via-white to-sky-50 p-8 shadow-sm">

Примеры из других файлов:

<!-- validation-tasks/+page.svelte — сырые цвета -->
<select class="rounded-md border border-slate-300 px-2 py-1.5 text-sm">

<!-- settings/+page.svelte — сырые цвета -->
<div class="border-b border-gray-200">

<!-- reports/+page.svelte — сырой Card вместо компонента Card -->
<div class="rounded-xl border border-slate-200 bg-white p-4 shadow-sm">

<!-- settings/+page.svelte — 3 разных стиля кнопок в одном файле -->
<button class="bg-blue-600 text-white px-3 py-1.5 text-sm rounded hover:bg-blue-700">
<button class="px-3 py-1.5 text-sm border border-gray-300 rounded hover:bg-gray-100">
<button class="text-blue-600 hover:text-blue-800 text-sm font-medium">

Последствия:

  • Изменение primary в tailwind.config.js не синхронизируется с indigo/sky-кнопками
  • AI-агент не может определить "правильный" цвет — каждый раз гадает
  • Визуальная несогласованность: кнопки на одной странице выглядят по-разному
  • 5 разных стилей для одного и того же UI-паттерна

3.2 🟡 Средние

🟡 Проблема 5: Button-компонент не стандартизирован

Описание: Только migration/+page.svelte системно использует $lib/ui/Button. 7 разных стилей ручных <button> в проекте.


🟡 Проблема 6: Дублирование каталогов компонентов

Описание: 3 корневые директории (lib/ui/, lib/components/, components/) без чётких правил. src/components/ — замороженное наследие.


🟡 Проблема 7: 33 any в TypeScript

Описание: 33 вхождения any в исходниках — функции getRunLink(run: any), dashboard: any, catch (err: any). Ключевые цели:

  • function getRunLink(run: any) → конкретный интерфейс Run
  • dashboard: anyDashboard
  • catch (err: any)catch (err: unknown)

Последствие: AI-агент вынужден угадывать форму данных. any — главный источник галлюцинаций.


🟡 Проблема 8: JSDoc вместо TypeScript

Описание: validation-tasks/+page.svelte и другие страницы используют JSDoc вместо <script lang="ts">.


🟡 Проблема 9: 8 легаси-сторов блокируют миграцию на runes

Описание: Все сторы, кроме maintenance.svelte.ts, используют Svelte 4 writable().


🟡 Проблема 10: Неравномерное покрытие UX-контрактами

@UX_REACTIVITY отсутствует везде, кроме MigrationPage. См. таблицу ниже.


🟡 Проблема 11: Нет Component Catalog — SSOT для UI-компонентов

Описание: AI-агент не может за 1 чтение узнать, какие компоненты существуют и какие у них пропсы. Масштаб: 8 атомов + 30+ доменных компонентов — все с разными интерфейсами пропсов.


🟡 Проблема 12: Нет API Data Contracts

Описание: api.ts содержит 30+ endpoint-методов, но ни один не документирует входные/выходные DTO. AI-агент не знает форму API-ответов без чтения бэкенда.


3.3 🟢 Малые

🟢 Проблема 13: cn() utility не строго типизирован


🟢 Проблема 14: Дублирование debounce


🟢 Проблема 15: Zero-state не переиспользуется


🟢 Проблема 16: Border radius плавает


4. Причины плавающего дизайна

4.1 Отсутствие единого Design Language

У проекта есть tailwind.config.js с семантическими токенами, но нет документированных правил:

  • Когда использовать primary vs blue-600 vs indigo-600?
  • Какой border radius для чего?
  • Какой набор компонентов обязателен к использованию?

4.2 Две эпохи Svelte

Проект начинался на Svelte 4 (writable stores, on:event, export let), потом мигрировал на Svelte 5 (runes, $state). Миграция не завершена:

  • 8 stores остались на writable
  • 30+ компонентов в src/components/ — наследие
  • Новые страницы пишутся на runes, но без model-first

4.3 AI-агенты без правил

Каждый AI-агент (человек или LLM) при создании новой страницы:

  1. Не знает, какой цвет использовать — выбирает "похожий"
  2. Не знает, какой компонент переиспользовать — пишет с нуля
  3. Не знает, куда положить файл — кладёт в routes/ или components/
  4. Не знает паттерн состояния — пишет $state + функции инлайн

4.4 Нет "шлюза" для нового кода

Нет code review checklist или pre-commit хука, который проверял бы:

  • Использует ли страница <Button> вместо ручного <button>?
  • Использует ли страница семантические токены?
  • Есть ли Screen Model для страницы с >5 атомами?

5. AI-Native Design Rules

Эти правила предназначены для включения в промпт AI-агента (Svelte-кодера) и для CI-валидации.

Rule 1: Semantic Color Tokens Over Raw Colors

// ✅ CORRECT
<div class="bg-primary text-white hover:bg-primary-hover">
<div class="bg-destructive text-white">
<div class="bg-secondary text-secondary-text">

// ❌ WRONG
<div class="bg-blue-600 text-white">
<div class="bg-indigo-50 text-indigo-700">
<div class="bg-red-100 text-red-700">
<div class="bg-gradient-to-br from-slate-50 via-white to-sky-50">

Исключение: Только в tailwind.config.js при определении новых токенов.


Rule 2: UI Atoms Are Mandatory For Pages

// ✅ CORRECT
import { Button, Card, Input, Select, PageHeader } from "$lib/ui";

<Button variant="primary" onclick={handleSave}>
<Card>
<PageHeader title="Settings" subtitle={() => <p>Sub</p>} />

// ❌ WRONG
<button class="px-3 py-1 bg-primary rounded text-white">
<div class="bg-white rounded-xl border shadow-sm p-6">
<div class="flex items-center justify-between mb-8">
  <h1 class="text-3xl font-bold">Settings</h1>

Исключение: layout-компоненты и сами атомы в $lib/ui/.


Rule 3: Screen Model For Any Page With >5 $state Atoms

// ✅ CORRECT: DashboardHubModel.svelte.ts
export class DashboardHubModel {
  dashboards: Dashboard[] = $state([]);
  page: number = $state(1);
  searchQuery: string = $state("");
  // ...
  async loadDashboards(): Promise<void> { ... }
  async deleteDashboard(id: string): Promise<void> { ... }
}

// ✅ CORRECT: migration/+page.svelte (thin render layer)
const model = new MigrationModel();
onMount(() => model.loadEnvironments());

// ❌ WRONG: dashboards/+page.svelte (2765 строк, ~30 $state атомов инлайн)

Trigger: Если в <script> страницы больше 5 $state(...) вызовов → создай Model.


Rule 4: Runes Only For New State Code

// ✅ CORRECT (.svelte.ts)
let count = $state(0);
const doubled = $derived(count * 2);

// ❌ WRONG (.ts)
import { writable, derived } from 'svelte/store';
const count = writable(0);
const doubled = derived(count, $count => $count * 2);

Исключение: Только рефакторинг существующих writable-сторов, у которых есть подписчики.


Rule 5: One Component Directory — lib/components/

// ✅ CORRECT
src/lib/components/git/GitManager.svelte
src/lib/components/git/BranchSelector.svelte

// ❌ WRONG
src/components/git/GitManager.svelte
src/components/git/BranchSelector.svelte

Trigger: Новый доменный компонент → src/lib/components/<domain>/. src/components/ — заморожено.


Rule 6: TypeScript Required On Pages

// ✅ CORRECT
<script lang="ts">
  let { data }: { data: PageData } = $props();
  let items: Item[] = $state([]);

// ❌ WRONG
<script>
  /** @type {Array<any>} */ let items = $state([]);

Rule 7: Consistent Border Radius Hierarchy

rounded-md   → inputs, buttons, selects  (h-10)
rounded-lg   → cards, containers, modals
rounded-xl   → panels, drawers (опционально)
rounded-3xl  → zero-state / empty-state только

Rule 8: Page Max 400 Lines

>400 lines → Model + Component decomposition
>10 cyclomatic complexity → helper functions

Rule 9: Route Registry For Navigation (NEW 2026-06-02)

// ✅ CORRECT
import { ROUTES } from '$lib/routes.js';
<a href={ROUTES.dashboards.validation(id, envId)}>History</a>
goto(ROUTES.dashboards.detail(id, envId));

// ❌ WRONG — raw string, неверифицируемо, риск 404
<a href={`/dashboards/${id}/validation?env_id=${envId}`}>History</a>
goto(`/dashboards/${id}?env_id=${envId}`);

Trigger: Любая навигация (goto, href, window.location.href) → только через ROUTES.*().

Исключение: Внешние URL (/api/auth/login/adfs).


Перед каждым CI-run:
  1. vitest run src/lib/__tests__/routes-link-integrity.test.ts
  2. Падать при любых raw goto/href строках (кроме ROUTES)
  3. Падать, если ROUTES builder не匹配 ни один файловый роут

6. Приоритетный план исправлений

Выполнено (PR 0-4, 2026-06-02)

# Задача Статус
PR 0 — Contract alignment: semantics-svelte skill + svelte-coder prompt — raw Tailwind заменён на semantic tokens 3 файла
PR 1 — Token expansion: tailwind.config.js (+surface/border/text/success/warning/info), атомы переведены на tokens 8 файлов
PR 1 — Button: destructive variant, danger как deprecated alias 1 файл
PR 1 — Icon: classNameclass: className 1 файл
PR 1 — Input/Select: Math.random() → детерминированный счётчик для ID 2 файла
PR 1 — svelte.config.js: алиас $components помечен @DEPRECATED 1 файл
PR 2 — Guardrail: scripts/audit-frontend-style.mjs — CI-ready аудит 1 файл
PR 3 — Pilot page: migration/+page.svelte — 0 raw-color violations, все кнопки → <Button> 1 файл
PR 3 — EmptyState: новый атом + barrel export 2 файла
PR 3 — cn() utility: тип расширен до clsx-совместимого 1 файл
PR 4 — DashboardHubModel: 350 строк, 35 атомов + 35 методов 1 файл
PR 4 — Helpers: dashboard-helpers.ts — 10 чистых функций 1 файл
PR 4 — ColumnFilterPopover: 5 инстансов вместо 5×35 строк 1 файл
PR 4 — DashboardRow: компонент строки таблицы (с semantic tokens) 1 файл
PR 4 — Dashboards page: 2765→1340 строк (-51%), 244→0 raw-color violations 1 файл
PR 4 — Tests fix: this-context bugs, миграционные тесты 2 файла

P0 — Следующая итерация

# Задача Файлы Ожидаемый результат
0.1 Modal extraction: вынос migration/backup модалок из dashboard page dashboards/+page.svelte 1340 → <400 строк
0.2 Фикс migration теста (1 remaining integration timing) тест 100% green

P1 — Важно (5-6 часов)

# Задача Файлы Ожидаемый результат
1.1 Выделить DatasetHubModel + ValidationTasksModel + ReportsModel 3 страницы model-first для оставшихся сложных страниц
1.2 Заменить raw colors на semantic tokens на ВСЕХ страницах ~70 файлов 3892 violations → <100
1.3 Заменить ручные <button> на <Button> на ВСЕХ страницах ~50 файлов Единый стиль кнопок
1.4 Миграция 2 сторов на .svelte.ts taskDrawer.ts, sidebar.ts Runes-only stores

P2 — Долгосрочно

# Задача Файлы Ожидаемый результат
2.1 Консолидировать src/components/ в lib/components/ 40 файлов Одна иерархия
2.2 Мигрировать оставшиеся 6 writable-сторов 6 файлов Runes-only
2.3 Подключить DashboardRow в grid шаблон dashboards/+page.svelte Ещё -80 строк

7. Статус исправлений (2026-06-02, ревизия 3)

PR 0: Contract Alignment

Изменение Файл(ы) Результат
semantics-svelte §VI — канонический шаблон на semantic tokens + <Button> SKILL.md Агенты копируют правильный шаблон
semantics-svelte §VII — Design Token Canon с таблицей / примеров SKILL.md Источник истины для цветов
semantics-svelte §I — 2 новых @INVARIANT (UI reuse + legacy freeze) SKILL.md Правила встроены в протокол
svelte-coder.md — Visual system переписан, добавлены UI reuse rules agent prompt Агент знает $lib/ui mandatory
svelte-coder.md — Frozen zones секция agent prompt src/components/ объявлен legacy
svelte.config.js$components alias помечен @DEPRECATED config Агент предупреждён

PR 1: Design Tokens + UI Atoms

Изменение Файл(ы) Результат
Расширены токены: surface (page/card/muted), border (DEFAULT/strong), text (DEFAULT/muted/subtle/inverse), success, warning, info tailwind.config.js 20+ новых токенов
Card.svelte: border-gray-200 bg-whiteborder-border bg-surface-card text-text 1 файл
Input.svelte: border-gray-300border-border-strong bg-surface-card text-text 1 файл
Select.svelte: то же + детерминированный ID 1 файл
PageHeader.svelte: text-gray-900text-text 1 файл
Button.svelte: destructive variant (канонический), danger deprecated 1 файл
Icon.svelte: classNameclass: className 1 файл
EmptyState.svelte: новый атом + barrel export 2 файла

PR 2: Guardrail

Изменение Файл(ы) Результат
scripts/audit-frontend-style.mjs — сканирует raw-цвета, oversized pages, model-first violations 1 файл 3892 violations каталогизированы, CI-ready
Результат: 70+ файлов с raw цветами, 11 oversized pages, 27 model-first warnings Карта долга готова

PR 3: Pilot — Migration Page

Изменение Файл(ы) Результат
Все raw цвета → semantic tokens migration/+page.svelte 0 raw-color violations
Все <button><Button variant="..."> то же
cn() utility: тип расширен до clsx-совместимого (ClassValue) lib/utils.ts
Миграционный тест: селектор обновлён (class → semantic) тест 4/4 pass

PR 4: DashboardHub Decomposition

Dashboard page metrics:

Метрика До После
Строк 2765 1340 (-51%)
Script строк ~1100 ~65 (-94%)
Raw-color violations 244 0
$state атомов в page ~35 3 (environments deriveds)
Функций в page ~55 5 (document click, search, env created, $effects)
Model нет DashboardHubModel (350 строк, класс)
Column filter popovers 5 копий inline ColumnFilterPopover (1 компонент)
Grid row компонент нет DashboardRow.svelte (готов)
Helpers inline dashboard-helpers.ts (10 функций)

Dashboard page violations history:

2765 строк, 244 violations → (script extraction) → 2585 строк
→ (filter popover component) → 2437 строк, 212 violations  
→ (Model extraction) → 1340 строк, 208 violations
→ (this-context fixes) → 1340 строк
→ (batch semantic tokens) → 1340 строк, 28 violations
→ (edge case tokens) → 1340 строк, 8 violations
→ (final fixes) → 1340 строк, 0 violations ✅

Метрики качества (глобальные)

Метрика До После
Raw color violations (всего) 3892 3892 (каталог готов, миграция начата)
Dashboards page violations 244 0
Migration page violations ~90 0
UI atoms на семантических токенах 1/8 8/8
Model-first страниц 1 (migration) 2 (migration + dashboards)
Guardrail скрипт нет CI-ready
Oversized pages (>400) 11 11 (dashboards: 2765→1340, осталось 10)
Build
Tests (pass/total) 642/651 642/651 (pre-existing failures)
$lib/ui атомов 8 9 (+EmptyState)
Моделей 9 10 (+DashboardHubModel)
src/components/ legacy files 40 40 (заморожено, миграция — P2)

8. Следующие шаги для AI-First

На основе аудита и выполненной работы, следующие улучшения для AI-First (в порядке приоритета):

8.1 Component Catalog ($lib/components.ts)

Почему: AI-агент тратит ~5-10 чтений на поиск подходящего компонента. Component catalog сокращает это до 1.

Формат:

export const COMPONENTS = {
  Button: {
    path: "$lib/ui/Button.svelte",
    props: {} as ButtonProps,
    variants: ["primary", "secondary", "destructive", "ghost"] as const,
  },
  Card: { path: "$lib/ui/Card.svelte", props: {} as CardProps },
  PageHeader: { path: "$lib/ui/PageHeader.svelte", props: {} as PageHeaderProps },
  // ... все 30+ компонентов
} as const;

Трудоёмкость: 1 файл, ~50 строк.

8.2 API Data Contracts

Почему: Каждый fetchApi в api.ts — чёрный ящик для AI-агента. Без DTO агент галлюцинирует поля.

Формат:

// @BRIEF Fetch paginated dashboards for an environment
// @DATA_CONTRACT params -> { env_id, page, page_size, search?, filters? }
// @DATA_CONTRACT response -> { dashboards: Dashboard[], total: number, page: number }
getDashboards: <T = DashboardListResponse>(envId: string, opts?: DashboardQueryOptions) => ...

Трудоёмкость: 30+ методов, ~2-3 строки документации на каждый.

8.3 Типизация any (33 вхождения)

Почему: any — главный источник AI-галлюцинаций. Агент вынужден угадывать структуру.

Ключевые цели:

  • function getRunLink(run: any) → конкретный интерфейс Run
  • dashboard: anyDashboard
  • catch (err: any)catch (err: unknown)

Трудоёмкость: ~15-20 файлов, механическая работа.

8.4 Store Registry ($lib/stores/index.ts)

Почему: Агент видит все глобальные состояния в одном месте.

Трудоёмкость: 1 файл, ~30 строк.

8.5 Semantic Index Maintenance

  • Перестроить индекс с DuckDB для fuzzy-search
  • Исправить 103 unresolved relations (информационный шум)

Трудоёмкость: 1 команда + фикс таргетов @RELATION.


9. Приложение: Карта файлов

9.1 UI-атомы (src/lib/ui/)

Файл Строк Использует токены Типы Статус
Button.svelte 77 bg-primary, bg-destructive + destructive variant lang="ts" PR 1
Card.svelte 58 border-border bg-surface-card text-text lang="ts" PR 1
Input.svelte 68 border-border-strong bg-surface-card text-text lang="ts" PR 1
Select.svelte 60 border-border-strong bg-surface-card text-text lang="ts" PR 1
PageHeader.svelte 45 text-text lang="ts" PR 1
Icon.svelte 111 currentColor, class: className lang="ts" PR 1
EmptyState.svelte 50 text-text, text-text-muted, text-text-subtle lang="ts" PR 3
HelpTooltip.svelte P2
LanguageSwitcher.svelte P2

9.2 Модели (src/lib/models/)

Файл Строк Семантика Статус
MigrationModel.svelte.ts 472 model-first — state architecture reference
DashboardHubModel.svelte.ts 350 model-first — 35 атомов, 35 методов, 10 инвариантов PR 4
GitManagerModel.svelte.ts
GitStatusModel.svelte.ts
GitConfigModel.svelte.ts
BranchModel.svelte.ts
DeploymentModel.svelte.ts
CommitModel.svelte.ts
MigrationSettingsModel.svelte.ts
MappingsModel.svelte.ts

7.3 Сторы (src/lib/stores/)

Файл Строк Паттерн Семантические токены
sidebar.ts 79 writable legacy аннотации
taskDrawer.ts 112 writable legacy
environmentContext.ts writable legacy
datasetReviewSession.ts 87 writable legacy
assistantChat.ts writable legacy
health.ts writable legacy
activity.ts writable legacy
translationRun.ts writable legacy
maintenance.svelte.ts 203 $state modern

9.4 Страницы (src/routes/)

Файл Строк Model? $lib/ui? Типы UX контракты Raw violations
dashboards/+page.svelte 1340 DashboardHubModel Button ts 5/5 0
dashboards/[id]/+page.svelte 582 частично ts 4/5 ~90
datasets/+page.svelte 472 ts 1/5 ~45
datasets/review/+page.svelte
migration/+page.svelte 640 MigrationModel Button,Card,PageHeader ts 5/5 0
settings/+page.svelte 290 ts 4/5 ~40
validation-tasks/+page.svelte 519 JSDoc 4/5 ~90
reports/+page.svelte 204 PageHeader any 3/5 ~25
translate/+page.svelte
+layout.svelte 112 ts

7.5 Старые компоненты (src/components/)

src/components/
├── auth/
│   └── ProtectedRoute.svelte
├── git/
│   ├── GitManager.svelte
│   ├── BranchSelector.svelte
│   ├── CommitHistory.svelte
│   ├── CommitModal.svelte
│   ├── ConflictResolver.svelte
│   ├── DeploymentModal.svelte
│   ├── GitInitPanel.svelte
│   ├── GitOperationsPanel.svelte
│   ├── GitReleasePanel.svelte
│   └── GitWorkspacePanel.svelte
├── tasks/
│   ├── TaskResultPanel.svelte
│   └── TaskLogPanel.svelte
│   └── LogFilterBar.svelte
│   └── LogEntryRow.svelte
├── tools/
│   ├── MapperTool.svelte
│   └── DebugTool.svelte
├── backups/
│   ├── BackupManager.svelte
│   └── BackupList.svelte
├── storage/
│   ├── FileUpload.svelte
│   └── FileList.svelte
├── llm/
│   ├── ValidationReport.svelte
│   ├── ProviderConfig.svelte
│   └── DocPreview.svelte
├── DashboardGrid.svelte
├── EnvSelector.svelte
├── Footer.svelte
├── MappingTable.svelte
├── MissingMappingModal.svelte
├── Navbar.svelte
├── PasswordPrompt.svelte
├── RepositoryDashboardGrid.svelte
├── StartupEnvironmentWizard.svelte
├── TaskHistory.svelte
├── TaskList.svelte
├── TaskLogViewer.svelte
├── TaskRunner.svelte
├── Toast.svelte
├── DynamicForm.svelte
└── RepositoryDashboardGrid.svelte

10. Ключевые уроки (PR 0-4)

Что работает

  1. Model-first на Svelte 5 runes — класс с $state атомами + методами. Тестируется без DOM (new Model() → прямое тестирование инвариантов за ~10ms).
  2. Семантические токены — замена bg-indigo-600 text-whitebg-primary text-white делает дизайн централизованно управляемым. Batch-замена regex на странице заняла 5 минут, дала 87% сокращение violations.
  3. Guardrail-скриптscripts/audit-frontend-style.mjs даёт CI-проверку за секунды, каталогизирует весь долг.
  4. Компонент-экстракция (ColumnFilterPopover) — 5×35 строк inline → 1×83 строки + 5×10 строк вызова. Экономия ~85 строк на компонент.

⚠️ Риск: Model как "god object"

Статус: DashboardHubModel сейчас 350 строк, 35 методов — допустимо. Но нельзя просто докидывать методы при добавлении фич.

Правило (зафиксировано в модели и semantics-svelte):

Порог Действие
Model > 400 строк Декомпозиция — вынести helpers или разбить на submodels
Model > 40 public методов Split: FiltersModel + SelectionModel + GitActionsModel

План split для DashboardHubModel (когда потребуется):

  • DashboardFiltersModel — search, column filters, sort, visible options
  • DashboardSelectionModel — checkbox, select all/visible, bulk actions
  • DashboardGitActionsModel — git init, sync, commit, pull, push, status batch
  • Основной DashboardHubModel — композиция submodels + core (pagination, load, environments)

Что ломалось

  1. this-контекст — при переносе методов в класс Model, все onclick={m.method} теряют this. Нужно onclick={() => m.method()}.
  2. Legacy сторы в Model$derived(store.field) в .svelte.ts не работает (store — объект, не значение). Нужно оставлять deriveds в .svelte файле.
  3. Double m. prefixreplaceAll привёл к m.m.openFilterColumn в одном месте. Проверка grep m\.m\. обязательна после batch-замены.
  4. Source-scanning тесты — тесты, ищущие function handleX в исходниках, ломаются при рефакторинге. Нужно переписывать на поведенческие.

Рекомендации для следующих PR

  1. Перед model-extraction: сначала вынести чистые helpers, потом компоненты, потом Model.
  2. После замены $state→Model: grep \$\state( в page для проверки, что все ушли.
  3. После batch-замены цветов: npm run build && node scripts/audit-frontend-style.mjs.
  4. Тесты на source-scanning заменить на render + screen.findByText проверки.

Аудит выполнен 2026-06-01. Ревизия 3: 2026-06-02 (PR 0-4 выполнены). Полный контекст: ~120 файлов, 9 UI-атомов, 55+ компонентов, 10 моделей, 9 сторов, 35+ страниц.