Files
ss-tools/frontend/ai-native-design-audit.md
busya a95f070441 feat(frontend): add DashboardHub components, EmptyState, and validation scaffold
- DashboardHubModel.svelte.ts: Screen Model for dashboard listing hub
  with filters, pagination, and selection state.
- DashboardRow.svelte: Individual dashboard card with Git/health badges.
- ColumnFilterPopover.svelte: Column-based filter dropdown for dashboards.
- dashboard-helpers.ts: Shared dashboard utility functions.
- EmptyState.svelte: Reusable empty state component with icon and CTA.
- validation route scaffold for per-dashboard validation views.
2026-06-02 09:56:25 +03:00

38 KiB
Raw Blame History

Frontend Design Audit: ss-tools

Дата: 2026-06-01 Цель: Аудит фронтенд кода на предмет "плавающего" дизайна, выработка AI-native правил Объём: ~120 файлов, 8 UI-атомов, 55+ компонентов, 9 моделей, 9 сторов, все страницы, tailwind.config.js


Содержание

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

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);
// Используется через fromStore() в компонентах

// ПАТТЕРН 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-агент не может определить, какой паттерн использовать
  • writable сторы нельзя использовать с $derived напрямую
  • Разный DX для тестирования: Model тестируется без DOM (new Model()), Store — через get()
  • Рефакторинг любого стора требует контекстного переключения

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

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

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

<!-- ❌ ВСЁ В ОДНОМ ФАЙЛЕ: состояние, логика, рендер -->
<script lang="ts">
  let allDashboards = $state([]);
  let dashboards = $state([]);
  let filteredDashboards = $state([]);
  let isLoading = $state(true);
  let error = $state(null);
  let currentPage = $state(1);
  let pageSize = $state(10);
  let totalPages = $state(1);
  let total = $state(0);
  let selectedIds = $state(new Set());
  let isAllSelected = $state(false);
  let searchQuery = $state("");
  let filterDashboardIds = $state([]);
  let sortColumn = $state("title");
  let sortDirection = $state("asc");
  let openFilterColumn = $state(null);
  let columnFilterSearch = $state({...});
  let columnFilters = $state({...});
  let validationStatuses = $state({});
  let validationPopoverPosition = $state({ left: 0, top: 0 });
  let showMigrateModal = $state(false);
  let showBackupModal = $state(false);
  let gitBusyIds = $state(new Set());
  let gitResolvedIds = $state(new Set());
  let gitLoadingIds = $state(new Set());
  // ... ~30 атомов

  // Инлайн-логика: loadDashboards, handleSearch, handleSort,
  // applyGridTransforms, handleGitInit, handleGitSync,
  // handleGitCommit, handleGitPull, handleGitPush,
  // fetchValidationStatuses, toggleFilterDropdown,
  // toggleFilterValue, clearColumnFilter, getSortValue,
  // getColumnCellValue, getFilterOptions, normalizeOwners,
  // normalizeTaskStatus, normalizeValidationStatus,
  // getValidationBadgeClass, getValidationLabel,
  // getValidationLabelForCell, getPaginationRange...
  // ~50 функций, бизнес-логика и Git-операции — всё инлайн
</script>

Сравнение с model-first подходом (migration/+page.svelte, 637 строк):

<!-- ✅ ТОНКИЙ СЛОЙ РЕНДЕРИНГА -->
<script lang="ts">
  import { MigrationModel } from "$lib/models/MigrationModel.svelte.ts";
  const model = new MigrationModel();
  onMount(() => model.loadEnvironments());
  // Вся логика — в model.action()
  // Компонент только рендерит model.state и вызывает model.*()
</script>

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

  • dashboards/+page.svelte невозможно протестировать unit-тестами без DOM
  • AI-агент теряет контекст к 20-й функции
  • Нарушение INV_7 (>400 строк, >10 cyclomatic complexity)
  • Любые изменения рискуют сломать невидимые cross-widget инварианты

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

Описание: Семантические цвета из 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 🟡 Средние

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

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

Доказательство — все стили button в проекте:

<!-- СТИЛЬ 1: Button компонент (только migration page) -->
<Button variant="primary" onclick={handleRunMigration}>Run</Button>

<!-- СТИЛЬ 2: primary кнопка (dashboards — ручная) -->
<button class="inline-flex items-center justify-center rounded-lg bg-primary px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-primary-hover">

<!-- СТИЛЬ 3: indigo кнопка (dashboards — ручная, дрейф) -->
<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">

<!-- СТИЛЬ 4: toolbar кнопка (dashboards — ручная) -->
<button class="px-3 py-1 text-sm border border-gray-300 rounded hover:bg-gray-100 transition-colors">

<!-- СТИЛЬ 5: secondary кнопка (validation-tasks — ручная) -->
<button class="inline-flex items-center justify-center rounded-lg border border-slate-300 px-3 py-1.5 text-sm font-medium text-slate-700 transition-colors hover:bg-slate-50">

<!-- СТИЛЬ 6: text-only кнопка (settings — ручная) -->
<button class="text-blue-600 hover:text-blue-800 text-sm font-medium">

<!-- СТИЛЬ 7: danger кнопка (dashboards — ручная) -->
<button class="px-4 py-2 bg-destructive text-white rounded hover:bg-destructive-hover transition-colors">

7 разных стилей для одного и того же — кнопки. При использовании <Button> их было бы 2-3 (variant + size).


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

Описание: Компоненты разбросаны по трём корневым директориям без чётких правил, куда что класть.

Директория Количество Назначение (фактическое)
src/lib/ui/ 8 Атомарные UI-компоненты общего назначения
src/lib/components/ 30+ Доменные компоненты (layout, translate, reports, health, llm, assistant, dataset-review, ui)
src/components/ 30+ "Всё остальное" — без доменной группировки

Пример: Компоненты Git разбросаны:

  • src/components/git/GitManager.svelte
  • src/components/git/BranchSelector.svelte
  • src/components/git/CommitHistory.svelte

Почему они не в lib/components/? Исторически сложилось. Для AI-агента это мёртвый груз — нужно помнить про 3 директории.


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

Описание: Страницы, написанные после внедрения runes, используют JSDoc-аннотации вместо TypeScript.

Доказательство (validation-tasks/+page.svelte):

<script>
  /** @type {{ data: import('./$types').PageData }} */
  let { data } = $props();

  /** @type {Array<any>} */ let tasks = $state(data.tasks ?? []);
  /** @type {number} */ let total = $state(data.total ?? 0);
  /** @type {number} */ let currentPage = $state(data.page ?? 1);
  /** @type {number} */ let pageSize = $state(data.page_size ?? 20);
  /** @type {string | null} */ let error = $state(data.error ?? null);
  let isLoading = $state(false);
  let searchQuery = $state('');

  /** @type {Record<string, boolean>} */
  let runningTasks = $state({});
  let deleteConfirmId = $state(/** @type {string | null} */(null));
</script>

Проблема: <script lang="ts"> даёт compile-time проверку типов. JSDoc — только документацию. any — отсутствие типов. @type {Array<any>} — worst of both worlds.


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

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

// src/lib/stores/environmentContext.ts
import { derived, get, writable, type Writable } from 'svelte/store';
const contextStore: Writable<EnvironmentContextState> = writable(INITIAL_STATE);

// src/lib/stores/taskDrawer.ts
import { writable, type Writable } from 'svelte/store';
export const taskDrawerStore: Writable<TaskDrawerState> = writable(initialState);

Проблема: Для чтения этих сторов в Svelte 5 нужно использовать fromStore():

// +layout.svelte — обязательный bridge
const sidebarState = fromStore(sidebarStore);
let isExpanded = $derived(sidebarState.current?.isExpanded || true);

Это работает, но создаёт лишний слой абстракции и не позволяет использовать $state / $derived напрямую в store-файлах.


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

Компонент @UX_STATE @UX_FEEDBACK @UX_RECOVERY @UX_REACTIVITY
MigrationPage
DashboardHub
ValidationTasksPage
ReportsPage
DashboardDetail
DatasetHub шапка
SettingsPage
Sidebar
Card legacy
Button

@UX_REACTIVITY (явное объявление источника состояния) отсутствует везде, кроме MigrationPage.


3.3 🟢 Малые

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

// src/lib/utils.ts — line 10
export function cn(...inputs: (string | undefined | null | false)[]): string {

Пропускает true и числа. Не критично, но неаккуратно.


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

// ✅ $lib/utils/debounce.ts — TypeScript, с типами
export function debounce<T extends (...args: unknown[]) => void>(func: T, wait: number): (...args: Parameters<T>) => void {

// ❌ dashboards/+page.svelte — инлайн-дебаунс (line 127-131)
const debouncedSearch = debounce((query) => {
    searchQuery = query;
    currentPage = 1;
    void loadDashboards();
}, 300);

Два дебаунса с одинаковой сигнатурой, один — в утилите (правильно), другой — инлайн (дублирование).


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

  • dashboards/+page.svelte — премиум empty state с градиентом, rounded-3xl, tracking-[0.24em]
  • reports/+page.svelte — примитивный <div> с текстом
  • validation-tasks/+page.svelte — центрированная карточка

Нет общего компонента EmptyState.


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

Элемент Страница Radius
Card (компонент) $lib/ui/Card rounded-lg
Surrogate Card reports rounded-xl
Surrogate Card validation-tasks rounded-xl
Surrogate Card settings rounded-lg
Zero-state dashboards rounded-3xl
Toolbar контейнер dashboards rounded-xl
Toolbar контейнер reports rounded-xl

Нет иерархии радиусов, AI-агент выбирает случайно.


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

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

P0 — Немедленно (2-3 часа)

# Задача Файлы Ожидаемый результат
0.1 Выделить DashboardHubModel.svelte.ts dashboards/+page.sveltelib/models/DashboardHubModel.svelte.ts 2765 → <400 строк в page, Model тестируется без DOM
0.2 Создать EmptyState компонент lib/ui/EmptyState.svelte + usage Единый zero-state паттерн

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

# Задача Файлы Ожидаемый результат
1.1 Заменить raw colors на semantic tokens dashboards/+page.svelte, reports/+page.svelte, settings/** Ни одного bg-indigo-*, text-indigo-*, bg-red-* в page-файлах
1.2 Заменить ручные <button> на <Button> Все page-файлы Единый стиль кнопок
1.3 Мигрировать 1-2 легаси стора на .svelte.ts taskDrawer.ts, sidebar.ts Подтверждение паттерна

P2 — Среднесрочно (4-5 часов)

# Задача Файлы Ожидаемый результат
2.1 Выделить DatasetHubModel datasets/+page.svelte model-first для datasets
2.2 Выделить ValidationTasksModel validation-tasks/+page.svelte model-first для validation
2.3 Перевести validation-tasks/+page.svelte на TypeScript validation-tasks/+page.svelte <script lang="ts">
2.4 Добавить @UX_REACTIVITY на все страницы Все page Единый UX-контракт

P3 — Долгосрочно (6-8 часов)

# Задача Файлы Ожидаемый результат
3.1 Консолидировать src/components/ в lib/components/ 30+ файлов Одна иерархия компонентов
3.2 Мигрировать оставшиеся 6 writable-сторов на .svelte.ts 6 store-файлов Runes-only stores
3.3 Добавить всем атомам семантические токены Card.svelte, Input.svelte, Select.svelte, PageHeader.svelte Единые токены во всех атомах

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

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+ страниц.