Qurany · Recite · отчёт о реализации

Qurany Recite - Mobile Phase 1 Implementation

Phase 1 — Mistake Evidence, мобильная часть A0–A7 Ветка: feature/recite-phase1-mistake-evidence База: integration/app-ml @ 6d2d3bd HEAD: c4609fb (только локально, не запушено) Дата: 12.10.2026
Needs Testing Locally implemented, awaiting live validation Всё сделано и проверено офлайн. Проверки против реального сервера 8723 и на телефоне ещё не было.

01Краткий результат

8 / 8
задач A0–A7 реализовано в коде
1763
тестов ридера проходят (до работы: 1702)
1344
тестов React Native проходят
0
сессий против реального 8723

Verified

Что реализовано и подтверждено офлайн

  • Протокол 1.6: разбор reason, letter_errors, heard_text, extra_words, session_id.
  • Локальная запись сессии: те же байты, что ушли в сокет.
  • Карточка слова, лишние слова, экран «Ошибки», цвета, строгость.
  • Прежний протокол и boundary 1.7 не сломаны (тесты зелёные).

Verified

Что протестировано

  • TypeScript: ридер и RN — без ошибок.
  • Unit / component тесты: ридер 83 файла, RN 95 наборов.
  • Код приложения против stub-сервера ML Senior по настоящему WebSocket (не реальный ML).
  • Android release APK собран.

Needs Testing

Что ещё не подтверждено

  • Реальные ready / word_update / final от 8723.
  • Кадр word_update с words: [] (stub его не шлёт).
  • APK на телефоне: отрисовка, прослушивание, файлы.
  • iOS нативная сборка.

02Выполнение A0–A7

Принцип плана сохранён: всё, что решает «ошибка или нет» и «какая именно», остаётся на сервере. Приложение хранит аудио, рисует и проигрывает. Цвет — из state + reason, подсветка — из character_span / character_index, отрезок — из start_seconds / end_seconds.

A0

Protocol 1.6 integration

Stub verified · live pending
Реализовано
open объявляет protocol_version: "1.6" и выбранную строгость. Разбор новых полей слова и extra_words на ready / word_update / final, ready.session_id. word_update с words: [] и изменившимся списком лишних слов применяется. Ревизии: меньшая или равная игнорируется, замороженное слово не меняется. Список extra_words заменяется целиком. Неизвестный reason → unclear. Битые поля улик отбрасываются, не ломая кадр и вердикт.
Компоненты
recite-ml-wire.ts, recite-ml-types.ts, recite-ml-session.ts, recite-ml-verse-session.ts
Результат
Против stub: все кадры приняты без ошибок разбора, поля дошли до состояния приложения.
Проверка
Verified unit + stub   Needs Testing 8723
A1

Session audio recording

Offline verified
Реализовано
Наблюдатель на RN-мосте пишет ровно те PCM-байты (16 бит LE, 16 кГц, моно), которые принял сокет, начиная с первого кадра после open. Смещение = секунды × 32 000. Папка recite-sessions/<session_id>/: audio.pcm, meta.json, final.json (кадр final как есть) или partial.json + статус «incomplete». На сервер ничего дополнительно не уходит. Удаление одной и всех сессий. Срок хранения 30 дней — сохраняемая настройка.
Компоненты
lib/recite-ml-recording.ts, lib/recite-ml-recording-store.ts, lib/recite-ml-bridge.ts, ReaderHost.tsx
Результат
Stub-сессия: отправлено 163 840 байт = записано 163 840, побайтно идентично; 0,82 с → байт 26 240 попадает в нужный чанк. Прерванная сессия даёт partial.json.
Проверка
Verified unit + stub   Needs Testing файлы на устройстве
A2

Word evidence index

Offline verified
Реализовано
По каждому событию сохраняются index, state, revision, frozen, reason, confidence, времена, vowel_errors, letter_errors, heard_text и extra_words. После final источником истины становится final.json. В списке подтверждённых ошибок — только frozen.
Компоненты
recite-ml-recording.ts (свёртка по правилам ревизий), parseRecordedSession, buildErrorRows
Результат
Сохранённая сессия читается тем же парсером, что и живой поток.
Проверка
Verified unit + stub   Needs Testing 8723
A3

Mistake detail card

Component verified
Реализовано
Тап по отмеченному слову открывает карточку: причина, «Ожидалось» с подсветкой конкретной буквы (красный) или хараката (жёлтый), «Услышано». Индексы — кодовые точки полного текста аята (ready.uthmani). Подсветка по графемному кластеру, вставка — маркер между символами. Прослушивание max(0, start−0,3) … end+0,3; null-граница берётся у соседа; skipped — от конца предыдущего до начала следующего; нет времён — кнопки нет.
Компоненты
ReciteMistakeCard.tsx, recite-ml-evidence-view.ts, recite-recordings-client.ts
Результат
Пример контракта 1:5: отрезок [11,12) подсвечивает عْ; индекс 36 — касру в نَسْتَعِينُ.
Проверка
Verified unit + render-тесты   Needs Testing звук на телефоне
A4

Extra words in Mushaf

Unit verified
Реализовано
Лишнее слово — отдельная красная вставка с heard_text между словом after_index и следующим (null — перед первым). Соседнее слово не перекрашивается. Незамороженная вставка приглушена и с пунктирной рамкой. Та же карточка и прослушивание.
Компоненты
TeacherMode.tsx (TeacherModeExtraWord), buildMlExtraMarks
Результат
Stub: ثُمَّ после слова 1 сначала unfrozen, затем frozen.
Проверка
Verified unit + stub   Needs Testing вёрстка на телефоне
A5

Errors screen and history

Logic verified
Реализовано
Кнопка «Ошибки» рядом с ML-статусом. История по сессиям и группировка по суре. Источник — сохранённый final или partial (метка «Не завершена»). Только frozen-ошибки и лишние слова; unclear — отдельная группа «Не удалось разобрать». Карточка и прослушивание, удаление сессии и всех записей.
Компоненты
ReciteErrorsSheet.tsx, мост reciteRecordings (list / clip / delete / deleteAll)
Результат
Логика строк и разбор сохранённых сессий покрыты тестами. Сам экран рендер-тестом не покрыт.
Проверка
Verified логика   Needs Testing экран на телефоне
A6

Mistake colors and reasons

Unit verified
Реализовано
wrong_letter и skipped — красный, wrong_vowel — жёлтый, unclear — серый, подпись причины под подтверждённым словом. До frozen оценка рисуется приглушённо, без подписи. ML-скоринг на клиенте не дублируется. Сервер без поля reason (до 1.6) получает прежний вид без изменений.
Компоненты
teacherMistakeAppearance, resolveMlEvidenceMark
Результат
Таблица отрисовки контракта закреплена тестами.
Проверка
Verified unit   Needs Testing вид на телефоне
A7

Strictness setting

Stub verified
Реализовано
Мягкий / обычный / строгий. На проводе: gentle / standard / strict («normal» сервер не примет). Сохраняется на устройстве, передаётся в open, пишется в meta.json. Изменение действует со следующего аята. Серверная логика не менялась.
Компоненты
recite-ml-strictness.ts, контроллер → sequencer → session
Результат
Stub принял strict и вернул его в ready.
Проверка
Verified unit + stub   Needs Testing 8723

03Визуальные сценарии пользователя

Это схемы, а не скриншоты приложения. Иллюстрации ниже нарисованы для пояснения сценария. Реальных скриншотов ещё нет: APK не запускался на телефоне.
1

Читает аят

Схема إِيَّاكَ نَعْبُدُ وَإِيَّاكَ Listening

Микрофон → PCM 16 кГц → сокет 8723. Те же байты пишутся в файл сессии.

2

ML отмечает ошибку

Схема إِيَّاكَ نَعْبُدُ Wrong letter ثُمَّ лишнее (предв.)

Цвет из reason. Лишнее слово — отдельная вставка; до frozen — пунктир.

3–5

Тап → буква, ожидалось / услышано

Схема Wrong letter Expected نَعْبُدُ Heard نَحْبُدُ ▶ Listen

Подсвечен именно символ из character_span. Харакат подсвечивается жёлтым.

6–7

Прослушивание и история

Схема audio.pcm (своя запись) 0.52 → 2.01 c (±0.3) Errors · 1:5 Wrong letter نَعْبُدُ Wrong vowel نَسْتَعِينُ

Отрезок слова с запасом 0,3 с. История — по сессии и по суре.

wrong_letter, skipped, лишнее слово wrong_vowel unclear до frozen — предварительно
Во время записи (микрофон активен) кнопка прослушивания заблокирована: иначе динамик попал бы в микрофон и ушёл на сервер как чтение.

04Тестирование

Все прогоны повторены 12.10.2026 на HEAD c4609fb перед подготовкой отчёта.

ПроверкаЧто проверяетРезультатСтатус
Reader tests (vitest)Весь офлайн-ридер, включая новые evidence-тесты83 файла · 1763 / 1763Verified
React Native tests (jest)RN-слой, включая запись, мост и валидатор сообщений95 наборов · 1344 / 1344Verified
TypeScript checkstsc --noEmit для ридера и RN0 ошибок · 0 ошибокVerified
Новые тесты Phase 1Протокол, ревизии, words: [], замена extra_words, frozen, Unicode/RTL, отрезки, строки ошибок, карточка, клиент записей, строгостьридер 26 + 21 + 5 + 3 · RN 14 + 7Verified
Boundary regressionСуществующее boundary 1.7 (replay, continuation, provenance)33 + 31 + 32 — все зелёныеVerified
Legacy regressionКадры без полей 1.6 и прежний вид ошибокотдельные тесты + полный прогонVerified
Architecture guardsРегулярные проверки архитектуры проектавсе пройдены*Verified
WebSocket stub testsКод приложения ↔ contract_stub_server.py (локально, настоящий WebSocket)Кадры без ошибок; запись побайтно совпала; прерванная сессия → partialVerified (stub)
Android APK buildRelease APK, endpoint 8723, тестовый ридерBUILD SUCCESSFUL, 20 мин 33 с; URL 8723 в бандлеVerified (сборка)
JS-бандлы Android и iOSMetro / Hermes собирает весь RN-кодОба собраныVerified
Реальный E2E с ML (8723)Сессии с настоящей модельюНе выполнялся: внутренняя сеть ML-сервера недоступнаBlocked
APK на телефонеUI, звук, файлы на устройствеНе устанавливалсяNeeds Testing
iOS native buildXcode-сборкаНет Mac в окруженииBlocked
Тесты против stub-сервера — это контрактные тесты, а не реальный E2E с ML. Stub отдаёт заранее записанную сессию аята 1:5 без модели.

* В Windows-окружении первый шаг скрипта (generate-quran-search-index --check) падает из-за autocrlf — это известная проблема, не связанная с Phase 1. Сами guard-проверки прогнаны отдельно без этого шага и пройдены.

05Ограничения

Blocked

8723 не проверен по реальной сети

Машина разработчика вне внутренней сети ML-сервера, порты 8720/8722/8723 недоступны. Нужна проверка из офисной сети.

Needs Testing

APK не протестирован на телефоне

Собран, но не установлен. Отрисовка, WebAudio-прослушивание и запись файлов на устройстве не подтверждены.

Blocked

iOS native build не подтверждён

Проверен только JS-бандл для iOS. Нативная сборка требует Mac.

Needs Testing

Нужна совместная проверка с ML Senior

Кадр words: [], реальные причины и времена, сценарий Тартиля.

Needs Testing

Хранение аудио

Записи лежат в cache-папке приложения: так они не попадают в облачный бэкап на обеих платформах без нативного кода. Но ОС может очистить cache при нехватке места раньше 30 дней. Полноценный isExcludedFromBackup / правила бэкапа Android требуют нативного модуля. Настройку срока хранения пока нельзя изменить из UI.

Needs Testing

UI

Лишнее слово — вставка между словами (не метка на поле). Переключатель строгости — в экране «Ошибки». Тексты подписей — заглушки на английском и индонезийском (языки ридера). Подписи под словами на длинных строках не проверены визуально.

Совместимость 1.6 и 1.7. «1.6» в задании ML Senior — это mistake evidence. В исследовательской ветке «1.6/1.7» означали boundary (boundary, capabilities, context_prefix_samples). Сервер 8723 собран без boundary-работы: capabilities игнорирует, boundary не присылает. Приложение при этом ничего не ломает и переходит к следующему аяту прежним способом, но без boundary replay при непрерывном чтении. Boundary-код в приложении не менялся.

06Что необходимо для завершения Phase 1

  • Мобильная часть A0–A7 реализована, unit / component / stub-тесты зелёные.
  • Android test APK собран с endpoint 8723.
  • Установить APK, проверить в Profile → ML server, что сохранённый override не перебивает 8723.
  • Живая сессия на 8723: ready → word_update → final без ошибок разбора.
  • Поймать word_update с words: [] и изменившимися extra_words.
  • У каждого красного, жёлтого и серого слова есть подпись.
  • Тап показывает подсвеченный символ, ожидалось / услышано и проигрывает свой голос в нужном месте.
  • Лишнее слово видно между словами, соседи не краснеют.
  • Аудио не покидает телефон; удаление работает.
  • Экран «Ошибки» показывает ошибки сессии и суры из сохранённых final.
  • Сценарий Тартиля: те же суры и намеренные ошибки на обоих приложениях.
  • Решения продакта: хранение (cache или Documents + исключение из бэкапа), форма лишнего слова, тексты подписей, показ unclear.
  • iOS: нативная сборка и проверка на устройстве.
  • Code review и решение о push / MR (пока всё локально).

07Информация для ML Senior

Подключение

  • Тестовый endpoint: ws://<внутренний тестовый сервер ML>:8723/stream (адрес у команды)
  • open.protocol_version: "1.6"
  • open.strictness: gentle | standard | strict (по умолчанию gentle)
  • Аудио: pcm_s16le, 16 000 Гц, моно, кадры по 320 мс
  • Приложение также шлёт capabilities: ["boundary_replay"] (8723 его игнорирует) и при наличии prior_tempo_baseline
{"type":"open","protocol_version":"1.6","verse_key":"1:5",
 "audio_format":{"encoding":"pcm_s16le","sample_rate":16000,"channels":1},
 "strictness":"standard", ...}

Какие поля APP принимает

  • Слово: index, text, state, frozen, revision, confidence, start_seconds, end_seconds, vowel_errors[], letter_errors[], heard_text, reason (phone_range — только переносится)
  • letter_errors[]: kind, character_span [start, end), expected, heard (не показывается), heard_text, confidence
  • Событие: extra_words[] — after_index, heard_text, start_seconds, end_seconds, frozen
  • ready.session_id, ready.uthmani; final.model_fingerprint сохраняется в meta

Какие события ожидает

  • ready (seq 0) → word_update* → position_update → verse_complete → final
  • backpressure (slow_down), error
  • word_update с words: [] при изменении только extra_words
  • extra_words — всегда полный список, заменяется целиком
  • Ревизия ≤ текущей игнорируется; frozen-слово не меняется

Что проверить вместе

  • Живая сессия на 8723 с APK, на нескольких аятах.
  • Кадр words: [] реально приходит.
  • ready.session_id присылается всегда (иначе запись получает локальное имя).
  • Времена слов совпадают с локальной записью при прослушивании.
  • Подсветка character_span на шадде, мадде, вставке.
  • Номер версии: объединить evidence и boundary в одну ветку.
Evidence 1.6 ≠ boundary 1.7. Сервер проверяет только мажорную версию, поэтому "1.6" принимают 8720, 8722 и 8723. Но boundary-поля на 8723 отсутствуют. Нужен единый номер версии после объединения веток.