Проблема: бот работает, но молчит о главном
Ядро — разговор, recasting, подбор чанков — аудит признал сильным («коронка», «ровно JTBD»). Провал в другом: всё состояние продукта невидимо. Шесть фрикций ниже — не гипотезы, а наблюдения из 91 хода симуляций, live-партии и заметок владельца.
Полная сцена кафе пройдена — система записала ноль
За 91 ход симуляций и полную живую сцену кафе (привет → заказ → цена → спасибо) — ни одного засчитанного умения. Бот празднует прозой, но система этого не записывает. Обещание «вижу свой рост» в продукте не существует.
/level работает, но это две строки: «Твой уровень: A0. Засчитано умений: 0» — прогресса не «видно»Аудит R2 · live 711772 · корень — S1/B2
История чата — могила выученного
Выученные фразы уплывают вверх в скролле; durable-памяти нет — recent_chunks держит ~10 штук и перетирается.
Перед аптекой достать «как там было „обезболивающее“?» — невозможно.
повтор живёт только внутри одного окна (recent_chunks ~10); поиск по истории Telegram бесполезен — чанки внутри длинных репликTMA-VISION F2 · аудит R2 §6
«Поняли бы меня в padaria?» — ответа нет
Голосовое сообщение получает только текстовый recast. КАК произнесено — сигнала ноль. Хуже: на тяжёлом акценте STT выдаёт мусор, и бот уверенно сочиняет фразу, которой не было.
владелец: «итоговое сообщение не разбирается с точки зрения скоринга произношения»Owner-finding O1 · 2026-06-11
«Бундае и Юг верал лампы дикфей» → бот уверенно сочинил «Bom dia e você já almoçou?»Live 711800 · дыра R2
Сценарии есть в коде — пользователь их не видит
В scenarios.py лежит каталог ситуаций с beat'ами — но выбрать «отрепетировать pediatra к завтра»
неоткуда. А это ядро JTBD: подготовка к завтрашней реальной ситуации. Заодно сцены стартуют
с «скажи bom dia» даже человеку с годом жизни в Бразилии.
scenarios.py есть, но пользователь не может его увидеть и выбрать; список из 6+ сценариев — это каталог, не сообщениеTMA-VISION F3 · аудит S3
Очередь в банке — а бот шлёт войс
Discreet-режима нет. Контекст «я в людном месте» продукту неизвестен, переключить нечем.
«говорю тихо в очереди» → войс в ответLive 711811 · дыра U3
Настройки и профиль — слепая зона
Язык объяснений, доля иммерсии, строгость коррекции — без контролов вообще. Цели из онбординга нигде не показаны и не редактируются.
настройки разбросаны по slash-командам; каждый контрол = отдельная команда + FSMTMA-VISION F5/F8
«Начнём прямо сейчас» — и ничего не начинается
Финал онбординга пассивен; цель не конвертируется в первую сцену. Голосовой ответ на вопрос цели — полная тишина: FSM проглатывает войс.
голосовой ответ на вопрос цели — полная тишина; финал «начнём прямо сейчас» ничего не начинаетLive-партия · дыра V1
Решение: семь экранов
Четыре таба: Home · Сценарии · Практика · Профиль («Практика» — хаб: повтор, произношение,
история). Дизайн — на токенах Telegram themeParams:
TMA выглядит частью Telegram, не вторым приложением. Контент в макетах — реальный, из транскриптов аудита.
Can-do карта: прогресс наконец-то видно
Прогресс — это состояние, а чат — поток: даже сообщение о победе само уплывает вверх. Дашборд держит на одном экране карту сцен (✅ закрыто / ◐ частично / ○ не начато), прогресс к уровню в can-do терминах («3 из 5 умений», не «A0.7») и CTA «ситуация на завтра» — ядро JTBD. Частичный проход тоже виден как ◐ 2/4 — ответ на «прошёл всю сцену в кафе и увидел ноль».
scenarios.py × новая таблица scenario_completions + биты из scenario_state; прогресс уровня — существующий can_do_count против порога A1.Каталог «отрепетировать к завтра»
Карточка = сценарий + сложность + beat'ы человеческим языком + сколько раз закрыт. Кнопка [Отрепетировать] — deep-link в чат: бэкенд пишет ScenarioState и шлёт первое сообщение сцены, TMA закрывается — пользователь оказывается в чате, где сцена уже началась, а не в пассивном приглашении. Это же — посадочная точка финала онбординга (F6). «Своя ситуация» уходит в чат как freeform-goal.
POST /api/scenario/start → запись ScenarioState → сообщение через Bot API → WebApp.close(). Каталог v1 = существующие 6 сценариев + 2–3 новых (домофон, devolução) — расширение scenarios.py, не новая система.Единственное место, где есть ответ «меня поняли бы?»
Запись фразы → Azure Pronunciation Assessment → пословный скоринг, слабые слова подсвечены, история попыток показывает рост 41 → 78. Reference text задан, поэтому assessment работает даже с тяжёлым акцентом — Azure сравнивает с эталоном, а не угадывает (в чате STT-гарбление делает это невозможным). Очередь фраз — из копилки и ключевых фраз завтрашнего сценария. Важно: компактный скор появится и в чате (O1, «🎯 86% · слабое: queijo» в 🎤-строке) — тренажёр это deep-practice с retry-циклом, не единственная точка сигнала.
MediaRecorder → ffmpeg (уже в стеке) → wav 16kHz → Azure PA (тот же Speech key, что STT) → JSONB в pronunciation_attempts. Синхронно, 2–4 сек.в очереди?»
Спейсед-повтор, который не спамит чат кнопками
Карточки RU → PT (вспомнить фразу), свайп «знаю / не знаю», озвучка голосом пользователя. SR-очередь требует UI «знаю/не знаю» — в чате это спам кнопками, здесь — жест. Дважды «не помню» → кнопка «потренировать в разговоре»: чанк возвращается в чат, в живой контекст — contextual SR из LINGUISTIC.md, не зубрёжка. Дневной лимит ~10 карточек: 2–3 минуты, вписывается в «10 минут утром» из JTBD.
chunk_cards WHERE sr_due_at <= today; интервалы 1→4→10→30 дней (не FSRS — отложен ещё в SPEC v2, схема совместима); TTS — существующий OpenAI, голос из users.tts_voice.«Как там было „обезболивающее“?» — за 5 секунд перед аптекой
Все когда-либо выученные чанки: PT + перевод + 🔊 озвучка + источник (сценарий/разговор) + дата. Поиск по обоим языкам — это use-case «достать фразу в момент реальной ситуации», survival job из JTBD. ⭐ закрепляет наверху и поднимает приоритет в SR-очереди. Никакой новой логики: это read-витрина той же таблицы, что и повтор, — один источник данных, два представления.
chunk_cards + TTS on-demand с кэшем в audio_ref; ILIKE-поиск по chunk и translation.«3 недели назад ты не мог заказать кофе»
Цель из онбординга — впервые видима и редактируема
native_lang, уровень, goals — наконец на экране и правятся. Правка пишет в те же колонки users, бот подхватывает со следующего сообщения — профиль и так загружается per-message. Статистика — скромная и внизу, в духе JTBD-фильтра: цифры — hidden depth, не front-and-center, без guilt-механик. Хронология — «3 недели назад ты не мог заказать кофе» — это emotional job «вижу свой рост», не геймификация.
users; «активных дней» — count distinct days по messages. Ноль новых таблиц.Пульт управления вместо россыпи slash-команд
Голос — тот же users.tts_voice, что меняет /voice, но с прослушиванием: в чате preview = 4 голосовых подряд (спам), здесь — кнопка ▶. Тихое место — бот перестаёт слать войсы (дублируется тоглом на Home). Язык объяснений, доля иммерсии, строгость — три контрола, у которых сегодня нет никакого UI. Это параметры существующих механик (correction budget, доля PT в промпте), не новые механики. Этот макет — в светлой теме: TMA автоматически следует теме Telegram через themeParams.
users.prefs (delivery_mode, explanation_lang, immersion_share, strictness, tts_speed); бот читает prefs перед отправкой ответа.Три сценария дня
Как чат и TMA работают вместе. Заметь: ни один шаг разговора не переехал в WebView — TMA только готовит, фиксирует и достаёт.
«Завтра идём к pediatra»
Win в чате → прогресс на экране
Тихое место + фраза в момент нужды
Архитектура: один бэкенд, одна база
TMA — не второй продукт, а второе окно в те же данные. Тонкий FastAPI импортирует существующие src/core и src/db напрямую: те же репозитории, тот же Postgres, ноль дублирования доменной логики.
Telegram WebApp SDK даёт initData; middleware проверяет HMAC-подпись bot-токеном, user.id = users.id. Invite-whitelist — тот же, что в боте.
Данные меняются от действий самого юзера (закрыл сцену в чате → открыл TMA через минуты). Refresh-on-open покрывает всё. Никаких WebSocket в v1.
Нужен домен + HTTPS — единственное новое внешнее требование. И это всё равно делать: аудит X1 показал, что прода сейчас нет (бот живёт на ноутбуке) — деплой-заход идёт параллельно.
Roadmap: сначала данные, потом стекло
Каждая фаза — юзабельный инкремент. Главная зависимость: бот должен начать durable-писать данные — сейчас выученные чанки живут в JSONB на ~10 штук и перетираются, а закрытия сцен не записываются вовсе. Без этого у TMA нет контента.
Пререквизит: бот начинает писать
Чанки → chunk_cards через sentinel-механизм (как [beat: done]); победы → scenario_completions на CanDoWin; фикс S1/B2 «win не зажигается» (свободные scenario-id сбрасывают стейт — корень найден); миграция 0004.
Скелет + Home + История фраз
FastAPI + initData-auth + Caddy/домен + Preact-каркас. Экраны: Home (can-do карта) и История фраз. Deep-link «🎉 → посмотреть прогресс». Самый болезненный сигнал аудита — «прошёл сцену, увидел ноль» — чинится самым дешёвым после персистенса способом.
Библиотека + Настройки + Профиль
Библиотека (+2–3 новых сценария в каталог), TMA→чат анкеровка сцены, фикс финала онбординга, настройки (голос с preview, тихое место, язык объяснений, строгость, иммерсия), профиль с редактируемыми целями; бот читает prefs. Четыре фрикции одним дешёвым экраном — почти всё это колонки, которые уже есть.
Повтор чанков (SR)
Очередь, свайпы, TTS. Намеренно после паузы: к этому моменту с v0 в chunk_cards накопится 50–100 фраз — очередь повтора сразу осмысленна, а не пуста.
Тренажёр произношения
MediaRecorder → ffmpeg → Azure assessment → пословный UI, retry, история попыток. Дифференциатор (нет ни у Duolingo-чата, ни у Chatty) — но самый тяжёлый кусок: аудио-pipeline в WebView. Делаем, когда остальное уже приносит ценность ежедневно. Компактный скор в чате (O1) едет раньше — в пакете фиксов голосового recast.
Что сознательно НЕ делаем
Anti-scope — такая же часть предложения, как и scope. Каждый пункт — решение, а не упущение.
Разговор живёт в Telegram-чате — дублировать значит расщепить продукт и историю. TMA только открывает чат deep-link'ом.
JTBD прямо: streaks — generic и потенциально toxic. Прогресс = can-do («ты теперь умеешь»), не календарь вины. «Активных дней» — пассивная цифра внизу профиля, не механика.
Anti-job из JTBD §7: уровень — побочный индикатор от can-do, не цель. Меряем «can order coffee», не «A1.2».
Один-два пользователя; job социального сравнения отсутствует.
Отложен ещё в SPEC v2. Простые интервалы 1→4→10→30; схема sr_interval / sr_due_at совместима с FSRS, если позже захочется.
Нечего стримить: данные меняются действиями самого юзера. Refresh-on-open. Единственный будущий кандидат — live-скор произношения — и тот живёт на request/response.
Статику раздаёт сам FastAPI на том же хосте. Семь экранов для одного сегмента — SEO и SSR не существуют как задачи.
Цвета из Telegram themeParams — TMA выглядит частью Telegram, не «вторым приложением». Светлый макет настроек выше — демонстрация именно этого.