Кнопки живут внутри сообщения, а не под ним
Bot API 24.08.2026 добавил в rich message кнопки (RichMessageButton, <tg-button>):
кнопка стала inline-элементом текста, стоит в строке рядом с пояснением, стили
danger/success/link, нажатие приходит тем же callback_query. До этого все меню,
мастера, предложение обновления и «Работаю» с ⏹ рисовались inline-клавиатурой под
сообщением: ряд коротких слов без места для пояснения.
Решение (13.09.2026, слово владельца). Всякая кнопка Iva — часть rich message.
Экран меню или мастера возвращает одну markdown-строку: заголовок, абзацы «кнопка — что
она делает», таблицы там, где данные табличные; ряд <tg-button-row> только для
равноправных вариантов без пояснений (да/нет, список моделей, ответы квиза). Один
помощник синтаксиса — scripts/lib/telegram-buttons.ts; agent/ держит свою пятистрочную
копию (граница слоёв). Модель предлагает кнопки сама по скиллу rich-replies, кладя
в callback_data саму реплику пользователя; Bridge превращает такой тап в обычное
входящее сообщение с этим текстом, тем же путём, что и любой текст (allowlist, очередь,
inbound pipeline). Пространства iva_* и eve* по-прежнему обрабатываются кодом.
Поправка 13.09.2026 (вечер). Android-клиенты рисуют кнопку внутри строки криво (подпись под
пилюлей), часть клиентов rich не поддерживает вовсе, поэтому: (1) кнопка всегда рядом-блоком
<tg-button-row> с пояснением строкой под ней, не в предложении; (2) по умолчанию стиль меню
classic — тот же экран разбирается в обычный текст и inline-клавиатуру одной функцией
(classicScreen в scripts/lib/telegram-buttons.ts), стиль rich включается в /menu →
Обслуживание и хранится в settings.json (menuStyle). Экраны по-прежнему пишут один rich
markdown: два рендера из одного источника, а не два набора экранов.
Отвергнуто. Двойной рендер «rich или inline по результату первого вызова» — вторая
delivery-подсистема (philosophy §5) и два места, которые разойдутся. Протокол команд в
callback_data для кнопок модели — второй handleWizardCallback, а модель и так пишет
реплику. Стриминг черновика с родным Stop — отдельное решение, не про кнопки.
Точка удаления. Когда Bot API даст подпись кнопки в callback_query, из скилла уходит
ограничение 64 байта на callback_data.
Дополнение 2026-10-06. Тап по кнопке модели Bridge подтверждает подсказкой «✅ <подпись>».
Повторный тап по той же кнопке получает «Уже выбрано» и вторым сообщением не становится. Память
о нажатиях живёт в процессе Bridge, без файла; после рестарта Bridge она пуста. «Установить»
помечается только после того, как установщик запустился: если он не запустился, копия
возвращается и кнопку можно нажать снова (ADR-0009). Нажатая кнопка правится в самом сообщении:
«✅» в подписи и стиль success, callback_data прежний. Форму без него (disabled) Telegram
живьём не проверяли, поэтому от второго сообщения держит память Bridge. У rich-сообщения
правятся блоки, у предложения в классическом виде меню — его кнопки. Цвет зависит от темы клиента («Apps may use theme-specific
colors»), поэтому знак стоит в подписи. Сообщение с медиа не правится, остаётся подсказка.
Отвергнуто: реакция под сообщением (не говорит, какая кнопка, и спорит с 👀 очереди) и хранение
отправленного markdown по message_id (новое состояние, правка Outbox).
Принятое ограничение: память Bridge ставится на тап, а не на исход хода. Если после первого тапа
ход не состоялся, второй тап по той же кнопке получает «Уже выбрано»: после неудачного хода выбор
набирается текстом. Связать память с исходом хода — новый механизм, отвергнуто.
Дополнение 2026-10-06 (вечер), слово владельца. Кнопка «Не сообщать про этого» под пунктом
Watch убрана: она писала вечный запрет за один тап, а владелец имел в виду «сегодня я в курсе».
Вместо неё «Я в курсе» (data — Я в курсе: <имя>, у англоязычного владельца Got it: <name>,
слова в scripts/lib/notice-policy.ts). Это единственная кнопка модели, которую Bridge исполняет
сам: удаляет сообщение (deleteMessage) и отвечает подсказкой, ход модели не идёт и в очередь
ничего не кладётся. Отказ удаления (старше 48 часов, нет прав) — подсказка «Не смогла удалить
сообщение», и только. Вернётся ли пункт, решает seen тика: заявка помечает пункт сообщённым до хода,
и он вернётся, только когда в чате или письме прибавится непрочитанное. Вечный запрет остаётся
фразой владельца в чате (скилл watch). Отвергнуто: ход модели на «Я в курсе» — лишний расход и
лишнее сообщение на ответ, который ничего не меняет.