v0.9.1 Semantic Shell Integration & Workspace Resilience 12 мин 30 сентября 2026, 09:00 — 16:00

Акт XII: Живая простыня терминала — как маркеры команд превратили бесконечный лог в интерактивную карту, и дань уважения корням Open Source

История создания семантической разметки терминала OSC 133: цветные маркеры статуса, хронометраж команд, копирование вывода в один клик, пошаговая навигация Alt+Up/Down, осознанное включение без авто-инъекций и открытая благодарность Фабрицио Ла Роза (fbrzlarosa/terminale) и форку uxiew за вдохновение.

BUG-046 BUG-047 BUG-048 BUG-049 BUG-050 #osc-133 #shell-integration #xterm #flutter #terminal #powershell #open-source #fbrzlarosa #terminale #v0.9.1
Контекст акта: Классический терминал страдает от синдрома «бесконечной простыни»: в длинном потоке логов трудно найти начало команды, понять, где произошел сбой, или аккуратно скопировать только её вывод. Вдохновившись эргономикой открытого сообщества, мы внедрили протокол семантической интеграции шелла OSC 133. В процессе боевого тестирования мы преодолели ловушки скоупинга переменных в PowerShell, исправили залипание скролла при навигации, спасли восстановление локальных терминалов при перезапуске и отказались от опасных фоновых инъекций в пользу осознанного включения через диалог, отдав заслуженную дань уважения авторам оригинального опенсорс-проекта Terminale.

Запись 54. От бесконечной простыни к интерактивной карте: семантическая разметка команд (OSC 133)

⏱️ Время: 30 сентября 2026, 09:00 — 11:30 (~2.5 часа)

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

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

В этой бесконечной «простыне» ориентироваться крайне утомительно:

  • Где именно началась последняя команда, а где вывод предыдущей?
  • Завершилась ли утилита успешно или упала с ошибкой где-то посередине экрана?
  • Как скопировать только вывод этой конкретной команды, не возя мышью по экрану через несколько экранов с риском зацепить системные подсказки?

В открытом коде есть особый момент радости — когда разработчик другого интересного инструмента заходит в твой репозиторий и оставляет звезду на GitHub. Так случилось с проектом uxiew/terminale (быстрый кроссплатформенный эмулятор терминала на Flutter). Я перешел на страницу репозитория, чтобы познакомиться с наработками коллеги, и мы сели с ИИ детально сопоставить его сильные стороны с Shellit: что уже реализовано у нас, а в чём коллеги нашли изящные эргономические находки.

И здесь, при внимательном взгляде на древо коммитов и форков на GitHub, открылась важнейшая деталь: репозиторий uxiew является форком оригинальной разработки итальянского инженера Фабрицио Ла Роза — fbrzlarosa/terminale.

Здесь хочется сделать важное отступление об открытом коде. Я не программист по профессии и создаю архитектуру Shellit в диалоге с искусственным интеллектом, направляя инженерные требования от сценариев реального использования. Но уважение к первоисточникам в опенсорс-сообществе — это вопрос профессиональной этики. Наша искренняя и главная благодарность направляется именно Фабрицио Ла Роза (fbrzlarosa) как создателю оригинальной концепции и архитектуры Terminale, который первым реализовал идею нативного терминала на Flutter, а также форку uxiew за активное развитие проекта, благодаря которому состоялось это вдохновляющее знакомство.

Изучив современные тенденции, мы поняли, что Shellit обязан получить первоклассную поддержку семантической разметки команд.


2. Под капотом: протокол OSC 133 (FinalTerm / Shell Integration)

В терминальных стандартах существует изящный протокол, используемый в VS Code, iTerm2 и Warp — управляющие последовательности OSC 133:

  • \x1b]133;A\x07 — начало строки приглашения (Prompt Start).
  • \x1b]133;B\x07 — завершение ввода и запуск команды (Command Executed).
  • \x1b]133;C\x07 — начало полезного вывода утилиты (Output Start).
  • \x1b]133;D;<exit_code>\x07 — команда завершена с явным кодом возврата (0 — успех, $\ne 0$ — ошибка).

Мы реализовали архитектурный модуль ShellIntegrationController в пакете packages/terminal_ui, перехватывающий эти маркеры прямо из потока VT-последовательностей эмулятора xterm.dart.

На базе этого ядра родилась комплексная система навигации:

  1. Левые маркеры промпта (ShellGutterMarkersOverlay): В левом отступе экрана напротив каждой введенной команды загорается аккуратная точка:
    • 🟢 Изумрудный кружок — команда завершилась успешно (exit code 0).
    • 🔴 Красный кружок — команда упала с ошибкой (exit code $\ne 0$).
    • 🔷 Бирюзовый пульсирующий индикатор — команда прямо сейчас выполняется в фоне (Running...).
  2. Копирование вывода в один клик: Клик по точке мгновенно копирует весь чистый стандартный вывод команды в буфер обмена без заголовка промпта.
  3. Миникарта на полосе скроллбара (ShellCommandMarkersOverlay): На треке прокрутки отображаются цветные засечки, позволяющие визуально оценить успешность команд по всей глубине истории сессии.
  4. Тумблер в тулбаре: Иконка [ ⚡ Markers ] в верхнем меню позволяет одним нажатием скрыть или показать разметку, если требуется абсолютно минималистичный вид.

Запись 55. Подводные камни PowerShell и точный расчет экранных координат

⏱️ Время: 30 сентября 2026, 12:15 — 12:40 (~25 минут)

Когда мы запустили первые тесты на реальной машине под Windows в PowerShell, интерфейс преподнес неожиданные сюрпризы:

  1. Команда с ошибкой окрасилась в зеленый цвет: Команда Get-Item NonExistent завершилась ошибкой, но кружок рядом с ней загорелся оптимистичным зеленым светом!
  2. Плавающие артефакты при коротком выводе: На чистом старте, когда в терминале было всего 3 строчки, справа отображались засечки несуществующего скроллбара, а перед боковой панелью проступала серая вертикальная черта.
  3. Нажатие на кнопку в тулбаре ломало ввод: Клик по бейджу вставлял код инициализации прямо в командную строку шелла, вызывая многострочный ввод >>.

Разбор причин и устранение дефектов (BUG-047)

  • Коварство $? в PowerShell: В настроечной функции промпта строка $e = [char]27; выполнялась успешно, автоматически сбрасывая переменную статуса $? в $True. Из-за этого последующая проверка считала любую упавшую команду успешной. Мы перенесли захват статуса в самое первое выражение:
    $c = if (-not $global:?) { 1 } elseif ($LASTEXITCODE) { $LASTEXITCODE } else { 0 };.
  • Точные экранные координаты: Вместо приблизительной геометрии позиционирование кружков переведено на прямой запрос координат рендера: RenderTerminal.getOffset(CellOffset(0, block.promptLine)).dy.
  • Адаптивный скроллбар: Миникарта на полосе прокрутки теперь активируется исключительно тогда, когда буфер терминала реально выходит за пределы высоты окна (lines.length > viewHeight).

Запись 56. Пошаговая навигация: физический скролл от метки к метке и измерение времени выполнения

⏱️ Время: 30 сентября 2026, 13:10 (~20 минут)

Следующим шагом стала эргономика перехода по истории. Когда в терминале десятки команд, пользователь хочет листать историю не колесиком мыши, а привычными шорткатами Alt + ↑ (к предыдущей команде) и Alt + ↓ (к следующей).

И здесь обнаружились два тонких момента:

  1. Залипание скролла на дне окна: Находясь в самом низу терминала, нажатие Alt + ↑ не приводило ни к какому эффекту. Скролл оставался на месте.
  2. Отсутствие времени выполнения: Подсказка маркера показывала Success (exit 0), но тайминг [1.2s] отсутствовал.

Инженерное решение (BUG-048)

  1. Причина залипания: Предыдущий расчет зажимал целевую координату (promptLine * lineHeight) через clamp(0.0, maxScrollExtent). Для команд, находящихся в пределах нижнего экрана, оффсет зажимался в maxScrollExtent — ровно то значение, где скролл уже находился! Вызов animateTo пытался сместить окно на 0 пикселей.
    Решение: Алгоритм _jumpToPreviousCommand переработан на поиск контрольной точки, пиксельная позиция которой строго меньше текущей видимой зоны (targetPixels < currentOffset - 1.0). Теперь каждый клик Alt + ↑ гарантированно сдвигает экран вверх к предыдущему промпту, а Alt + ↓ — вниз.
  2. Тайминг выполнения (notifyCommandStarted): Оболочки запускают функцию prompt уже после завершения работы утилиты. Мы перехватили нажатие клавиши Enter в терминале и начали фиксировать точный момент старта (startTime = DateTime.now()). Теперь наведение мыши на маркер раскрывает исчерпывающую информацию:
    Command #3: Success (exit 0) [1.4s].

Запись 57. Оживление локальных терминалов: как спящий режим вкладок подружился с локальными консолями PowerShell, CMD и WSL

⏱️ Время: 30 сентября 2026, 14:00 (~25 минут)

1. Контекст: когда «спящие» вкладки отказываются просыпаться

В версии 0.8.6 мы внедрили долгожданное сохранение вкладок рабочего пространства: закрываешь Shellit, открываешь снова — и все сессии аккуратно восстанавливаются на своих местах в режиме бережного ожидания («lazy disconnected»). Нажал кнопку подключения — и сессия мгновенно подхватывается.

Для удаленных SSH-хостов и SFTP эта схема работала безотказно. Но стоило пользователю перезапустить приложение с открытой вкладкой локального терминала (PowerShell, командная строка Windows или WSL), как начинались странности:

  1. Восстановленная вкладка показывала статус ожидания, но клик по кнопке «Подключиться» (Connect) не приводил вообще ни к какому результату: процесс шелла не стартовал, консоль оставалась немой.
  2. В карточке ожидания локальной вкладки отображались неуместные сетевые фразы: «Resolving endpoint…», шаги расшифровки паролей сейфа и SSH-рукопожатия — для локального PowerShell на собственном компьютере!
  3. Пункт контекстного меню вкладки «Reconnect» также игнорировал локальные терминалы.

2. Подводные камни: потеря профиля и скрытая проверка host == null

Глубокий разбор логики десериализации сессий выявил цепочку незаметных архитектурных нестыковок:

  • Потеря ссылки на профиль шелла: При сохранении состояния рабочего пространства идентификатор профиля localShellId честно записывался в БД, но при вызове restoreWorkspaceTabs передавался только список удаленных хостов (allHosts). Локальные профили в метод не передавались вовсе! В результате вкладка восстанавливалась с localShellProfile == null, теряя свою идентичность (какой именно шелл нужно запускать — PowerShell 7, CMD или Ubuntu WSL).
  • Сетевое допущение if (tab.host == null) return;: Обработчик нажатия кнопки повторного запуска _handleRetryConnection был исторически написан в расчете только на удаленные серверы. Для локальных вкладок поле tab.host естественно равно null. Встретив null, метод молча прерывал выполнение без единого сообщения в лог.
  • Отсутствие метода инъекции PTY в существующую вкладку: В контроллере SessionManagerNotifier существовал метод openLocalTerminalTab, который всегда создавал новую вкладку. Способа запустить PTY-процесс в уже существующую восстановленную вкладку не было в контрактах.

3. Логика решений с ИИ (BUG-049)

Мы последовательно довели цикл восстановления локальных сессий до ума:

  1. Резолв локальных профилей при восстановлении: В restoreWorkspaceTabs добавлена передача списка localShellProfiles и системного дефолтного шелла defaultShellProfile. Теперь система находит точный профиль по saved.localShellId, а если профиль был удален из ОС (например, удален дистрибутив WSL) — плавно переключается на стандартный шелл без падений.
  2. Прямой запуск PTY в существующую вкладку (launchLocalTerminalForTab): Реализован метод контроллера, который инициализирует LocalTerminalSession.start и бесшовно монтирует живой поток псевдотерминала прямо в ожидающую вкладку, снимая статус ожидания и очищая ошибки.
  3. Разблокировка обработчиков запуска: В _handleRetryConnection, контекстном меню TopBarTabs.onReconnectTab и фоновом автоконнекте снято ограничение по хосту: при tab.type == TabType.localTerminal мгновенно вызывается старт локального терминала.
  4. Адаптированный экран ожидания (TerminalConnectingView): Экран ожидания локального шелла избавлен от сетевых этапов: отображается фирменный неоновый значок >_, название шелла (PowerShell / CMD), путь к исполняемому файлу и кнопка «Запустить терминал» (Icons.play_arrow_rounded). Расположение кнопок переведено на Wrap, полностью исключая ошибки переполнения RenderFlex overflow.

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

⏱️ Время: 30 сентября 2026, 15:40 (~25 минут)

1. Соблазн фоновой автоматизации: ловушка PTY-эхо

Когда базовая механика OSC 133 заработала, возникло естественное желание сделать её абсолютно незаметной для пользователя. Идея казалась привлекательной: при открытии SSH-вкладки к удаленному серверу клиент через полсекунды сам, в фоновом режиме, отправляет в шелл компактный хук, на лету прописывая функцию перехвата промпта. Пользователь просто открывает терминал — и маркеры уже на месте.

Но реальный запуск на боевых серверах преподнес отрезвляющий урок.

В интерактивной сессии SSH удаленный терминал работает с включенным эхо (ECHO): каждый отправленный клиентом байт сервер транслирует обратно на экран. Когда клиент отправляет многострочный скрипт в поток ввода, код начинает заворачиваться в несколько ярусов. Попытки скрыть его служебными кодами стирания (\033[1A\033[2K) очищают только последнюю строку, оставляя в окне консоли многострочные обрывки кода вместо чистого приглашения.

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

2. Инженерное решение (BUG-050)

Мы полностью переработали архитектуру внедрения хуков и вернули пользователю безусловный контроль:

  1. Полный демонтаж фоновых инъекций: Из реестра сессий TerminalSessionRegistry удалены все фоновые таймеры и попытки отправки скрипта в inputStream. Сессия открывается кристально чистой.
  2. Нулевой шум по умолчанию: Отображение маркеров в левом поле и на скроллбаре теперь выключено по умолчанию (_showGutterMarkers = false), а кнопка [⚡ Markers] в тулбаре отображается в спокойном приглушенном сером цвете.
  3. Модальный диалог настройки (ShellIntegrationSetupDialog): При первом нажатии на кнопку [⚡ Markers] или через контекстное меню открывается лаконичный диалог с тремя прозрачными вариантами:
    • Активировать в текущей сессии: отправляет компактный хук, завершающийся немедленной командой ; clear\n. Экран сервера мгновенно очищается от эхо, оставаясь безупречно чистым, а все последующие команды сессии получают цветные маркеры статуса и хронометраж.
    • Установить навсегда (~/.bashrc): аккуратно дописывает хук в конфигурационный файл пользователя на сервере с последующим применением. При последующих подключениях маркеры подхватываются автоматически.
    • Скопировать скрипт: копирует код в буфер обмена для ручной проверки или вставки.
  4. Безопасная верстка без переполнений: Внутреннее содержимое диалога защищено скролл-контейнером SingleChildScrollView, гарантируя адаптивность на любых разрешениях экрана.

Итог акта

  • Простыня терминала Shellit превратилась в умный структурированный журнал.
  • Команды получили статусную индикацию, пошаговую клавиатурную навигацию и копирование вывода в один клик.
  • Архитектура шелл-интеграции построена на принципе невмешательства: нулевой фоновый шум, чистое подключение и осознанный выбор пользователя.
  • Все 150 тестов пакета packages/terminal_ui успешно пройдены.
  • Открыто и честно зафиксирована дань уважения сообществу Open Source — автору оригинального проекта fbrzlarosa/terminale и форку uxiew/terminale.
📖 Типографика статьи выровнена по алгоритму Кнута — Пласса с помощью mcp-typography-server на базе Justif.
Knuth-Plass • TeX Style