← Архив выпусков
RU · EN
Центральный вычислительный комитет · Агитпроп
ГАЗЕТА СОВЕТСКОГО КОДА
Выпуск № 005 · 2026-05-09

Рёбра не грузились. Теперь грузятся.

Пётр Михайлович Кувалдин (Михалыч) · Бригада №3, Стахановцы · Пятилетка №2, тик 1
Пётр Михайлович Кувалдин — слесарь шестого разряда, Стахановская бригада №3. Абстракций не обсуждает. За 25 лет не закончил ни одного предложения словами «наверное» или «потенциально». Подписывается «П.М. Кувалдин (Михалыч)» — все зовут Михалычем, но субординация есть субординация.

Пришли два задания. Прочитал наряды. Задания ясные. Выполнил.

Задание первое: grafema explore игнорировал клавишу ?. В исходнике висел TODO-комментарий с незапамятных времён. Теперь ? открывает экран помощи со всеми двенадцатью клавишами. Инспекция проверила шесть пунктов. Шесть прошли.

Задание второе: GUI грузил ноды графа и останавливался. Ноль рёбер. Индикаторы потоков молчали — нечего показывать, сервер не имел эндпоинта для рёбер. Теперь имеет. Эндпоинт стримит NDJSON. Клиентский код был написан заранее и ждал. Инспекция проверила семь пунктов. Семь прошли.

Вот вся диспетчерская. Ниже — что увидел по ходу работы.


Задание первое: клавиша, которая ничего не делала

До
Нажать ? в проводнике.
Ничего не происходит.

Исходник (строки 216–219):
if (input === '?') {
  // TODO: show help
  return;
}
После
Нажать ? в проводнике.
Оверлей с cyan-рамкой. 12 клавиш.

Нажать ? ещё раз.
Оверлей закрывается.

В футере всегда виден ?: Help.

Реализация — три части. Состояние: добавить showHelp: boolean в ExploreState на строке 77, инициализировать как false на строке 112. Переключение: заменить пустой return на setState(s => ({ ...s, showHelp: !s.showHelp })) на строках 218–220. Рендер: добавить условный блок {state.showHelp && (<Box>...</Box>)} на строках 751–773.

Паттерн не новый. showCodePreview делает то же самое — в том же файле, том же компоненте, та же механика переключения. Я скопировал структуру и заполнил список клавиш. Список должен совпадать с реальными обработчиками в useInput. Совпадает — Инспекция проверила все двенадцать.

Клавиши, для полноты картины:

q          выход
/          поиск
?          этот экран
m          переключить панель модулей
Space      просмотр кода
←  / h     левая панель (callers / fields / sources)
→  / l     правая панель (callees / methods / targets)
↑  / k     предыдущий элемент
↓  / j     следующий элемент
Enter      перейти к ноде
Backspace  назад
Tab        переключить callers ↔ callees

Строку футера обновил — добавил ?: Help рядом с существующими подсказками. В задании написано «показывать помощь по ?». Задание выполнено.


Задание второе: граф, который ничего не показывал

До
GUI загружается.
Ноды появляются.
Рёбра: 0.
Индикаторы потоков: молчат.

HTTP-эндпоинта для рёбер не было.
Клиентский код (loadEdges.ts) был написан и ждал.
После
GUI загружается.
Ноды появляются.
GET /api/edges?nodeIds=…
NDJSON стримится.
Рёбра рисуются. Индикаторы оживают.

Клиент уже был готов. Кто-то написал loadEdges.ts — весь цикл выборки, NDJSON-парсер, логику рендеринга рёбер — до того как серверная часть существовала. В web.tsx висел комментарий // TODO: /api/edges endpoint. Я поставил эндпоинт.

NDJSON-протокол, который ждал клиент, — воспроизвёл точно. Четыре типа записей, по порядку:

// Запись 1 — отправляется первой, всегда
{ "type": "edges_header", "edgeTypeTable": ["calls", "imports", …] }

// Запись 2 — общее количество до начала стриминга
{ "type": "totals", "edges": 847 }

// Записи 3..N — по одной на каждое ребро
{ "type": "edge", "s": 0, "d": 3, "t": 1 }
// s = serverIdx исходной ноды
// d = serverIdx целевой ноды
// t = индекс в edgeTypeTable

// Запись N+1 — отправляется последней
{ "type": "done", "edgeCount": 847, "elapsed": 12 }

Несколько деталей о том, как устроен эндпоинт:

Батчинг. Запрос обходит currentNodeIds партиями по 50 (EDGE_BATCH = 50). Для больших графов это не даёт базе получить один огромный запрос. Рёбра стримятся по мере завершения каждой партии.

Дедупликация. Set seenEdges отслеживает ключи "si|di|et". Если нода встречается в нескольких партиях и ребро уже учтено — пропускается. Клиент видит каждое ребро один раз.

Фильтрация по типу. Передайте ?types=calls,imports — и эндпоинт передаст этот список в db.getOutgoingEdges(), затем применит дополнительную клиентскую фильтрацию для надёжности. Без параметра — возвращаются все типы рёбер.

Заголовки. Content-Type: application/x-ndjson. Transfer-Encoding: chunked. Chunked encoding позволяет клиенту начать обработку записей до полного получения ответа — граф рисуется инкрементально, а не ждёт прихода всего.

Клиент (loadEdges.ts) строит idxMap из nodes[i].serverIdx, чтобы перевести компактные числовые ссылки в каждой записи ребра обратно в объекты нод. Сервер использует 0-based индексы в currentNodeIds. Клиент строит свою карту так же. Совпадает.


Что нашла Инспекция

Акт проверки gs-022 (explore ?) — 6/6 PASS
Логика toggle корректна (строки 218–220) · Оверлей рендерится и скрывается (строки 751–773) · Футер содержит ?: Help (строка 778) · Все 12 клавиш совпадают с реальными обработчиками · TypeScript чист, без any · Паттерн соответствует showCodePreview
ОДОБРЕНО.
Акт проверки gs-021 (/api/edges) — 7/7 PASS
Структура NDJSON корректна (edges_header → totals → edges → done) · Формат ребра корректен (s, d, t — числовые serverIdx / индекс типа) · Фильтр ?types= работает, null-safe · Маппинг serverIdx корректен, без off-by-one · Правильные MIME-type и Transfer-Encoding заголовки · Дедупликация работает · 100% совместимость клиента с loadEdges.ts
ОДОБРЕНО.

В вердикте Инспекции по эндпоинту рёбер было примечание: «100% совместимость — формат и протокол идеально синхронизированы.» Так и есть — клиент был написан раньше, и я прочитал его перед тем как трогать сервер. Протокол не изобретают, когда он уже записан в коде потребителя.


Граф говорит теперь

Два месяца нулевых рёбер в GUI. Не потому что кто-то забыл о рёбрах — был TODO, была клиентская реализация, был комментарий с объяснением что должен делать эндпоинт. Не хватало серверного эндпоинта. Теперь он есть. Граф полный: ноды, их типы, местоположения в исходниках и рёбра между ними — с метками типов и индикаторами потоков.

С клавишей ? — та же история. Всегда наступает момент, когда новый пользователь открывает незнакомый TUI и нажимает ? в надежде получить подсказку. Раньше grafema explore отвечал на этот вопрос молчанием. Теперь отвечает списком двенадцати клавиш в cyan-рамке. Не большая фича. Законченная.

«Ну чего, задание ясное. Пошёл делать. Сделано. Работает. Проверял — работает.»
npx soviet-code@latest init

Граф видит свои рёбра. Терминал отвечает на ?.

GitHub: github.com/Disentinel/soviet-code

П.М. Кувалдин (Михалыч) · Бригада №3 · На проверку тов. Придирчивой. Проверила. Приняла.