Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QKHeGuAYh4W1jNdjMConZa
7.4 KiB
voice-note-format Specification
Purpose
Определяет, где и под какими именами лежат аудиофайлы и заметки голосовых записей, как в заметке хранится состояние записи и как плагин обновляет заметку, не затрагивая текст пользователя.
Requirements
Requirement: Идентификатор и имена файлов
Идентификатор записи MUST иметь вид YYYYMMDD-HHMMSS-xxxx (время начала записи в UTC и 4 случайных шестнадцатеричных символа в нижнем регистре). Аудио MUST сохраняться как voice/source/<id>.<ext>, где расширение определяется по mime без параметров: audio/mp4 → m4a, audio/webm → webm, audio/ogg → ogg, любой другой → bin. Заметка MUST создаваться как voice/inbox/YYYY-MM-DD HH-mm-ss.md по локальному времени начала записи; при занятом имени MUST добавляться суффикс (2), (3) и т.д.
Scenario: Mime с параметрами
- WHEN запись в формате
audio/webm;codecs=opusначата 27.09.2026 в 14:15:03 на устройстве в поясе UTC+3 - THEN аудио сохранено как
voice/source/20260927-111503-<4 hex>.webm, заметка —voice/inbox/2026-09-27 14-15-03.md
Scenario: Коллизия имени заметки
- WHEN заметка
voice/inbox/2026-09-27 14-15-03.mdуже существует - THEN новая заметка создана как
voice/inbox/2026-09-27 14-15-03 (2).md
Requirement: Ключи frontmatter
Состояние записи MUST храниться в плоских ключах frontmatter: voice-id, voice-status, voice-device, voice-audio (wiki-ссылка на аудио), voice-mime (исходный mime с параметрами), voice-attempts, а также, когда применимо, voice-next-attempt и voice-sent-at (ISO 8601 UTC), voice-error, voice-interrupted. Плагин MUST изменять только ключи с префиксом voice-, а остальные ключи и их порядок MUST оставаться нетронутыми. При переходе в inserted ключи voice-next-attempt, voice-sent-at и voice-error MUST удаляться. Заметка с отсутствующим или некорректным voice-status MUST игнорироваться очередью.
Scenario: Пользовательские свойства сохраняются
- WHEN пользователь добавил в заметку свойство
tags: [idea], а затем запись перешла вinserted - THEN
tags: [idea]на месте в прежней позиции, ключиvoice-*обновлены
Scenario: Испорченный статус
- WHEN пользователь вручную записал
voice-status: foo - THEN очередь не обрабатывает заметку и не падает
Requirement: Тело заметки до распознавания
Новая заметка MUST содержать вложение аудио ![[<путь к аудио>]], затем управляемую область, ограниченную маркерами-комментариями %%voice-note:<id>%% и %%/voice-note%%. Внутри области MUST отображаться состояние: для saved/sent — > [!info] Распознаётся…; для retry — > [!info] Не удалось отправить, повтор в HH:mm (попытка N/5) по локальному времени; для failed — > [!warning] Не распознано: <причина>. Команда «Повторить распознавание», кроме причины «нет речи», для которой — > [!info] В записи не распознано речи (без слова «ошибка» и без призыва повторить). Для прерванной записи над областью MUST стоять строка > [!note] Запись прервана, сохранено M:SS, которая остаётся и после вставки текста.
Scenario: Заметка в ожидании повтора
- WHEN запись перешла в
retryпосле второй неудачи со следующей попыткой в 14:17 по местному времени - THEN область содержит
> [!info] Не удалось отправить, повтор в 14:17 (попытка 2/5)
Requirement: Вставка транскрипта
При переходе в inserted управляемая область вместе с маркерами MUST заменяться текстом транскрипта. Весь текст за пределами области MUST оставаться без изменений. Если маркеры не найдены, транскрипт MUST дописываться в конец заметки через пустую строку. Если запись уже в inserted, повторная вставка MUST не менять заметку. Обновление статуса и тела MUST выполняться одной операцией записи.
Scenario: Пользователь дописал текст под заметкой
- WHEN под управляемой областью пользователь написал «купить молоко», затем пришёл транскрипт
- THEN область заменена транскриптом, «купить молоко» осталось на месте
Scenario: Маркеры удалены вручную
- WHEN пользователь стёр маркеры, затем пришёл транскрипт
- THEN транскрипт дописан в конец заметки, статус
inserted
Scenario: Дубль ответа
- WHEN транскрипт приходит для записи, уже находящейся в
inserted - THEN содержимое заметки не изменилось
Requirement: Golden-эталоны
Для каждой пары фикстур tests/fixtures/<name>.<audio-ext> + <name>.txt с эталоном <name>.note.md полный путь «захват → очередь → вставка» с провайдером, отвечающим содержимым <name>.txt, при фиксированных времени, идентификаторе устройства и источнике случайности MUST давать заметку, побайтно совпадающую с <name>.note.md. Провайдер MUST получать байты аудиофикстуры без изменений и исходный mime.
Scenario: test-01
- WHEN прогоняется фикстура
test-01.m4aс mimeaudio/mp4и ответом изtest-01.txt - THEN итоговая заметка совпадает с
test-01.note.md