Все записи
БлогВыпускПроизводительностьИнженерия

Пятьдесят карточек на одном холсте: куда уходил процессор, и карточка «Команда»

Простаивающий холст с тремя карточками агентов съедал целое ядро процессора. Виноват оказался мультяшный персонаж. Что мы намерили, что поменяли, и карточка, которая теперь есть на каждом холсте.

7 мин чтенияЧто вошло в 0.1.117

NeuroSquad 0.1.117 — выпуск, который замечаешь в основном по тому, что прекратилось: вентилятор, разгоняющийся, когда ничего не происходит, рывки при перетаскивании рамки, полной карточек, подвисание каждый раз, когда отдаляешь холст за порог, после которого карточки становятся плитками. А ещё в нём появилась карточка «Команда» — одна доска, где видно, чем занят каждый агент воркспейса.

Цель для холста не менялась с первых недель: до пятидесяти живых карточек — терминалов, браузеров, агентов — с плавными паном, зумом, перетаскиванием и изменением размера. Правило, как к ней идти, тоже не менялось: сначала замерить. Каждое число в этой записи получено на отдельном экземпляре приложения со свежим профилем, которым управляли настоящими событиями ввода через протокол DevTools.

Первый раунд: пятьдесят терминалов

Первый проход случился ещё до публичного выпуска — на воркспейсе из пятидесяти карточек, где двенадцать терминалов печатали с максимальной скоростью. Зум тормозил по причине, которую легко пропустить: шапка каждой карточки держит постоянный экранный размер, и делала она это через width и margin. Это свойства раскладки. Каждый кадр зума менял размер каждой шапки, та меняла размер тела карточки, а оно дёргало ResizeObserver каждого терминала — пятьдесят подгонок терминала, пятьдесят сообщений в главный процесс, пятьдесят ресайзов консоли. На каждый кадр.

Исправление: фиксировать значение зума один раз за жест, а не на каждом кадре, менять размер терминала, только когда реально меняется его сетка из колонок и строк, и на время движения зума подменять живые сетки терминалов статичной заглушкой. Затем терминалы перешли с DOM-рендерера xterm на WebGL-рендерер — и именно туда уходила большая часть времени: медиана кадра зума упала с 18,2 до 6,1 мс, p95 — со 133 до 12 мс.

Один замер того раунда определил правило, которое до сих пор живёт в коде. Заглушки вместо терминалов вдвое ускорили зум (медиана 6,1 против 12,0 мс) и замедлили пан (в среднем 19,8 против 16,3 мс): пан просто сдвигает уже нарисованные слои, и спрятать пятьдесят сеток, а потом вернуть их, стоит дороже, чем даёт. Поэтому заглушки появляются, только когда жест действительно меняет зум.

Второй раунд: холст в простое

Второй раунд начался с более странной жалобы: холст тяжёлый, даже когда ничего не происходит. Четыре параллельных ревью кода и одна сессия живого профилирования на воркспейсе «Launch day» нашли не одну причину, а кучу мелких постоянных трат. Самой крупной оказался мультяшный персонаж.

У карточек ИИ-агентов есть маленький маскот, который показывает состояние агента. Его анимации ожидания были бесконечными CSS-анимациями на дочерних элементах SVG — а их браузер перерисовывает в главном потоке, каждый кадр, вечно. С тремя карточками Claude Code на холсте при зуме 60% процесс рендерера занимал 98–104% ядра, а GPU — 32–36%, ничего при этом не делая. Теперь зациклено только состояние «работает», и двигается в нём HTML-обёртка, с которой композитор справляется сам; остальные состояния проигрываются несколько раз и замирают в позе, которая сама говорит, что происходит.

~100% → 15%
процессор рендерера, холст в простое с тремя карточками агентов
~34% → 3%
GPU на том же холсте
180 → 66
композитных слоёв после одного CSS-правила

Остальное — список вещей, каждая из которых стоила немного, но все работали постоянно:

  • Селекторы с * и :has() внутри холста. Переключение класса на панели холста каждый раз стоило 25–34 мс пересчёта стилей: правила вроде «каждый потомок жеста» приходилось заново сопоставлять с каждым узлом каждой карточки. Явный список элементов, которые действительно анимируются, свёл это примерно к 1,5 мс.
  • CSS-переменная на панели. Уровень зума жил в кастомном свойстве на элементе, который содержит все карточки, и его изменение инвалидировало стили всего поддерева, терминалы включительно, — 22–34 мс на запись. Теперь это одно правило в теге <style>, которое касается только элементов, читающих это значение.
  • Кнопки со своими слоями. Библиотека компонентов даёт кнопкам собственный GPU-слой. Внутри холста это означало десятки лишних слоёв; одно переопределение вне нажатого состояния снизило их число со 180 до 66.
  • Таймеры в карточках. Карточки, которые что-то опрашивают, — порты, бюджет, стендап, наблюдение за страницей, — теперь тикают, только пока их воркспейс открыт и окно видно, и делят одни часы на период вместо собственного интервала у каждой.
  • Браузерная карточка, на которую никто не смотрел, продолжала стримить кадры. Теперь кадры идут, только пока карточку показывает хоть одно окно: в обзоре и в скрытых воркспейсах — ноль, при возврате — снова идут.

Долгие задачи во время жестов исчезли из перетаскивания рамки, выделения рамкой, зума через порог обзора и пана в обзоре. Пану и перетаскиванию теперь нужно примерно вдвое меньше процессора рендерера; время стилей и раскладки в трассе пана упало с 311 до 53 мс и с 378 до 28 мс.

Когда карточки слишком мелкие, чтобы их читать

Отдалите холст как следует — и терминал станет серым прямоугольником. Ниже 55% (порог меняется в настройках) каждая карточка показывает вместо содержимого плитку: иконку, название и один факт, важный для её типа, — состояние агента, 3/11 у списка задач, колонки доски, первую строку заметки.

Тело карточки остаётся смонтированным под плиткой, в своём настоящем размере: терминал не убивается и даже не подгоняется заново. Плитка рисуется в экранных пикселях, поэтому читается при любом зуме. Пересечение порога перерисовывает ноль тел карточек и каждую плитку по одному разу; пан не перерисовывает вообще ничего.

Воркспейс Launch day при зуме ниже 55%: каждая карточка — читаемая плитка с состоянием
«Launch day» издалека: каждая карточка превращается в плитку с главным фактом, а терминалы под ними продолжают работать.

Есть одна тонкость. Терминал за плиткой невидим, но не на паузе — собственная проверка видимости у библиотеки терминала не замечает CSS-свойство visibility, — так что он продолжал бы рисовать вывод, который никто не видит. Вместо этого карточка копит вывод и записывает его одним махом, когда вы снова приближаете холст. Кроме одного вида фрагментов: вопрос, который программа задаёт терминалу («кто ты?», «где курсор?»), — часть диалога, и ответить на него нужно сразу. Запоздалый ответ появлялся в приглашении bash буквальным 1;2c — найдено вживую и исправлено.

Карточка «Команда»

Когда на холсте много агентов, вопрос «кто меня ждёт?» не должен требовать экскурсии по холсту. Карточка «Команда» отвечает на него одной доской: все ИИ-агенты воркспейса по группам ждёт вас, работает, закончил и свободен — с CLI, моделью и тем, сколько длится текущий ход. Нажмите на плитку, чтобы отфильтровать список; нажмите на строку — и камера перелетит к этой карточке.

Живая копия — попробуйте
КомандаАгентов: 5Ждут вас: 1
Копия карточки «Команда» со строками самого приложения. Часы идут; нажмите на плитку, а потом на строку.

Колонка модели аккуратнее, чем кажется. Она показывает модель последнего запроса агента, как она записана в его собственном журнале сессии; если такой записи нет — модель, заданную на карточке; если нет и её — модель по умолчанию из файла настроек самого CLI. Когда нет ничего из этого, там написано «Модель по умолчанию», а не догадка.

Карточка «Команда» в воркспейсе Launch day: три агента Claude Code и их состояние
Настоящая карточка. Строка один раз вспыхивает янтарным, когда агент останавливается, чтобы что-то у вас спросить.

«Команда» добавляется в каждый воркспейс один раз — левее самой левой карточки. Если её удалить, она останется удалённой; вернуть её можно из меню. Чтобы «один раз» работало правильно, понадобилось ещё одно исправление: первая версия ставила карточку раньше, чем загружалась сохранённая раскладка воркспейса, и холст раскладывал все карточки заново. Первое открытие существующего воркспейса могло сложить все его карточки в один угол. Это и есть баг из раздела «Исправлено» этого выпуска.

Изменилось и меню добавления: агенты идут первыми, установленные на этом компьютере отмечены, есть поиск, а пустой холст открывается крупными плитками агентов. Полный список — в журнале изменений 0.1.117.