Files
ss-tools/frontend/ai-native-design-audit.md
2026-06-02 11:46:00 +03:00

884 lines
38 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Frontend Design Audit: ss-tools
> Дата: 2026-06-02 (ревизия 2)
> Цель: Аудит фронтенд кода на предмет "плавающего" дизайна, выработка AI-native правил
> Объём: ~120 файлов, 8 UI-атомов, 55+ компонентов, 9 моделей, 9 сторов, все страницы, tailwind.config.js + ROUTES registry + link-integrity test
---
## Содержание
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<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 строк):**
```svelte
<!-- ❌ ВСЁ В ОДНОМ ФАЙЛЕ: ~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}`
```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
<!-- ✅ Семантический токен (редко) -->
<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">
```
**Примеры из других файлов:**
```svelte
<!-- 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: any``Dashboard`
- `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
```svelte
// ✅ 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
```svelte
// ✅ 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
```typescript
// ✅ 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
```typescript
// ✅ 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
```svelte
// ✅ 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)
```svelte
// ✅ 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`).
---
### Rule 10: Link Integrity Test Required (NEW 2026-06-02)
```
Перед каждым CI-run:
1. vitest run src/lib/__tests__/routes-link-integrity.test.ts
2. Падать при любых raw goto/href строках (кроме ROUTES)
3. Падать, если ROUTES builder не匹配 ни один файловый роут
```
---
## 6. Приоритетный план исправлений
### ✅ Выполнено (2026-06-02)
| # | Задача | Статус |
|---|--------|--------|
| ✅ | Создать `ROUTES` registry (`$lib/routes.ts`) | ✅ Все 46 builder-функций |
| ✅ | Link-integrity тест | ✅ 698 тестов проходят |
| ✅ | Мигрировать 47 raw goto/href → ROUTES | ✅ 21 файл изменён |
| ✅ | Создать роут `/dashboards/{id}/validation/` | ✅ Было 404 → отдаёт данные |
| ✅ | Починить `/validation/{taskId}``/validation-tasks/{taskId}` | ✅ Было 404 → работает |
### P0 — Немедленно (2-3 часа)
| # | Задача | Файлы | Ожидаемый результат |
|---|--------|-------|---------------------|
| 0.1 | Выделить `DashboardHubModel.svelte.ts` | `dashboards/+page.svelte` | 2765 → <400 строк |
| 0.2 | Создать `EmptyState` компонент | `lib/ui/EmptyState.svelte` | Единый zero-state |
### P1 — Важно (5-6 часов)
| # | Задача | Файлы | Ожидаемый результат |
|---|--------|-------|---------------------|
| 1.1 | **Component Catalog** SSOT всех UI-компонентов с пропсами | `$lib/components.ts` | AI-агент видит все компоненты за 1 чтение |
| 1.2 | **API Data Contracts** `@DATA_CONTRACT` на все endpoint-методы | `api.ts` | AI-агент знает DTO без чтения бэкенда |
| 1.3 | Типизация `any` (33 вхождения) | 15+ файлов | `run: any` `Run`, `dashboard: any` `Dashboard` |
### P2 — Среднесрочно (4-5 часов)
| # | Задача | Файлы | Ожидаемый результат |
|---|--------|-------|---------------------|
| 2.1 | Заменить raw colors на semantic tokens | Все page-файлы | Ни одного `bg-indigo-*` в page |
| 2.2 | Заменить ручные `<button>` на `<Button>` | Все page-файлы | Единый стиль кнопок |
| 2.3 | Мигрировать 1-2 легаси стора на `.svelte.ts` | `taskDrawer.ts`, `sidebar.ts` | Runes-only stores |
| 2.4 | Выделить `DatasetHubModel` | `datasets/+page.svelte` | model-first для datasets |
| 2.5 | Выделить `ValidationTasksModel` | `validation-tasks/+page.svelte` | model-first для validation |
### P3 — Долгосрочно (6-8 часов)
| # | Задача | Файлы | Ожидаемый результат |
|---|--------|-------|---------------------|
| 3.1 | Консолидировать `src/components/` в `lib/components/` | 30+ файлов | Одна иерархия |
| 3.2 | Мигрировать оставшиеся 6 writable-сторов | 6 store-файлов | Runes-only |
| 3.3 | Семантические токены для всех UI-атомов | `Card`, `Input`, `Select`, `PageHeader` | Единые токены |
| 3.4 | Store Registry SSOT для всех сторов | `$lib/stores/index.ts` | 1 файл, 30 строк |
---
## 7. Статус исправлений (2026-06-02)
### Что сделано
| Изменение | Файлы | Тесты |
|-----------|-------|-------|
| Создан `$lib/routes.ts` ROUTES registry | 1 новый файл | 46 тестов на валидность каждого builder |
| Создан link-integrity тест | 1 новый файл | Сканирует все `src/` на raw goto/href |
| Создан роут `/dashboards/{id}/validation/` | 2 файла (+page.svelte, +page.ts) | Работает + пагинация + статус-бейджи |
| Мигрированы 47 raw goto/href ROUTES | 21 файл изменён | 0 raw строк осталось |
| Исправлен битый роут `/validation/{id}` | 1 файл (ScheduleAtAGlance) | Теперь ведёт на `/validation-tasks/{id}` |
| Исправлены 2 контрактных теста | 2 тестовых файла | Проверяют `ROUTES.*()` вместо raw строк |
### Метрики качества
| Метрика | До | После |
|---------|:--:|:-----:|
| Raw goto/href строк в коде | 47 | **0** |
| Битых роутов | 2 | **0** |
| Тестов на целостность навигации | 0 | **46** |
| Файлов, изменённых для миграции | | 21 |
| Всего тестов | 696 | **698** (все проходят) |
---
## 8. Следующие шаги для AI-First
На основе аудита и выполненной работы, следующие улучшения для AI-First (в порядке приоритета):
### 8.1 Component Catalog (`$lib/components.ts`)
**Почему:** AI-агент тратит ~5-10 чтений на поиск подходящего компонента. Component catalog сокращает это до 1.
**Формат:**
```typescript
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 агент галлюцинирует поля.
**Формат:**
```typescript
// @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: any` `Dashboard`
- `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. Приложение: Карта файлов
### 7.1 UI-атомы (`src/lib/ui/`)
| Файл | Строк | Использует токены | Типы |
|------|-------|-------------------|------|
| `Button.svelte` | 75 | `bg-primary`, `bg-destructive` | `lang="ts"` |
| `Card.svelte` | 58 | `border-gray-200 bg-white` | `lang="ts"` |
| `Input.svelte` | 66 | `border-gray-300` + `focus:ring-primary-ring` | `lang="ts"` |
| `Select.svelte` | 58 | `border-gray-300` | `lang="ts"` |
| `PageHeader.svelte` | 45 | `text-gray-900` | `lang="ts"` |
| `Icon.svelte` | 111 | `currentColor` | `lang="ts"` |
| `HelpTooltip.svelte` | | | |
| `LanguageSwitcher.svelte` | | | |
### 7.2 Модели (`src/lib/models/`)
| Файл | Строк | Семантика |
|------|-------|-----------|
| `MigrationModel.svelte.ts` | 472 | model-first gold standard |
| `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 | |
### 7.4 Страницы (`src/routes/`)
| Файл | Строк | Model? | $lib/ui? | Типы | UX контракты |
|------|-------|--------|----------|------|-------------|
| `dashboards/+page.svelte` | 2765 | | частично | ts | 5/5 |
| `dashboards/[id]/+page.svelte` | 582 | частично | | ts | 4/5 |
| `datasets/+page.svelte` | 472 | | | ts | 1/5 |
| `datasets/review/+page.svelte` | | | | | |
| `migration/+page.svelte` | 637 | | | ts | 5/5 |
| `settings/+page.svelte` | 290 | | | ts | 4/5 |
| `validation-tasks/+page.svelte` | 519 | | | JSDoc | 4/5 |
| `reports/+page.svelte` | 204 | | PageHeader | any | 3/5 |
| `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
```
---
*Аудит выполнен 2026-06-01. Полный контекст: ~120 файлов, 8 UI-атомов, 55+ компонентов, 9 моделей, 9 сторов, все 35+ страниц.*