RUGitHubInstall

Кнопки живут внутри сообщения, а не под ним

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). Отвергнуто: ход модели на «Я в курсе» — лишний расход и лишнее сообщение на ответ, который ничего не меняет.

View original Markdown ↗