# Interactive Connections Graph — Design

**Дата:** 2026-05-10 (ночь)
**Статус:** Approved by user · ready for plan
**Автор:** Claude (Opus 4.7) под mode «экономия 0%»
**Связано:** [`docs/visualizations/hooks-skills-plugins-map.html`](../../visualizations/hooks-skills-plugins-map.html) (2240 строк, расширение)

---

## 1. Цель

Добавить в существующую HTML-визуализацию `hooks-skills-plugins-map.html` интерактивную force-directed network диаграмму, показывающую **связи** между всеми сущностями системы (плагины, скилы, хук-скрипты, hook events, state-файл, permissions, Pravila §12, CLAUDE.md).

**Зачем:** иерархия (контейнерная/приоритетная/временная) уже есть в §II после прошлой итерации. Но **связи** между блоками — кто что вызывает, кто что пишет/читает, кто что блокирует — ещё не визуализированы. Пользователь сказал «запутался» — нужна графическая карта зависимостей с возможностью «потрогать».

## 2. Scope

### 2.1. В scope

- Новая секция в HTML: §X «Связи — interactive map»
- Force-directed network через D3.js v7 (CDN)
- ~50 узлов (полный inventory) с фильтрами по категориям
- Интерактив: drag, click-to-highlight, hover-tooltip, category filters, reset button
- Responsive до mobile (graph horizontally scrolls, sidebar → bottom-sheet)
- Сохранение текущей vintage-blueprint aesthetic

### 2.2. Не в scope (YAGNI)

- Поиск по имени узла
- Экспорт PNG / share URL
- Редактирование графа (добавление/удаление узлов через UI)
- Анимация ребёр / pulse-effects
- Zoom/pan (D3 поддерживает, но не нужен для 50 узлов)
- Сохранение пользовательского layout'а (refresh = reset)

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

### 3.1. Технология

- **D3.js v7** через CDN `https://cdn.jsdelivr.net/npm/d3@7`. Force layout API из стандартного D3.
- **Pure SVG** для рендеринга узлов и рёбер (не Canvas — для accessibility и hover-detection).
- **Vanilla JS** для interactivity, без фреймворков.
- **Inline в существующий HTML** — никаких отдельных файлов.

### 3.2. Структура данных

```javascript
const nodes = [
  // Plugins (4)
  { id: 'plg:superpowers', type: 'plugin', label: 'superpowers', size: 22 },
  { id: 'plg:claude-md',   type: 'plugin', label: 'claude-md-management', size: 22 },
  { id: 'plg:fd',          type: 'plugin', label: 'frontend-design', size: 22 },
  { id: 'plg:upm',         type: 'plugin', label: 'ui-ux-pro-max', size: 22 },

  // Skills (28: 14 superpowers + 2 claude-md + 2 ui + 6 config + 2 auto + 2 review)
  { id: 'skl:brainstorming', type: 'skill', label: 'brainstorming', size: 10 },
  // ... 27 more

  // Hook scripts (7)
  { id: 'scr:skill-marker',      type: 'script', label: 'skill-marker.py', size: 14 },
  { id: 'scr:skill-check',       type: 'script', label: 'skill-check.py', size: 14 },
  { id: 'scr:economy-mode',      type: 'script', label: 'economy-mode.py', size: 14 },
  { id: 'scr:economy-self-check',type: 'script', label: 'economy-self-check.py', size: 14 },
  { id: 'scr:economy-state-guard',type:'script', label: 'economy-state-guard.py', size: 14 },
  { id: 'scr:economy-verifier',  type: 'script', label: 'economy-verifier.py (Sonnet 4.6)', size: 14 },
  { id: 'scr:economy-postcompact',type:'script', label: 'economy-postcompact.py', size: 14 },

  // Hook events (5)
  { id: 'evt:session-start',     type: 'event', label: 'SessionStart' },
  { id: 'evt:user-prompt-submit',type: 'event', label: 'UserPromptSubmit' },
  { id: 'evt:pre-tool-use',      type: 'event', label: 'PreToolUse' },
  { id: 'evt:post-compact',      type: 'event', label: 'PostCompact' },
  { id: 'evt:stop',              type: 'event', label: 'Stop' },

  // State file (1)
  { id: 'st:economy-state', type: 'state', label: '$TEMP/claude-economy-<sid>.json' },

  // Permissions (3)
  { id: 'prm:allow', type: 'perm', label: 'permissions.allow (1)' },
  { id: 'prm:deny',  type: 'perm', label: 'permissions.deny (7)' },
  { id: 'prm:ask',   type: 'perm', label: 'permissions.ask (16)' },

  // Rules (2)
  { id: 'rul:pravila-12', type: 'rule', label: 'Pravila §12 (hard rule)' },
  { id: 'rul:claude-md',  type: 'rule', label: 'CLAUDE.md' },
];

const links = [
  // contains (4 plugin → multiple skills each)
  { source: 'plg:superpowers', target: 'skl:brainstorming', type: 'contains' },
  // ... ~28 contains edges total

  // triggers (event → script, 7 edges)
  { source: 'evt:session-start',      target: 'scr:economy-self-check', type: 'triggers' },
  { source: 'evt:user-prompt-submit', target: 'scr:economy-mode', type: 'triggers' },
  { source: 'evt:pre-tool-use',       target: 'scr:skill-marker', type: 'triggers' },
  { source: 'evt:pre-tool-use',       target: 'scr:skill-check', type: 'triggers' },
  { source: 'evt:pre-tool-use',       target: 'scr:economy-state-guard', type: 'triggers' },
  { source: 'evt:post-compact',       target: 'scr:economy-postcompact', type: 'triggers' },
  { source: 'evt:stop',               target: 'scr:economy-verifier', type: 'triggers' },

  // writes (1)
  { source: 'scr:economy-mode', target: 'st:economy-state', type: 'writes' },

  // reads (3)
  { source: 'scr:economy-state-guard', target: 'st:economy-state', type: 'reads' },
  { source: 'scr:economy-verifier',    target: 'st:economy-state', type: 'reads' },
  { source: 'scr:economy-postcompact', target: 'st:economy-state', type: 'reads' },

  // mandates (1 → 14 superpowers skills)
  { source: 'rul:pravila-12', target: 'skl:brainstorming', type: 'mandates' },
  // ... 14 mandates edges

  // references (CLAUDE.md → Pravila §12, Tooling)
  { source: 'rul:claude-md', target: 'rul:pravila-12', type: 'references' },

  // blocks (permissions.ask → all hook scripts)
  { source: 'prm:ask', target: 'scr:skill-marker', type: 'blocks' },
  { source: 'prm:ask', target: 'scr:skill-check', type: 'blocks' },
  { source: 'prm:ask', target: 'scr:economy-mode', type: 'blocks' },
  // ... 7 blocks edges

  // denies (permissions.deny → state-file)
  { source: 'prm:deny', target: 'st:economy-state', type: 'denies' },
];
```

Итого: **50 узлов**, **52 ребра**.

Точный breakdown узлов: 4 plugins + 28 skills + 7 scripts + 5 events + 1 state + 3 perms + 2 rules = 50.

Точный breakdown рёбер:

- 18 contains (plugin → skill): 14 superpowers + 2 claude-md + 1 fd + 1 upm. **10 скилов standalone** (update-config, keybindings-help, simplify, fewer-permission-prompts, init, claude-api, loop, schedule, review, security-review) — НЕТ contains-edge, узел висит свободно (привязывается к Forces only)
- 7 triggers (event → script)
- 1 writes (economy-mode → state)
- 3 reads (state-guard / verifier / postcompact ← state)
- 14 mandates (Pravila §12 → 14 superpowers skills)
- 1 references (CLAUDE.md → Pravila §12)
- 7 blocks (permissions.ask → 7 hook scripts)
- 1 denies (permissions.deny → state-file)

### 3.3. Visual encoding

| Атрибут | Plugins | Skills | Scripts | Events | State | Perms | Rules |
|---|---|---|---|---|---|---|---|
| Shape | circle | circle | circle | rect | diamond | hexagon | star/rect |
| Fill | rust `#8B3A1B` | blueprint `#1A6E8E` | amber `#C48B26` | sage `#5E7A4F` | rust `#8B3A1B` | ink-fade | ink+rust frame |
| Size | r=22 | r=10 | r=14 | 16×16 | 18×18 | 20×20 | 20×20 |
| Border | ink 2px | ink 1.5px | ink 1.5px | ink 2px | ink 2px solid + rust dashed | ink 1.5px | rust 2px |

Подпись узла — fraunces 10px below the node, с paper background (rect bg) для читаемости поверх линий.

**Edges by type:**

| Type | Color | Width | Style | Arrowhead |
|---|---|---|---|---|
| contains | `--blueprint` | 1.5px | solid | нет |
| triggers | `--sage` | 2px | solid | →arrow |
| writes | `--amber` | 2px | solid | →filled |
| reads | `--amber` | 1.5px | dashed | ←open |
| mandates | `--rust` | 2.5px | solid | →double |
| references | `--ink-fade` | 1px | dotted | →arrow |
| blocks | `--rust` | 1.5px | dashed | T-end |
| denies | `--rust` | 2px | dashed | X-end |

### 3.4. Initial positioning (clustering)

D3 force simulation с custom forces:

```javascript
const sim = d3.forceSimulation(nodes)
  .force('link', d3.forceLink(links).id(d=>d.id).distance(d => linkDistance(d.type)))
  .force('charge', d3.forceManyBody().strength(-300))
  .force('center', d3.forceCenter(W/2, H/2))
  .force('y-cluster', d3.forceY(d => yByType(d.type)).strength(0.15))
  .force('collide', d3.forceCollide().radius(d => d.size + 8));
```

`yByType` стратификация:

- Rules (top): y=80
- Plugins: y=160
- Skills: y=320
- Events: y=480
- Scripts: y=540
- State + Permissions: y=420 (middle, как hubs)

### 3.5. Interactivity

```
Drag node     → fx/fy фиксируется во время drag, освобождается после
Click node    → highlight all edges where source/target == clicked
              → grey out non-related (opacity 0.15)
              → show sidebar with: name, type, list of connections
Click empty   → reset highlight
Hover edge    → SVG tooltip с типом связи + source/target labels
Filter chip   → toggles display:none for all nodes of that category +
                их incident edges
Reset btn     → recalc simulation, clear filters, dismiss sidebar
```

### 3.6. Sidebar (right, hidden by default)

При клике на узел:

- Имя узла (заголовок)
- Тип (badge)
- Описание (1-2 предложения из inventory)
- Секция «Связи»:
  - **Outgoing:** список «`→ {edge-type}` → {target}»
  - **Incoming:** список «{source} `← {edge-type}` ←»

Mobile: sidebar → bottom-sheet через `position: fixed; bottom: 0`.

### 3.7. Category filters

В шапке секции — 7 chip-buttons. Click toggles категорию:

```
[Plugins ●] [Skills ●] [Scripts ●] [Events ●] [State ●] [Perms ●] [Rules ●]
```

Каждая кнопка — toggle on/off (active state visible через filled background).

## 4. Detailed contracts

### 4.1. HTML структура

```html
<section class="section">
  <div class="frame">
    <div class="section-num display-i">X</div>
    <h2 class="section-title">Связи &mdash; interactive map</h2>
    <p class="section-lede">...</p>

    <div class="graph-controls">
      <div class="graph-filters">
        <button class="graph-filter active" data-type="plugin">Plugins</button>
        ... 6 more
      </div>
      <button class="graph-reset">Reset layout</button>
    </div>

    <div class="graph-container">
      <svg class="graph-svg" viewBox="0 0 1100 700">
        <!-- D3 fills this -->
      </svg>
      <aside class="graph-sidebar" hidden>
        <!-- D3 populates on click -->
      </aside>
    </div>

    <div class="graph-legend">
      <!-- Static legend: shapes + colors + edge types -->
    </div>
  </div>
</section>
```

### 4.2. JS module

Один `<script type="module">` блок в конце `<body>` (после всего HTML).

```javascript
// Inline data
const nodes = [/* ... */];
const links = [/* ... */];

// Initialize D3 force simulation
const sim = d3.forceSimulation(nodes) /* ... */ ;

// Render
const svg = d3.select('.graph-svg');
const linkSel = svg.append('g').selectAll('line').data(links).join('line') /* ... */;
const nodeSel = svg.append('g').selectAll('g.node').data(nodes).join('g').classed('node', true) /* ... */;

// Interactivity handlers
nodeSel.on('click', onNodeClick);
linkSel.on('mouseover', onEdgeHover);
// filters, reset btn
```

### 4.3. CSS additions

```css
.graph-controls { ... display: flex; justify-content: space-between; }
.graph-filter { ... border: 1px solid var(--rule); padding: 6px 14px; cursor: pointer; }
.graph-filter.active { background: var(--ink); color: var(--paper); }
.graph-container { display: grid; grid-template-columns: 1fr 280px; gap: 24px; }
.graph-sidebar { ... }
.graph-legend { ... }
.node { cursor: pointer; }
.node text { font-family: 'Fraunces', serif; font-size: 9px; ... }
.link.triggers { stroke: var(--sage); stroke-width: 2; }
.link.writes { stroke: var(--amber); stroke-width: 2; marker-end: url(#arrow-amber); }
...
.dimmed { opacity: 0.15; }
.highlighted { opacity: 1; }

@media (max-width: 900px) {
  .graph-container { grid-template-columns: 1fr; }
  .graph-sidebar { position: fixed; bottom: 0; ... }
}
```

## 5. Testing strategy

### 5.1. Manual verification

- [ ] Все 50 узлов рендерятся
- [ ] Все 52 ребра видны
- [ ] Drag любого узла работает
- [ ] Click на plugin: подсвечены все его skills + связи
- [ ] Click на state-file: подсвечены 1 writer + 3 readers + 1 denier (5 связей)
- [ ] Click на Pravila §12: подсвечены 14 superpowers skills
- [ ] Filter «Plugins» toggled off → исчезают 4 plugin узла + их contains edges
- [ ] Reset clears filters, recenters layout, dismisses sidebar
- [ ] Mobile (<900px): graph остаётся видимым, sidebar бекомес bottom-sheet
- [ ] Без интернета: D3 не загружается, но HTML page продолжает работать (graph section показывает fallback message)

### 5.2. Browser compatibility

- Chrome 120+, Firefox 120+, Edge 120+, Safari 17+ (D3 v7 поддерживает)
- IE / старый Safari — не поддерживаются (out of scope)

### 5.3. Performance

50 узлов + 52 ребра — well within D3's force layout capability. Should reach stable layout within 2-3 seconds. Idle CPU после стабилизации.

## 6. Risks

| Риск | Митигация |
|---|---|
| D3 CDN недоступен (offline) | Graceful fallback: проверка `typeof d3 !== 'undefined'`, иначе показать message «Connections graph requires internet (D3 v7 CDN). Other sections work offline.» |
| Слишком плотный граф | Filters + drag + центральная гравитация рассеивают plotpoints |
| Конфликт CSS-имён с существующими (`.graph-*` уникальные prefix) | Prefix `graph-` для всех новых классов |
| Изменения в data — где править | Все nodes/links inline в `<script>` блоке, легко находится по `// Inline data` маркеру |
| Mobile UX плохой при ~50 узлах | Уменьшаем размер узлов на mobile, sidebar в bottom-sheet, scrollable graph horizontally |

## 7. Success criteria

После имплементации:

- [ ] Открыть HTML в браузере → виден новый раздел §X «Связи»
- [ ] Граф ренджерится в течение 3 секунд
- [ ] Все 50 узлов с подписями
- [ ] Все 52 ребра с правильными цветами по типу
- [ ] Click на любой узел → видны все его связи, sidebar показывает details
- [ ] Filter toggle работает (показывает/скрывает категорию)
- [ ] Reset кнопка возвращает initial state
- [ ] No console errors
- [ ] Mobile (<900px): функциональный, не сломан
- [ ] Без D3 CDN: graceful fallback message виден
- [ ] Файл валиден HTML (0 unclosed tags, 0 orphan closes)

## 8. Scope каскадирующих изменений

- Существующие секции I-IX — **без изменений**
- Текущая §X (Practical Actions) переименовывается в §XI
- 1 renumber в section-num + текстовые правки в footer если упоминают «9 sections» (не упоминают)
- Файл вырастает с 2240 строк → ~2500 (~260 добавки)

## 9. Open questions

Нет. Все ключевые решения утверждены в brainstorming диалоге выше.

## 10. Next steps

После approve'а этого spec'а:

1. `superpowers:writing-plans` — пошаговый план имплементации
2. Execution choice: inline или subagent-driven
3. Стадии:
   - a. Renumber текущей §X → §XI
   - b. Insert new §X section structure (HTML + CSS skeleton)
   - c. Inline data (nodes + links)
   - d. D3 simulation + rendering
   - e. Interactivity: click, drag, hover
   - f. Filters + reset
   - g. Sidebar populate
   - h. Mobile responsive
   - i. CDN fallback
   - j. Manual verification против success criteria

---

**Status: Approved by user via brainstorming dialogue · ready for writing-plans.**
