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

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