# Frontend Design Audit: ss-tools > Дата: 2026-06-02 (ревизия 3 — PR 0-4 выполнены) > Цель: Аудит фронтенд кода на предмет "плавающего" дизайна, выработка AI-native правил > Объём: ~120 файлов, 9 UI-атомов (+EmptyState), 55+ компонентов, 10 моделей (+DashboardHubModel), 9 сторов, все страницы, tailwind.config.js, guardrail-скрипт --- ## Содержание 1. [Методология](#1-методология) 2. [Архитектурный ландшафт](#2-архитектурный-ландшафт) 3. [Найденные проблемы](#3-найденные-проблемы) - 3.1 [Критические](#31-критические) - 3.2 [Средние](#32-средние) - 3.3 [Малые](#33-малые) 4. [Причины плавающего дизайна](#4-причины-плавающего-дизайна) 5. [AI-Native Design Rules](#5-ai-native-design-rules) 6. [Приоритетный план исправлений](#6-приоритетный-план-исправлений) 7. [Статус исправлений](#7-статус-исправлений) 8. [Следующие шаги для AI-First](#8-следующие-шаги-для-ai-first) 9. [Приложение: Карта файлов](#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`) Определены семантические цвета, которые должны быть единым источником истины: ```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 несвязанных подхода к управлению состоянием. **Доказательство:** ```typescript // ПАТТЕРН 1: Legacy writable store (src/lib/stores/sidebar.ts) import { writable, type Writable } from 'svelte/store'; export const sidebarStore: Writable = writable(initialState); // ПАТТЕРН 2: Rune-based store (src/lib/stores/maintenance.svelte.ts) export function createMaintenanceStore(): MaintenanceStore { let activeEvents = $state([]); 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 строк):** ```svelte ``` **Последствия:** - Невозможно протестировать 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}` ```typescript // Было (размазано): 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:** ```svelte