---
title: CTO-19 closure — Lucide icon system migration через Vuetify custom IconSet
date: 2026-05-13
status: approved-by-customer
author: Claude (через superpowers:brainstorming)
related-rules: CLAUDE.md §2 «Иконки: Lucide» — бренд-spec; Реестр CTO-19 ⏸ (v1.79); CTO-19 closure → v1.83.
---

# Design: Lucide icon system migration (Vuetify custom IconSet)

## 1. Контекст и триггер

**Текущая проблема (CTO-19, реестр v1.79):**

- `app/package.json` НЕ содержит icon-related deps: ни `@mdi/font`, ни `lucide-vue-next`, ни любых других.
- `app/resources/js/plugins/vuetify.ts` НЕ содержит `icons` config — Vuetify использует default MDI icon set (string names `mdi-*`), но MDI font CSS не загружен → все `<i class="mdi-...">` рендерятся пустыми (нет glyph'ов).
- 51 файл (Vue/TS) использует 78 уникальных `mdi-*` имён в `icon`, `prepend-icon`, `append-icon`, `clearable`, etc. props.

**Бизнес-impact:** все кнопки/поля/индикаторы выглядят без glyph'ов: «+ Создать проект» без plus, search-input без magnify, notification bell пустой, dropdown chevron'ы невидимы.

**Решение:** Lucide migration per CLAUDE.md §2 (`Иконки: Lucide` — бренд-spec). Заказчик 13.05.2026 явно выбрал path α (aliases-only, views untouched) через AskUserQuestion brainstorming flow.

## 2. Архитектура

### 2.1. Стратегия — path α (aliases в vuetify.ts)

- **Views НЕ трогаются** — 51 файл с `icon="mdi-plus"` etc. остаются как есть; `mdi-*` строки становятся semantic-ID для Lucide компонентов.
- Один core change в `app/resources/js/plugins/vuetify.ts` — добавление `icons` config с custom `IconSet`.
- Lucide selective imports — tree-shakable bundle ~25-30 KB gzip для 78 icons.

### 2.2. vuetify.ts custom IconSet

```ts
import { h, type Component } from 'vue';
import {
  Plus, Search, Bell, BellOff, AlertCircle, AlertTriangle, Check, CheckCircle,
  X, XCircle, ChevronRight, ChevronDown, ArrowLeft, ArrowRight, ArrowUp, ArrowDown,
  User, UserPlus, Users, UserCheck, Archive, RefreshCw, Settings, MessageCircle,
  Camera, CreditCard, BarChart3, Clock, ClockCheck, FileText, Inbox, Mail,
  ArrowUpDown, Cog, Database, Download, Edit, Eye, EyeOff, Filter, Flag, Folder,
  Globe, Home, Info, Key, LayoutDashboard, Layers, Link, List, Lock, LogIn, LogOut,
  Megaphone, Menu, MoreHorizontal, MoreVertical, Pencil, Phone, Pin, Play, Plus as PlusIcon,
  Power, Printer, ReceiptText, Repeat, Save, Send, Server, Shield, ShoppingCart,
  Star, Tag, Trash, Trash2, TrendingUp, TrendingDown, Upload, Wallet, Zap,
  HelpCircle, // fallback for unmapped
  // (полный список — финализируется на implementation phase после grep всех 78 имён)
} from 'lucide-vue-next';
import type { IconSet, IconProps } from 'vuetify';

const lucideMap: Record<string, Component> = {
  'mdi-plus': Plus,
  'mdi-magnify': Search,
  'mdi-bell-outline': Bell,
  'mdi-bell-off-outline': BellOff,
  'mdi-alert-circle': AlertCircle,
  'mdi-alert-circle-outline': AlertCircle,
  'mdi-alert-outline': AlertTriangle,
  'mdi-check': Check,
  'mdi-check-circle': CheckCircle,
  'mdi-check-circle-outline': CheckCircle,
  'mdi-close': X,
  'mdi-close-circle': XCircle,
  'mdi-chevron-right': ChevronRight,
  'mdi-chevron-down': ChevronDown,
  'mdi-arrow-left': ArrowLeft,
  'mdi-arrow-right': ArrowRight,
  'mdi-arrow-up': ArrowUp,
  'mdi-arrow-down': ArrowDown,
  'mdi-account-outline': User,
  'mdi-account-plus-outline': UserPlus,
  'mdi-account-group-outline': Users,
  'mdi-account-switch': UserCheck,
  'mdi-account-arrow-right-outline': UserCheck,
  // ... ВСЕ 78 entries (финальный список генерируется в plan-phase)
};

const liderraLucideSet: IconSet = {
  component: (props: IconProps) => {
    const Icon = lucideMap[String(props.icon)] || HelpCircle;
    return h(Icon, { size: 20, strokeWidth: 1.75 });
  },
};

export const vuetify = createVuetify({
  theme: { defaultTheme: 'liderraForest', themes: { liderraForest } },
  icons: {
    defaultSet: 'liderra',
    sets: { liderra: liderraLucideSet },
  },
  defaults: { /* existing — не трогать */ },
});
```

### 2.3. Семантическое маппинг — guidelines

Для 78 имён map'ить по semantic similarity:

- Прямое соответствие имени (mdi-X → X Lucide): `mdi-plus → Plus`, `mdi-check → Check`, `mdi-bell-outline → Bell`.
- `-outline` суффикс → тот же Lucide (Lucide — все stroke-based, outline по умолчанию).
- Compound (`mdi-account-plus-outline`) → `UserPlus` (composite Lucide component).
- Action-specific (`mdi-cash-plus`) → semantic-closest: `mdi-cash-plus → CirclePlus` или `BadgePlus`. Принцип: понятность пользователю > pixel-точность.
- Отсутствует Lucide-эквивалент → fallback `HelpCircle` в IconSet component fn.

Полный final mapping table 78 строк — produces в implementation plan (отдельный artifact).

### 2.4. Что меняется

| Файл | Изменение |
|---|---|
| `app/package.json` | +1 dep `lucide-vue-next` (`^0.460.0`+ — последняя на 13.05.2026) |
| `app/package-lock.json` | автогенерация `npm install` |
| `app/resources/js/plugins/vuetify.ts` | +icons config, +78-entry lucideMap, +liderraLucideSet (~80 строк добавлений) |
| Реестр CTO-19 ⏸ → ✅ | при явном «закрываем» от заказчика на финальном этапе (Pravila §2.2 / §7.1) |
| CLAUDE.md §0 реестр row | bump к v1.83 (registry version после CTO-19 closure) через `/claude-md-management:claude-md-improver` |
| Memory `reference_archive.md` + `MEMORY.md` index | sync v1.82 → v1.83 |

### 2.5. Что НЕ меняется

- 51 Vue/TS файл с `icon="mdi-*"` — НЕ touched. Все 78 `mdi-*` строки остаются.
- ProjectsView.vue clearable workaround (`::after content '✕'`) — можно убрать после Lucide migration (clearable теперь рендерит реальный X icon). **Решение откладывается** — отдельный hygiene-task post-CTO-19.
- Другие нормативные документы (Pravila / PSR_v1 / Tooling) — не меняются. R15 motion settings, paired-stack rules — invariant.
- Vuetify theme / defaults — invariant.

## 3. Bundle impact

**До (CTO-19 broken state):**

- 0 KB icons in bundle (всё `<i class="mdi-...">` без glyph'ов)
- Без font CSS → no visual icons → broken UX

**После Lucide migration (path α):**

- `lucide-vue-next` selective imports 78 components — ~25-30 KB gzip (Vite tree-shaking)
- Vuetify icons API без изменений в views → no migration cost on consumer side
- SVG inline в DOM (Lucide rendering) → optimal a11y (`<svg role="img">`)

**Альтернатива path (i) — npm i @mdi/font:**

- +250 KB CSS bundle (`materialdesignicons.css`) — full MDI font (>7000 icons из которых используется 78)
- Против бренда (CLAUDE.md §2 «Иконки: Lucide»)
- **REJECTED** заказчиком (13.05.2026 ii > i)

## 4. Testing strategy

### 4.1. Regression baseline

- Vitest **88 files / 683 passed / 3 skipped** (фактическое baseline на 13.05.2026 day) — должно остаться без regression. Иконочный refactor — pure plugin-level change, не затрагивает component logic / state / events.
- Pest --parallel **742 / 739 passed / 3 skipped** — backend нетронут, baseline неизменна.
- Vite build — exit 0; bundle increase ~25-30 KB acceptable.

### 4.2. Visual smoke (manual через Playwright MCP)

5 ключевых views:

| URL | Что проверить (visual) |
|---|---|
| `/dashboard` | KPI-cards icons, action buttons, notification bell |
| `/projects` | search-input magnify, clearable, action menu icons, status pills |
| `/settings` | tab icons, save buttons, security cards icons |
| `/admin/billing` | navigation drawer icons, table icons, dropdown chevrons |
| `/no-such-404` | error illustration if uses icons |

Acceptance: 0 пустых icon mounts, все glyph'ы видны и semantically correct.

### 4.3. a11y (axe-core CDN inject)

После migration на `/dashboard` + `/admin/billing` — re-run axe-core scan. Acceptance: 0 нових violations (Lucide SVG inline должно `role="img"` или `aria-hidden` — проверить Vuetify wrapper).

### 4.4. Acceptance criteria

- ✅ `npm i lucide-vue-next` — пакет в `package.json`
- ✅ vuetify.ts содержит icons config с 78-entry lucideMap
- ✅ Vitest baseline match (683/3 skipped, 0 failed)
- ✅ Vite build exit 0
- ✅ Visual smoke 5 views — все iconography видна
- ✅ 0 новых a11y violations на iconography-related rules
- ✅ Реестр CTO-19 ⏸ → ✅ closed (при явном «закрываем»)
- ✅ Pre-commit hooks pass (gitleaks + markdownlint + cspell + pint + larastan + pest)
- ✅ Pre-push hooks pass (gitleaks-full-history + lychee)

## 5. Risks + mitigation

| Риск | Severity | Mitigation |
|---|---|---|
| Некоторые MDI имена не имеют clean Lucide-эквивалента (например `mdi-cash-plus`, `mdi-clock-check-outline`) | P2 | Best-fit semantic mapping (`CirclePlus` / `Clock`) + fallback `HelpCircle` в IconSet component fn. Полный mapping table — в implementation plan. |
| StrokeWidth / size mismatch — Lucide default 24px stroke=2; Vuetify default icon size 24px | P3 | strokeWidth=1.75, size=20 — близко к Vuetify default. Tweakable если visual smoke покажет несовпадение |
| Vuetify `prepend-icon` / `append-icon` props ждут string «mdi-...» — наш IconSet может не сработать через alias-path | P2 | Custom IconSet регистрируется на `defaultSet: 'liderra'` — Vuetify routs ВСЕ icon props (включая prepend/append/clearable) через `lucideMap`. Verify в visual smoke |
| Bundle size +25-30 KB | P3 | Acceptable (заменяет potential 250 KB MDI font). Tree-shaking подтверждается через `npm run build:analyze` (rollup-plugin-visualizer уже установлен в Sprint 4). |
| Vitest stubs `<v-icon>` — поведение IconSet не тестируется unit-tests | P3 | Visual smoke через Playwright MCP — purpose-built для cross-cutting render verification |
| Reset / rollback | — | git revert последнего коммита; vuetify.ts вернётся в pre-Lucide state. `npm uninstall lucide-vue-next` cleanup. Без code-changes в views — rollback дешёвый |

## 6. Closure plan

### 6.1. CTO-19 ✅ closure

Pravila §2.2 / §7.1 — закрытие в реестре требует явного «закрываем» от заказчика. Implementation вывод:

- После visual smoke ОК + tests green → задать вопрос «CTO-19 закрываем как ✅?» через AskUserQuestion.
- При «да/закрываем» → реестр §3 CTO-19 ⏸ → ✅ + Сводка §0 counter update (17 ✅ → 18 ✅, 1 ⏸ → 0 ⏸).

### 6.2. Registry bump v1.82 → v1.83

- Header version bump + новый changelog entry «v1.83 — CTO-19 ✅ closed via Lucide migration».
- Footer line update.
- Сводка §0 row CTO: «18 / 17 ✅ / 0 / 1 ⏸ / 0 / 0 / 1 P2 [?]» → «18 / 18 ✅ / 0 / 0 / 0 / 0 / 0».
- Итого продуктовых: 87 / 70 ✅ / 5 🟦 / 12 ⏸ → 87 / 71 ✅ / 5 🟦 / 11 ⏸.

### 6.3. CLAUDE.md + memory sync

- CLAUDE.md §0 row «Открытые вопросы» bump v1.82 → v1.83 через `/claude-md-management:claude-md-improver` (per CLAUDE.md §5 п.10) или ручной Edit с обоснованием «registry version sync».
- Memory `reference_archive.md` description + `MEMORY.md` index line 7 — sync.

### 6.4. Commit strategy

**Single atomic commit** — `feat(icons): Lucide migration (CTO-19) via custom Vuetify IconSet`. Содержит:

- `app/package.json` + `app/package-lock.json`
- `app/resources/js/plugins/vuetify.ts`
- `docs/Открытые_вопросы_v8_3.md` (registry CTO-19 ✅ + v1.83 + Сводка)
- `CLAUDE.md` (§0 registry row + version line bump)

Memory updates — git-untracked, отдельный шаг.

## 7. Что НЕ делается в этом дизайне

- **НЕ удаляется** ProjectsView.vue clearable workaround (`::after content '✕'`). Отдельный hygiene-task post-CTO-19 — может быть выполнен опционально.
- **НЕ мигрирует** mdi-*строки на lucide-* (path β explicitly rejected заказчиком).
- **НЕ устанавливаются** дополнительные icon packages (`@mdi/font`, `heroicons`, `tabler-icons` etc.).
- **НЕ меняется** Vuetify theme / defaults / palette / typography.
- **НЕ изменяется** narrative ТЗ v8.5 / schema v8.20 / Pravila / PSR_v1 / Tooling.
- **НЕ trigger** новые open Q-items (CTO-19 — единственный affected; новых not opened).

## 8. Открытые вопросы по дизайну

Нет — design self-contained. Mapping table 78 entries — генерируется automatically на implementation phase через iterate over `grep -roh "mdi-[a-z][a-z0-9-]*" app/resources/js | sort -u`. Best-fit decisions — Claude's call по semantic similarity с opportunity для пользовательской correction post-implementation.

---

**Approved**: 13.05.2026 через AskUserQuestion brainstorming flow (path ii Lucide migration → scope α aliases-only).

**Next:** invoke `superpowers:writing-plans` для detailed implementation plan.
