diff --git a/MessageContext-RU.md b/MessageContext-RU.md
index eed9dce..f7779b8 100644
--- a/MessageContext-RU.md
+++ b/MessageContext-RU.md
@@ -105,6 +105,19 @@ English version: [[MessageContext]]
Они покрывают самые частые callback-сценарии без ручного хождения в `tgapi`.
+## Rich-сообщения
+
+`RichAnswer(...)` и `RichAnswerKeyboard(...)` отправляют структурированные rich-сообщения Bot API 10.1, собранные из фрагментов `tgfmt`:
+
+```go
+ctx.RichAnswer(
+ tgfmt.H1(tgfmt.NewRich("Отчёт")),
+ tgfmt.P(tgfmt.NewRich("всё работает").Bold()),
+)
+```
+
+Полный DSL и приёмная сторона описаны на странице [[Rich-Messages-RU]].
+
## Drafts и localization
У `MessageContext` есть:
diff --git a/MessageContext.md b/MessageContext.md
index f111dee..a995e58 100644
--- a/MessageContext.md
+++ b/MessageContext.md
@@ -111,6 +111,19 @@ This is useful for:
- generated summaries
- reports with an action button at the end
+## Rich messages
+
+Use `RichAnswer(...)` and `RichAnswerKeyboard(...)` to send structured Bot API 10.1 rich messages built from `tgfmt` fragments:
+
+```go
+ctx.RichAnswer(
+ tgfmt.H1(tgfmt.NewRich("Report")),
+ tgfmt.P(tgfmt.NewRich("all systems go").Bold()),
+)
+```
+
+See [[Rich-Messages]] for the full DSL and the receive side.
+
## Markdown helpers
Use:
diff --git a/Rich-Messages-RU.md b/Rich-Messages-RU.md
new file mode 100644
index 0000000..7b740a0
--- /dev/null
+++ b/Rich-Messages-RU.md
@@ -0,0 +1,96 @@
+# Rich-сообщения
+
+Rich-сообщения — механизм Bot API 10.1 для сильно структурированного текста: заголовки, абзацы, списки с чекбоксами, таблицы, медиа-коллажи, раскрывающиеся секции, математика и другое. Laniakea поддерживает их целиком: типизированный HTML-DSL для отправки, типизированные wire-структуры для приёма и стриминг черновиков.
+
+## Ключевая асимметрия
+
+Telegram сделал отправку и приём намеренно разными, и Laniakea повторяет это разделение:
+
+- **Отправляется разметка, а не дерево.** Единственный способ отправить rich-сообщение — `tgapi.InputRichMessage` со строкой `HTML` или `Markdown`. API для отправки дерева блоков не существует.
+- **Принимается дерево, а не разметка.** Входящее rich-сообщение приходит как `Message.RichMessage` — полностью типизированное дерево узлов `RichBlock` и `RichText`, собранное сервером.
+
+Поэтому в Laniakea:
+
+- **out**-сторона живёт в `tgfmt` (`rich.go`): типизированные HTML-фрагменты и конструкторы с чистыми именами (`P`, `H1`, `Bold`, `Photo`, ...);
+- **in**-сторона живёт в `tgapi` (`richtext.go`, `richblock.go`): wire-типы с именами официальных объектов API (`RichBlockParagraph` — это `RichBlockWrap{Tag: "paragraph"}`, `RichTextBold` — `RichTextWrap{Tag: "bold"}` и т.д.) плюс `UnmarshalRichMessage`.
+
+## Отправка из обработчика
+
+`MessageContext.RichAnswer(...)` принимает фрагменты `tgfmt` и отправляет их через `sendRichMessage`:
+
+```go
+import "git.scuroneko.dev/scuroneko/laniakea/tgfmt"
+
+func report(ctx *laniakea.MessageContext, _ struct{}) error {
+ ctx.RichAnswer(
+ tgfmt.H1(tgfmt.NewRich("Дневной отчёт")),
+ tgfmt.P(
+ tgfmt.NewRich("Статус: "),
+ tgfmt.NewRich("всё работает").Bold(),
+ ),
+ tgfmt.Ul(
+ tgfmt.LiCheckbox(true, tgfmt.NewRich("бэкапы")),
+ tgfmt.LiCheckbox(false, tgfmt.NewRich("миграция")),
+ ),
+ )
+ return nil
+}
+```
+
+`RichAnswerKeyboard(kb, items...)` прикрепляет inline-клавиатуру.
+
+## DSL в `tgfmt`
+
+Два типа фрагментов превращают невалидную вложенность в ошибку компиляции:
+
+- `tgfmt.Rich` — inline-содержимое (жирный, ссылки, эмодзи, время, математика, ...);
+- `tgfmt.RichBlock` — блочное содержимое (заголовки, абзацы, списки, таблицы, медиа, ...).
+
+Блочные конструкторы принимают только `Rich`-аргументы, поэтому таблица внутри абзаца не скомпилируется. На *верхнем уровне* допустимы оба (`RichItem`): соседний inline-контент Telegram сам собирает в абзацы.
+
+Сырой текст попадает в DSL ровно одним способом — `tgfmt.NewRich("...")`, который экранирует HTML. Всё остальное — композиция уже безопасных фрагментов.
+
+Inline-хелперы (методы `Rich`): `Bold`, `Italic`, `Underline`, `Strike`, `Code`, `Mark`, `Sub`, `Sup`, `Spoiler`, `Link`, `Email`, `Phone`, `Mention`, `Anchor`, `AnchorLink`, `Ref`, `Time`, `TimeFormat`, `Math`; свободные функции `Emoji`, `Br`.
+
+Блочные конструкторы: `H1`–`H6`, `P`, `Pre`, `PreCode`, `Footer`, `Hr`, `AnchorBlock`, `Ul`/`Ol` с `Li`/`LiCheckbox`, `Blockquote`, `Aside`, `Photo`/`Video`/`Audio` (медиа-билдеры с `.Block()`, `.Caption(...)`, `.SetSpoiler()`), `Map`/`MapCaption`, `Collage`/`Slideshow` (+варианты `...Caption`), `Table`/`Row`/`Cell`, `Details`, `MathBlock`.
+
+Чтобы собрать payload без отправки, используйте `tgfmt.RichHTML(items...)` (строка) или `tgfmt.RichMessage(items...)` (готовый `tgapi.InputRichMessage` с включённым `skip_entity_detection`, чтобы сервер не добавлял авто-entities).
+
+## Прямой доступ через `tgapi`
+
+- `API.SendRichMessage(params)` — отправка; `params.RichMessage` — это `InputRichMessage`.
+- `API.SendRichMessageDraft(params)` — стриминг частичного сообщения во время генерации. Черновик — эфемерное превью на ~30 секунд с ключом `DraftID` (обновления с тем же ID анимируются); в конце вызовите `SendRichMessage` с полным сообщением. Черновики — единственное место, где встречается блок `thinking`.
+- `EditMessageText.RichMessage` — редактирование rich-сообщения на месте (`Text` оставьте пустым).
+- `InputRichMessageContent` — rich-контент для результатов inline-запросов (`input_message_content`).
+
+## Приём
+
+`Message.RichMessage` (`*tgapi.RichMessage`) разбирается автоматически. Обходите его type switch-ами:
+
+```go
+for _, block := range msg.RichMessage.Blocks {
+ switch b := block.(type) {
+ case tgapi.RichBlockWrap: // paragraph, footer, thinking
+ handleText(b.Text)
+ case tgapi.RichBlockList:
+ for _, item := range b.Items {
+ // item.Label — готовый видимый маркер: "1.", "c.", "vii.", "•"
+ }
+ }
+}
+```
+
+Неизвестные будущие типы узлов с полем `text` сохраняются как `RichTextWrap`/`RichBlockWrap`, а не роняют разбор — парсинг переживает расширения API.
+
+## Подводные камни
+
+- **Медиа — только по URL.** В HTML-режиме ``/`