---
name: ceberspost-overview
description: "CebersPost — контент-агент Telegram-канала КодоЦех (@CodoCeh): стек, запуск, статус спринтов"
metadata: 
  node_type: memory
  type: project
  originSessionId: b11e39bd-4f6f-47c9-ba7b-680ccf041a5b
  modified: 2026-08-17T07:18:28.171Z
---

**CebersPost** — контент-агент канала **КодоЦех** (`@CodoCeh`, id `-1003887555035`, бот **@cebersLog_bot** id 8984800391). Следит за изменениями проектов (git log + Obsidian Changelog) → LLM-черновик → одобрение владельцем в личке [✅/✏️/🗑] → пост в канал с брендовой картинкой. Цель: вести канал ~15 мин/нед. Аудитория смешанная (#для_бизнеса ведущая + #для_агентств).

**⚠️ 17.08.2026 — владелец отклоняет ЖАНР, а не рубрику. Диагноз через долю отказов.**
Разбор очереди: 147 одобрено, 141 отклонено, 250 висело. Доля отказов по рубрике вскрыла причину —
`грабли` 0/24 (100% отказов), `кейс` 0/7 (100%), `релиз` 22/75 (77%). Это **106 отказов из 141**.
А «вечнозелёные» идут хорошо: ликбез 24%, урок 19%, инструмент и агенты 17%, резюме и поиск работы 0%.
Вывод: посты «мы выпустили» не нужны каналу на 17 подписчиков, и владелец это стабильно чувствует.
**Метод переносимый:** при жалобе «бот генерирует не то» считать долю отказов ПО РУБРИКЕ, а не читать
промпт — сигнал оказывается сосредоточен в 2-3 категориях.

**Что сделано:** `PROJECT_RUBRICS` сведён к `("релиз",)`; `draft_project` теперь возвращает
**`Optional[DraftResult]`** — модель может вернуть `{"skip": true, "reason": …}`, если изменения
внутренние (рефакторинг, тесты, зависимости), и черновик не создаётся. Обработано в обоих местах
вызова (`bot.py` кнопка «Черновики», `weekly.py` дайджест). При отказе состояние проекта
**НЕ продвигается** — мелкие коммиты дождутся правки, заметной пользователю, и войдут в пост
с контекстом; цена — повторная оценка, пока коммиты в окне `DRAFT_LOOKBACK_DAYS`.
239 висящих помечены статусом **`expired`** (оставлены 11 августовских не-релизных).

**ГРАБЛЯ на будущее: новый статус надо СНАЧАЛА объяснить коду.** Ветвление в `bot.py` знало только
`approved` и `rejected`, любой другой статус проваливался дальше — то есть кнопка «Опубликовать» на
июньской карточке опубликовала бы июньский пост, а таких карточек в переписке 177. Введена константа
`_CLOSED = ("approved", "rejected", "expired")`, 9 кортежных проверок переведены на неё, 4 парные
получили ветку «протух». Антидубль при этом не страдает: `recent_titles`/`recent_hooks`/`recent_hooks_all`
статус не фильтруют вообще, берут последние по id.

**⚠️ 17.08.2026 — `PRODUCT_SPOTLIGHTS` в `ideas.py` это ВТОРАЯ копия фактов о продуктах, и она протухает.**
Владелец получил пост про ТБР с «бронью участка» и «AI-отчётом». Бот не выдумывал — так было
написано в справочнике `PRODUCT_SPOTLIGHTS` (питчи продуктов для рубрики «промо»). Обе ошибки:
бронь как продукт снята (`tbr/config.py:78` «BOOKING_FEE удалён вместе с бронью», `:115`
«продукт снят»), а «AI-отчёт» переименован в экспертный 01.08.2026 — переименование прошло по
четырём кодовым базам и этот справочник ПРОПУСТИЛО.
**Правило: любое переименование/снятие функции обязано проходить и по контент-машине.**
Места, где дублируются факты о продуктах: `ideas.py` → `PRODUCT_SPOTLIGHTS` (питчи), `projects.yaml`
→ `blurb`, `channels.yaml` → `theme`/`cta`. Грепать по всем трём.
Исправлено: питчи ТБР и ЗБР переписаны по стартовому экрану бота ТБР (`handlers.py:309` — источник
правды: лоты с torgi.gov.ru и банкротных торгов на карте, сравнение с закрытыми сделками рядом,
экспертный отчёт до подачи заявки, «землёй мы не торгуем»). В промпт прожектора добавлено правило
не приписывать возможностей сверх описания и не называть отчёт «AI-отчётом».

**⚠️ 17.08.2026 — промо-посты рекламировали ТОЛЬКО сайт, бота не упоминали ни разу.**
В `PRODUCT_SPOTLIGHTS` у каждого продукта было одно поле `link`, и везде это сайт — хотя у ЗБР,
ТБР, ВакХантера и Автеро бот и есть основной вход. Добавлено поле **`bot`**, `build_spotlight_topic`
теперь требует назвать оба входа. Имена сверены через `getMe`, не по памяти: `@VakHunter_bot`,
`@avtero_bot`, `@ZemlyaBezRiskaCheck_bot`, `@TorgiBezRiska_bot`. У КодоЦеха бота нет — там контакт
`@CeBers_dev`, он в промпте и был.
**Тем же заходом убран МЕХАНТ из ротации** — проект выведен 06.08.2026, домен отключён 07.08, то есть
раз в шесть недель прожектор делал бы промо мёртвого продукта с нерабочей ссылкой. Запись оставлена
закомментированной, вернуть = снять комментарий. В ротации теперь 5 продуктов (порядок сдвинулся).
**Побочное наблюдение:** качество промо-текста сильно зависит от того, какая модель ответила. Когда
праймери отдаёт 429 и генерация падает на резерв, текст скатывается в иишные обороты («это реально
с помощью нашего продукта»). Факты при этом верные. Лечится повторной генерацией — кнопка «Переписать»
берёт тему из `source_payload`, то есть уже исправленную.

**Где:** mini `~/Разработки/CebersPost`, venv `~/.venvs/cebers-post` (Python 3.9.6), launchd `local.cebers-post` (лог `state/bot.log`). ⚠️ **SSH к mini теперь через `Cebers-mini.local` (mDNS)** — IP сменился с .107. ⚠️ **venv-грабля (2026-06-09):** интерпретатор был symlink на Xcode-python3, Xcode обновился → dangling, бот падал бы при рестарте. Чинится перенаправлением `~/.venvs/cebers-post/bin/python3` на `/Library/Developer/CommandLineTools/usr/bin/python3` (3.9.6) + правка `pyvenv.cfg home`. Obsidian `Projects/CebersPost` (5 файлов). Канал — витрина бренда [[cebers-dev-brand]], кормит субподряд + будущий SaaS Гаечкин.

**Стек/грабли:** aiogram 3 polling через `AiohttpSession(proxy=http://127.0.0.1:2081)` — РКН-обход xray (с mini api.telegram.org напрямую недоступен, get_me таймаутит). ⚠️ aiogram требует `aiohttp-socks` даже для http-прокси. nohup буферизует stdout → `-u`/PYTHONUNBUFFERED. Токен в `.env` (chmod 600, gitignored). admins: `1005954706` (осн.) + `8688355181` (тест, убрать после разработки). Проверка — скилл [[tg-web-e2e]] (`python -m tests.e2e.tg_client cebersLog_bot /ping` из Gaechkin).

**Статус: Sprint 1-3 готовы (Codex + моё ревью/фиксы, 2026-06-04, commit `bb8566e`).** Работает end-to-end: collector (git+Changelog, дедуп) → drafter (OmniRoute) → brand-картинка → постинг в канал + approve/reject/rewrite (FSM) + weekly launchd. Проверено реальным прогоном (draft через /v1/messages, обезличивание zemlya, публикация+удаление, `/draft` вживую).

⚠️ **OmniRoute = Anthropic `/v1/messages`, НЕ `/v1/chat/completions`** (D7) — Codex ошибся путём, я починил: messages основной. Модель `gemini-cli/gemini-3.1-flash-lite-preview` (чистый текст; cerebras/zai/pro отдают thinking-блок и режут JSON). gemini-cli периодически 404 (upstream quota) — drafter ретраит. Картинка строго в бренде (D8): тёмно-зелёный+сетка+наковальня `assets/kodoceh_mark.png`, без debug-текста.

**Fallback-цепочки (2026-06-04, commit `aa8257c`) — no single point of failure (D11):** текст drafter перебирает модели разных провайдеров `gemini-cli/gemini-3.1-flash-lite-preview → gemini-cli/gemini-3-flash-preview → cerebras/gpt-oss-120b → nvidia/openai/gpt-oss-120b` (env `OMNIROUTE_MODELS`), max_tokens 2600. Картинка: Flux `schnell→dev` (env `IMAGE_API_URLS`) → векторная карточка.

**⚡ Быстрый primary (2026-06-19, commit `1aaa4c5`):** ПЕРЕД fallback-цепочкой drafter зовёт надёжный **`zai/glm-4.5-flash` через OmniRoute OpenAI-шину `:20129`** (`_request_chat_primary`, `thinking:{type:disabled}`, чистый JSON ~2–5с) — взято из FreeTierRadar. Ключ = **`OMNIROUTE_API_KEY` из `~/.codex/omniroute.env`** (37 симв., `settings.draft_chat_token`; ≠ omniroute_token для :20127). Раньше gemini-праймери отдавал битый JSON → фолбэк на медленную reasoning-модель ~75с (ощущалось как зависание); теперь 4.9с. Мёртвый chat-путь на :20127 (всегда 404) убран (`_request_openai_chat` удалён). Конфиг: `DRAFT_CHAT_URL`, `DRAFT_PRIMARY_MODEL`.

**AI-картинки (2026-06-04, commit `98e820b`):** картинка поста = Flux-иллюстрация (NVIDIA `flux.1-schnell`, 4 шага, endpoint `ai.api.nvidia.com/v1/genai/black-forest-labs/...`) + брендовый оверлей (скримы для читаемости, бейдж рубрики, заголовок, футер наковальня+@CeBers_dev). `images.render_post_image` (async): OmniRoute flash-lite переводит RU-заголовок→EN image-prompt → Flux (стиль flat-vector изумруд/тёмный, без текста) → композит. Фолбэк на векторную карточку (D8/D10) при сбое — пост всегда с картинкой. Ключ NVIDIA из `~/.codex/omniroute.env` (формат `export KEY=`). ⚠️ **Имя ключа менялось при ребрендинге cebers-pulse→Делор: теперь `DELOR_IMAGE_API_KEY`** (был `CEBERS_PULSE_IMAGE_API_KEY`). `_discover_image_key` (config.py) пробует несколько имён: DELOR/CEBERS_PULSE/NVIDIA_API_KEY/IMAGE_API_KEY (commit `bf61371`). Если картинки вдруг «только надпись на фоне» — проверить, что ключ находится (`settings.image_api_key`), иначе рендер падает в векторную карточку. Тумблер `IMAGE_AI_ENABLED`. NVIDIA reachable с mini напрямую.

**Ссылка на проект в постах (2026-06-09, commit `04ca6c3`, D14):** посты про свои проекты заканчиваются «👉 Попробовать: <link>». `link` в `projects.yaml` (Гаечкин `@gaechkin_bot`, VakHunter `@VakHunter_bot`; cebers-pulse без). `collector` тащит link в CollectedProject; `drafter._append_project_link` дописывает кодом (не через LLM — исказит URL) **только для own и не client_sensitive** (клиентские обезличены — ссылка раскрыла бы). link в source_payload → жив при rewrite. Проверено реальным draft_project (VakHunter).

**Мульти-канальная публикация (2026-06-20, commit `6502e24`):** посты проекта **VakHunter идут в @vakhunterInfo**, не в основной @CodoCeh. `projects.yaml`: поле `channel:` у проекта (VakHunter=`@vakhunterInfo`); нет поля → основной (`CHANNEL_ID` -1003887555035). `config.channel_for_project/channel_label_for_project` (карта из projects.yaml, `import yaml` на уровень модуля). Карточка одобрения: строка «📢 Канал: <label>» (видно КУДА уйдёт). `publish_draft(bot,draft,channel=None)` шлёт в канал проекта. Approval-first сохранён. ⚠️ @cebersLog_bot должен быть админом @vakhunterInfo с правом постинга. Каналов теперь 2.

**Мультиканальность — выбор канала на карточке (2026-06-22, commit `49583f2`, D16):** кнопка **📡 Канал** → пикер каналов, где бот админ. Список ведётся авто: `@dp.my_chat_member` ловит добавление/удаление бота админом → `storage.channels` (таблица; upsert/remove/list/title); дефолтный канал засевается на старте через `get_chat`. Выбор → `drafts.target_chat_id` (NULL=дефолт), `set_draft_target`; `publish_draft(channel=draft.target_chat_id)` шлёт туда. Чтобы канал появился — добавить бота туда админом. Проверено tg-web-e2e (target пишется в БД). Кнопки карточки: Опубликовать/Переписать/Отклонить/🖼 Другая картинка/✍️ Заголовок/📡 Канал. ⚠️ **Фикс (2026-06-24, `32cfa66`):** пикер показывал только КодоЦех — `my_chat_member` ловит лишь НОВОЕ добавление бота админом, а в @vakhunterInfo бот был админом раньше → в таблицу не попадал. Теперь `main()` сеет ВСЕ каналы из `settings.configured_channels` (дефолт + поля `channel:` из projects.yaml) через `get_chat` на старте. Новый канал в ленту → добавить бота админом ИЛИ прописать в projects.yaml + рестарт.

**Правка заголовка на картинке (2026-06-20, commit `8fd2ced`):** кнопка **✍️ Заголовок** (`draft:headline`, FSM `Flows.headline_edit`) → новый текст → `storage.update_image_headline` → `render_post_image(overlay_only=True)` → `edit_message_media`. Кэш сырых байтов Flux (`draft_<id>_raw.png`) → overlay_only перекомпонует ТОЛЬКО оверлей поверх той же иллюстрации (без нового Flux). Кнопки карточки: Опубликовать/Переписать/Отклонить/🖼 Другая картинка/✍️ Заголовок. Проверено tg-web-e2e (PHOTO_CHANGED True).

**Перегенерация картинки + фикс кривого текста (2026-06-19, commit `db50fbd`):** кнопка **🖼 Другая картинка** (`draft:reimage`) на карточке — `render_post_image(draft, seed=рандом)` перерисовывает иллюстрацию (текст поста не трогает), меняет фото через `edit_media`. Корень кривого псевдо-текста: `_build_flux_prompt` шёл через флапающий gemini :20127 и почти всегда падал → prompt = сырой рус. заголовок → Flux рисовал буквы. Перевод переведён на надёжный `:20129 zai/glm-4.5-flash` → чистый англ. visual prompt; усилён no-text в FLUX_STYLE. Проверено tg-web-e2e (PHOTO_CHANGED True).

**UX: индикатор печати + фикс кнопок (2026-06-19, commit `042a23e`):** `ChatActionSender.upload_photo` вокруг генерации (run_idea/auto_idea/draft/rewrite) — виден «отправляет фото…» (генерация бывает ~до 75с). ⚠️ Карточка = фото (caption); `edit_text` на фото падал → кнопки не снимались. `_finalize_review_message` теперь edit_caption для фото / edit_text для текста + гарантированное `edit_reply_markup` в фолбэке; rewrite снимает кнопки со старой фото-карточки и шлёт свежую. Проверено tg-web-e2e (карточка→переписать→отклонить→btns_left=0). **Известное:** праймери gemini-3.1-flash-lite часто отдаёт битый JSON («Unterminated string») → долгий фолбэк на reasoning-модель (медленно); кандидат на тюнинг (max_tokens/смена праймери/убрать мёртвый openai_chat 404).

**Фикс «публиковалась не та картинка» (2026-06-09, commit `f096694`):** карточка и публикация рисовали картинку независимо (`_build_image_name` имел timestamp → новый файл каждый раз; `_build_flux_prompt` недетерминирован). Фикс: имя детерминированно `draft_<id>.png`; `render_post_image(reuse_existing=True)` в publisher переиспользует готовый файл из карточки (фолбэк-рендер цел, если файла нет). Backfill старых черновиков. Проверено: SAME_BYTES. ⚠️ rewrite перерисовывает и перезаписывает `draft_<id>.png` — корректно (новая предложенная = опубликованная).

**Мульти-канал + контент по каналам + своё фото (2026-06-24, commit `5cdfc27`):** (1) **Мульти-отправка** — `drafts.target_chat_ids` (CSV), пикер «📡 Канал» мульти-выбор (▫️/✓, «Готово (N)», callback `togglechan`), `approve` шлёт во ВСЕ выбранные (`_resolve_targets`: мульти→legacy→дефолт). (2) **Своё фото** — кнопка «📷 Своё фото» → FSM `custom_image` → юзер шлёт фото → «🎨 С брендом» (`render_post_image(overlay_only=True)` поверх их фото) / «🖼 Как есть» (`images.use_raw_as_final`). (3) **Контент по каналам** — `channels.yaml` (профили: handle/audience/theme/cta/rubrics); `ideas.propose_extra_channels` раз в день (утро) генерит пост под аудиторию каждого доп. канала с предустановленным target и per-channel дедупом (`идея:<handle>`); `drafter.draft_idea(channel_profile,forced_rubric,project)`+`_build_channel_idea_prompt` (аудитория/тема/CTA канала, не КодоЦех). **@vakhunterInfo=карьера/работа, CTA @VakHunter_bot**; КодоЦех=осн. система рубрик (ИИ/инструменты). Новый канал в автоконтент → профиль в channels.yaml. Проверено реальными прогонами (vakhunter-пост; карточки #179/#180 владельцу; мульти-publish в оба канала).

**Длиннее/обучательнее + новости ИИ + надёжный антидубль/эмодзи (2026-06-24, commits `f9fd789`/`fdd1bac`/`e99e099`):** (1) **Антидубль идей** — был по заголовкам (ярлык «А вы знали?» → бесполезно); теперь свободным рубрикам в avoid идут реальные хуки недавних постов ТОЙ ЖЕ рубрики (`storage.recent_hooks`), курируемым — ротация по `idea_seeds` (таблица seed-термов). `build_topic_instruction(rubric,hooks,seeds,news_item=None)→(topic,seed)`. (2) **Гарантированные эмодзи** — слабая zai/glm ставила нестабильно; `drafter._ensure_emoji(body,rubric)` добивает до 3–6 (эмодзи в хук + вместо «• », потолок), если модель дала <3; ≥3 не трогает. (3) **Длина/обучательность** — idea 900-1600 знаков (было 450-900), project 700-1300; явная обучающая структура (хук→что это→как работает→пример→вывод). (4) **Крен в инструменты** — MWF_ROTATION перестроена (21/28 утр. слотов tool/agent/news/ликбез/развёртывание/урок), ликбез-инструменты 70%→80%. (5) **Новости ИИ** — новый `news.py`, рубрика `новости`: RSS **Хабр/ИИ + TechCrunch AI + MIT Tech Review + Google AI** (все отдают `<item>` напрямую с mini, без прокси; vc.ru битый), `fetch_fresh_news(avoid_links)` берёт свежий пункт; LLM пишет разбор СТРОГО по сути источника (заголовок+summary+source как заземление, запрет выдумывать сверх — иначе сочинит даты/релизы); seed=ссылка для антидубля; фолбэк на `тренд` если ленты молчат. Вплетено в `propose_idea` и кнопку `run_auto_idea` (⚠️ её call-site чинил отдельно `fdd1bac` — падала на старой сигнатуре). Проверено реальными прогонами (новости-пост 1225 знаков/5 эмодзи из Хабра). Новые ленты → `news.NEWS_FEEDS`.

**Обогащение ленты — 8 idea-рубрик (2026-06-16, commit `06fac59`):** к урок/инструмент/мнение/ликбез добавлены **миф** (разбор заблуждения), **факт** («А вы знали?»), **применение** («ИИ в жизни»), **тренд** (разбор тренда — БЕЗ дат/версий/цифр, анти-галлюцинация). Расписание `ideas` (все будни 11:30): Вт/Чт=ликбез, Пн/Ср/Пт=ротация остальных по дню года (`rubric_for_today` в ideas.py, plist не трогали). `build_topic_instruction` ветвится по каждой рубрике. Кнопка **🎲 Тема от бота** = `random.choice(IDEA_RUBRICS)` (вариативность), 🧠=ликбез. Проверено реальными прогонами (миф/факт).

**2 поста в день (2026-06-18, commit `bf9c455`):** по будням 2 авто-поста — утро `local.cebers-post-ideas` 11:30 + день `local.cebers-post-ideas-pm` 16:00 (`ideas pm`). `rubric_for_today(slot)`: slot1 даёт рубрику, гарантированно отличную от утренней (`SECOND_SLOT_ROTATION`). `ideas.main`: arg `pm`→день, arg=рубрика (агенты)→explicit. Расписание целиком: будни 11:30+16:00 (2 поста, разные рубрики) + daily-дайджест проектов при коммитах; Сб 12:00 агенты. Публикация через одобрение (D1).

**Рубрика «развёртывание» — агент без мощного железа (2026-06-18, commit `80f9771`):** где/на чём запустить агента дёшево (Android/Termux, дешёвый VPS, старый ноут, Raspberry Pi, serverless, по расписанию). Курируемый `ideas.DEPLOY_TOPICS` (9), посыл «тяжёлую модель считает облачный API → GPU не нужен». В MWF_ROTATION + SECOND_SLOT_ROTATION. `_pick_deploy_topic` антидубль. Проверено (Android/Termux пост).

**Посты-сравнения инструментов (2026-06-18, commit `63508f1`):** рубрика `сравнение` в ленте (MWF_ROTATION) — честные сравнения инструментов для ИИ-агентов. Курируемый `ideas.COMPARISON_TOPICS` (7: Claude Code/Cursor/Codex, IDE vs терминал, автодополнение vs агент, локальный vs облако, моно vs мультиагент, чат vs агент). Вывод «лучше/хуже» через «кому/каким задачам что подходит», без выдуманных цен/версий/функций и огульных оценок. `_pick_comparison_topic` антидубль. Проверено (Claude Code vs Cursor). Новые сравнения → COMPARISON_TOPICS.

**Серия про AI-агентов (2026-06-18, commit `125ea1d`, D15):** рубрика `агенты` — отдельная серия (устройство, развёртывание, где, примеры). Курируемый `ideas.AGENT_TOPICS` (14 тем), ветка `агенты` в build_topic_instruction объясняет на основе seed (без выдумки фреймворков), `_pick_agent_topic` антидубль. `ideas.main` принимает arg-рубрику → отдельный запуск `python -m cebers_post.ideas агенты`. launchd `local.cebers-post-agents` — **Сб (Weekday 6) 12:00**, отдельное время от утреннего 11:30. Проверено реальным прогоном (#103). Менять темы → AGENT_TOPICS.

**Ликбез: база по ИИ-инструментам разработки (2026-06-17, commit `053ab35`):** рубрика ликбез ~50% объясняет тонкости Claude Code/Codex/Cursor (CLAUDE.md, skills, MCP, slash, субагенты, контекст, Cursor rules, hooks, план-режим), ~50% — общие концепции ИИ. Темы инструментов — **курируемый `ideas.CODING_TOOL_TOPICS` (term, корректная суть)**; модель объясняет НА ОСНОВЕ seed и не выдумывает детали (сторонние LLM слабо знают эти тулзы). `_pick_coding_tool_topic` антидубль по недавним заголовкам. Менять детали тулзов → править CODING_TOOL_TOPICS. **2026-06-17 (commit `4093b7b`):** список расширен до 16 (+ultracode, git worktrees, фоновые агенты, MCP-реестр, память, permissions), доля инструментов 50%→70%.

**Контент-тип «ИИ на пальцах» / ликбез (2026-06-16, commit `f126efc`):** новая рубрика `ликбез` (в IDEA_RUBRICS + idea-промпт) — объяснить термин/концепцию ИИ просто, бытовой аналогией, для широкой аудитории (вовлечение). `ideas.build_topic_instruction` ветвится по рубрике: для ликбеза — спец-инструкция. Расписание `ideas` расширено на все будни 11:30 (пн урок/вт ликбез/ср инструмент/чт ликбез/пт мнение). Кнопка **🧠 ИИ на пальцах** → `run_auto_idea(rubric="ликбез")`. Проверено реальным draft_idea (пост про RAG).

**Бот сам предлагает вечнозелёные посты (2026-06-09, commit `8f02430`, D13):** помимо черновиков от git, `ideas.py` проактивно предлагает «вечнозелёные» — модель сама выбирает тему (рубрика по дню недели, D9: пн урок/ср инструмент/пт мнение) и шлёт карточку. Переиспользует `drafter.draft_idea` с мета-темой «сам выбери тему рубрики X» (drafter не трогали). `storage.recent_titles("идея",8)` → в промпт как «не повторяй» (антидубль). Кнопка **🎲 Тема от бота** (`run_auto_idea`) = тема от бота без ручного ввода; 💡 Идея (своя тема) осталась. launchd `local.cebers-post-ideas` Пн/Ср/Пт 11:30 (≠ тихий: идея = сам контент, всегда шлёт). Проверено: прямой прогон #21 + кнопка через tg-web-e2e #22 (фото+текст+кнопки).

**Карточка-предпросмотр с картинкой, без тега (2026-06-08, commit `4f070ce`):** предложение поста теперь приходит **фото+подпись** (render_post_image), а не текстом. Адаптер `_ImageDraft` в review_cards мапит `DraftResult.draft_id`→`.id` для рендера. Длинный текст (>1024) → фото+краткая подпись + полный текст отдельным сообщением с кнопками; фолбэк на текст если картинка не вышла. Убрана тег-строка «Рубрика/Трек» из карточки + хэштег-шапка из `compose_channel_post`/`publisher._compact_caption` (пост в канале начинается сразу с текста). Бейдж рубрики на самой картинке оставлен. После правки бота надо kickstart (callbacks approve/rewrite/publish держат код в памяти). ⚠️ mini IP сменился → SSH через `Cebers-mini.local` (mDNS).

**UX reply-клавиатура (2026-06-04, commit `d16e75a`):** управление без команд — постоянное меню 📝 Черновики/💡 Идея/📊 Опрос/📌 Welcome/🩺 Статус (+ ✖️ Отмена). Логика в run_draft/idea/poll/welcome; команды оставлены запасным путём. FSM-ввод для Идеи/Опроса/правки. Матчинг `F.text.in_({label,word})` — тап с эмодзи и слово. Проверено реальным TG (Идея→тема→черновик #13). Кнопки-хендлеры до state-хендлеров → тап меню сбрасывает ввод.

**Sprint 4 (2026-06-04, commit `d9cc684`):** наполнение канала — рубрики (из коммитов #релиз/#кейс/#грабли, из темы #урок/#инструмент/#мнение; поле `rubric` в drafts, бейдж на картинке + хэштег в посте), команды `/idea <тема>` (вечнозелёные посты, draft_idea, source kind=idea), `/poll Вопрос?|в1|в2`, `/welcome` (публикует+пинит welcome). Контент-микс D9: Пн кейс/урок, Ср релиз/инструмент, Пт грабли/мнение/опрос. ⚠️ aiogram `unpin_chat_message` требует `message_id=` kwarg. Всё проверено реальным прогоном.

**Каденс доставки (2026-06-05, commit `6742814`/`1682e3f`, D12):** черновики приходят (1) сразу после `git commit` через `post-commit` хук во всех включённых репо (cebers-pulse/Гаечкин/VakHunter/zemlya-web) и (2) ежедневным backstop-прогоном launchd `local.cebers-post-daily` в 11:00. Оба в **тихом режиме** (`--quiet`/`DIGEST_QUIET=1` в `weekly.py`): на пустом прогоне бот молчит (без «изменений нет»). Антидубль: `scripts/trigger_digest.sh` lockdir+дебаунс 20с → серия коммитов = один прогон; collector дедупает по последнему коммиту. ⚠️ Хук детачит trigger в фон (`& exit 0`) — иначе LLM-генерация блокирует commit. Новые репо → переустановить `scripts/install_hooks.sh`. **Корень прошлого молчания:** рассылка была только на понедельник (`Weekday 1`), не наступавший после установки. Старый `local.cebers-post-weekly` удалён. Проверено: ручной прогон прислал 2 карточки владельцу (VakHunter#18, zemlya#19), хук вернулся мгновенно + молча на пустом.

Решения: approve-gate (D1), нейтральный тон без «я один» (D4), обезличивание client (D5), OmniRoute messages-API (D7), brand-картинка (D8), рубрика+дорожка (D9), каденс ежедневно+по-коммиту тихо (D12). **Открыто:** Автеро/CebersResearch не git (enabled:false); кнопочный approve в TG-UI не гонялся (publish проверен изоляцией); тестовый admin убран (owner-only 1005954706). S4 backlog (вечнозелёные) — далее.
