v0.9.3 High-Performance Rendering & Buffer Coalescing 11 мин 2 октября 2026, 08:50 — 12:30

Акт XIV: Аудит в Google AI Studio и триумф кадрового конвейера — как Gemini 3.8 Flash вскрыл скрытые лаги Flutter, а мы победили вытеснение буфера

Как полный аудит репозитория моделью Gemini 3.8 Flash в Google AI Studio выявил критические уязвимости рендеринга терминала, как три параллельных субагента устранили BUG-052..BUG-054, и как живой стресс-тест привел к решению проблемы вытеснения командных меток OSC 133.

BUG-052 BUG-053 BUG-054 #flutter #rendering #gemini-3.8-flash #ai-studio #terminal #stream-coalescing #box-drawing #osc-133 #performance #v0.9.3
Контекст акта: Когда проект оброс матричными сплитами, интерактивными разметками команд и интеграцией с ConPTY, пришло время взглянуть на кодовую базу со стороны предельно непредвзятым взглядом. Загрузка репозитория в Google AI Studio и глубокий анализ моделью Gemini 3.8 Flash преподнесли отрезвляющий диагноз: графический конвейер терминала находился в шаге от перегрузки главного потока и незаметно сжигал батарею в режиме ожидания. Мы приняли вызов: развернули трех параллельных субагентов для перестройки рендеринга, а в ходе последующего стресс-тестирования совершили еще одно критическое открытие — спасли систему командных меток OSC 133 от хаоса при вытеснении строк истории.

Запись 60–62. Аудит репозитория в Google AI Studio, модернизация рендеринга и победа над вытеснением буфера истории

⏱️ Время: 2 октября 2026, 08:50 — 12:30 (~3.5 часа)

1. Контекст: когда репозиторий отправляется на рентген

Я всегда придерживался простого принципа: архитектор не должен слепо верить в безупречность своего кода только потому, что юнит-тесты горят зеленым, а интерфейс приятно радует глаз. Терминал — коварная среда. В нативных утилитах вроде Alacritty или Kitty отрисовка ложится на плечи кастомных шейдеров видеокарты и ассемблерных оптимизаций. Во Flutter же мы работаем поверх абстракций движка Impeller/Skia, дерева виджетов и компоновщика параграфов.

Чтобы получить независимую, жесткую оценку зрелости проекта, я скормил весь репозиторий Shellit в Google AI Studio, подключив новейшую флагманскую модель Gemini 3.8 Flash.

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

Заключение Gemini 3.8 Flash оказалось предельно точным и местами обескураживающим. Нейросеть буквально разложила по кадрам графический конвейер нашего терминала и указала на три критических архитектурных изъяна, мимо которых мы проходили каждый день.

2. Подводные камни: три диагноза от Gemini 3.8 Flash

Отчет аудита вскрыл следующие фундаментальные проблемы:

  1. Паразитный жор батареи на мигании курсора (BUG-052):
    Каждые 550 миллисекунд внутренний таймер каретки вызывал метод _terminal.notifyListeners(). Поскольку терминальный рендерер рисовал фон, текст и курсор на одном общем слое Canvas, этот периодический сигнал приводил к тому, что Flutter в режиме полного простоя (когда на экране не меняется ни единого символа!) заново рассчитывал и перерисовывал все сорок видимых строк терминала. На производительном настольном компьютере это оставалось незаметным, но на ноутбуках и смартфонах это означало постоянную ненужную нагрузку на графический чип и стабильный разряд аккумулятора.

  2. Угроза зависания UI Isolate при интенсивном выводе (BUG-053):
    Входящий сетевой поток SSH-сокета передавался в терминальную стейт-машину порциями по мере поступления байтов из операционной системы. Если удаленный сервер начинал выплевывать тысячи строк (например, при компиляции ядра или выводе cat access.log), поток пользовательского интерфейса бомбардировался сотнями вызовов в секунду. Отсутствие кадровой синхронизации (VSync) приводило к микрофризам интерфейса, а если порция байтов разрезала многобайтовый символ UTF-8 (например, кириллицу) прямо посередине, терминал выводил уродливые символы-ромбы U+FFFD.

  3. Микрозазоры и ступеньки в псевдографике (BUG-054):
    Символы рамок Box Drawing (U+2500..U+257F) рисовались как обычные текстовые глифы установленного моноширинного шрифта. Из-за субпиксельного сглаживания и округления координат при различных масштабах экрана (125%, 150%) рамки в htop, mc или lazygit распадались на прерывистые сегменты с неприятными микрощелями.

Диагноз был поставлен, и мы немедленно зафиксировали детальную спецификацию в архитектурном документе TERMINAL_RENDERING_ARCHITECTURE.md.

3. Логика решений с ИИ: три субагента в параллельном строю

Вместо того чтобы вручную переписывать модули в течение многих дней, я применил модульную организацию монорепозитория: сформулировал строгие контракты и запустил в Antigravity трех специализированных субагентов параллельно:

  • Субагент сетевого конвейера (BUG-053): спроектировал TerminalStreamCoalescer. Компонент аккумулирует сетевые чанки байтов и передает их в терминал строго синхронно с частотой обновления экрана через SchedulerBinding.instance.scheduleFrameCallback. Независимо от того, сколько сотен пакетов прилетело из сокета за 16 миллисекунд, парсинг и перерисовка происходят ровно один раз за кадр. Дополнительно был реализован алгоритм _getIncompleteUtf8TrailingByteCount, который придерживает незавершенные хвосты UTF-8 до следующего кадра, и защитный порог OOM Guard (>512 КБ).
  • Субагент векторной графики (BUG-054): создал BoxDrawingVectorRenderer, охвативший весь диапазон из 128 символов псевдографики. Линии вычерчиваются прямыми математическими векторами canvas.drawLine строго по геометрическому центру знакоместа без антиалиасинга. Все рамки mc и htop стали монолитными при любом масштабировании экрана.
  • Субагент слоя каретки (BUG-052): вынес мигающий курсор в изолированный виджет TerminalCursorOverlay с собственным RepaintBoundary и прозрачным для мыши IgnorePointer. Таймер переведен на легковесный ValueNotifier<bool>. В режиме ожидания перерисовка холста терминала полностью прекратилась: нагрузка на процессор упала до честного нуля.

После сборки и интеграции все 198 тестов завершились зеленым успехом. Мы собрали свежий исполняемый файл shellit.exe и перешли к полевым испытаниям.

4. Внезапное открытие на живом тесте: загадка вытесненных строк

Я запустил свежую сборку и дал терминалу экстремальную нагрузку, заставив его непрерывно генерировать гигантские простыни текста.

Результат впечатлял: терминал буквально летал. Никаких намеков на подтормаживание, прокрутка оставалась шелковистой, а процессор в паузах мгновенно засыпал.

Но в процессе скроллинга наверх мой взгляд зацепился за странную аномалию. Когда вывод насчитывает десятки тысяч строк, ранние фрагменты естественно вытесняются из памяти терминала — емкость кольцевого буфера истории не бесконечна. Однако наши фирменные маркеры шелл-интеграции OSC 133 вели себя так, словно ничего не произошло!

Цветные точки на левом поле и засечки на полосе прокрутки продолжали отображаться для команд, чьи строки уже давно растворились в небытии. Хуже того: при попытке кликнуть на такую метку терминал скроллился в самый верх доступной истории и указывал на совершенно случайный текст чужих команд!

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

  1. Метки обязаны исчезать по мере вытеснения строк:
    Если строка с приглашением ввода (promptLine) была вытеснена из кольцевого буфера, ассоциированный с ней командный блок больше не имеет физического адреса на экране. Оставлять его маркер на левом поле или на скроллбаре — значит вводить пользователя в заблуждение. Метки слева и справа должны скрываться синхронно.

  2. Связь через живой объект строки:
    Вместо хранения статичного целочисленного индекса строки мы связали ShellCommandBlock напрямую со строкой буфера BufferLine with IndexedItem. Если буфер вытесняет строку, метод promptBufferLine.isEvicted мгновенно возвращает true.

  3. Информативная индикация:
    Пользователь не должен гадать, куда подевались его старые команды. На полосе прокрутки в самом верху появился аккуратный счетчик [▲ N]. При наведении курсора всплывает четкая подсказка: «N ранних команд вытеснено из буфера истории терминала». При этом клавиатурная навигация по командам (Alt+Up / Alt+Down) безопасно игнорирует вытесненные блоки, предотвращая прыжки в пустоту.

5. Итог

Мы еще раз прогнали расширенный набор тестов:

  • 202 юнит- и виджет-теста пакета terminal_ui пройдены со 100% успехом (включая 4 новых теста на динамическое вытеснение строк и индикатор [▲ N]).
  • Статический анализатор flutter analyze по всему монорепозиторию рапортует об абсолютной чистоте: No issues found!
  • Сформирована и проверена боевая сборка shellit.exe.
  • Shellit обрел эталонный графический конвейер: абсолютно холодный режим ожидания, плавный вывод на любых скоростях и безупречную честность истории команд.

Благодаря жесткому и объективному аудиту Gemini 3.8 Flash в Google AI Studio мы не просто исправили недочеты, а вывели архитектуру терминального рендеринга на качественно новый инженерный уровень.

📖 Типографика статьи выровнена по алгоритму Кнута — Пласса с помощью mcp-typography-server на базе Justif.
Knuth-Plass • TeX Style