opencode voice dictation
Find a file
Sergey e4ec12960f
fix(ui): skip composer injection in child session (#33)
## Что сделано

Фикс бага из issue #32: в child session (subagent) кнопка 🎤 появлялась
над disabled-блоком "Prompt is disabled / Back to parent", а клик падал
с toast "Could not find input field".

- **`src/ui.ts` `findComposer()`** — добавлен guard:
`session-prompt-dock` skip'ается в цикле `COMPOSER_SELECTORS`, если
внутри него НЕТ `[data-component="prompt-input"]` или
`[data-component="prompt-input-v2"]`. В child session внутри dock'а
только disabled-блок — guard срабатывает, `findComposer()` возвращает
`null`, кнопка не вставляется.
- **`src/ui.ts` `injectIntoComposer()`** — defence-in-depth: ранний
return, если в документе нет `[data-component="prompt-input"],
[data-component="prompt-input-v2"]`. Страховка от любых future-случаев
отсутствия реального composer (не только child session).
- **Бамп `@version`** 1.0.2 → 1.0.3 синхронно в `package.json` и
`vite.config.ts` — разблокировать автообновление userscript-менеджеров
(ловушка PR #29#30).
- **`tests/ui.test.ts`** (новый, 5 кейсов): dock с `prompt-input-v2` →
кнопка есть; dock с `prompt-input` → кнопка есть; dock без composer
(child session disabled-блок) → кнопки нет; пустой dock → кнопки нет;
question-dock открыт → composer-кнопки нет (PR #31 regression guard).
Тестирует через публичный `setupUI` + side-effect (`.ocvd-btn` в DOM).
- **Handoff** `docs/handoff/pr-XX-child-session-disabled-composer.md` и
**ADR 0004** `docs/decisions/0004-pr-XX-child-session-composer-guard.md`
с placeholder'ом PR-номера (исправлю после получения номера).

Это тот же класс бага, что и PR #31 (question-dock duplicate button) —
`session-prompt-dock` использовался как composer-target когда реальный
composer внутри не отрендерен. PR #31 пофиксил question-dock case, этот
PR закрывает child-session case.

## Почему

- **`session-prompt-dock` — общий wrapper для трёх состояний.** В
`anomalyco/opencode` (`session-composer-region.tsx`) dock рендерится
всегда когда `showComposer()` truthy (`!blocked() || !!parentID()` → в
child session всегда truthy). Внутри условно: question-dock (PR #31),
disabled-блок "Prompt is disabled / Back to parent" (child session, этот
PR), или реальный composer.
- **`findComposer()` выбирал wrapper.** `COMPOSER_SELECTORS` =
[`prompt-input-v2`, `session-prompt-dock`, `session-new-composer`,
`session-composer`] (ADR 0001). В child session `prompt-input-v2`
отсутствует, `session-prompt-dock` присутствует. Guard PR #31 (skip если
внутри `session-question-dock`) не срабатывал — внутри disabled-блок, не
question-dock.
- **Клик падал.** Кнопка вставлялась в dock через `injectIntoElement`
(`position: absolute; top: 8px; right: 8px`). При клике →
`insertIntoContenteditable()` →
`querySelector('[data-component="prompt-input"]')` = `null` → toast
"Could not find input field".
- **Defence-in-depth.** Guard в `findComposer` (не возвращать dock без
composer внутри) + guard в `injectIntoComposer` (не вставлять если в
документе нет composer). Оба слоя независимы — любой один достаточно,
второй страховка на race conditions. Аналог паттерна PR #31 / ADR 0003.

Closes #32

Closes #32

---------

Co-authored-by: opencode-agent <agent@opencode.local>
2026-07-26 17:18:08 +03:00
.github build(deps): bump softprops/action-gh-release from 2 to 3 (#14) 2026-07-09 08:13:29 +03:00
docs fix(ui): skip composer injection in child session (#33) 2026-07-26 17:18:08 +03:00
src fix(ui): skip composer injection in child session (#33) 2026-07-26 17:18:08 +03:00
tests fix(ui): skip composer injection in child session (#33) 2026-07-26 17:18:08 +03:00
.gitignore feat: initial implementation of OpenCode Voice Dictation 2026-07-08 05:37:17 +00:00
.nvmrc feat: initial implementation of OpenCode Voice Dictation 2026-07-08 05:37:17 +00:00
biome.json feat: initial implementation of OpenCode Voice Dictation 2026-07-08 05:37:17 +00:00
knip.json feat: initial implementation of OpenCode Voice Dictation 2026-07-08 05:37:17 +00:00
LICENSE feat: initial implementation of OpenCode Voice Dictation 2026-07-08 05:37:17 +00:00
package-lock.json build(deps-dev): bump @types/node from 22.20.0 to 26.1.1 (#16) 2026-07-09 08:19:28 +03:00
package.json fix(ui): skip composer injection in child session (#33) 2026-07-26 17:18:08 +03:00
README.md fix(ui): support OpenCode 1.18.x composer selectors (#29) 2026-07-25 01:29:00 +03:00
tsconfig.json feat: initial implementation of OpenCode Voice Dictation 2026-07-08 05:37:17 +00:00
vite-env.d.ts feat: initial implementation of OpenCode Voice Dictation 2026-07-08 05:37:17 +00:00
vite.config.ts fix(ui): skip composer injection in child session (#33) 2026-07-26 17:18:08 +03:00
vitest.config.ts feat: initial implementation of OpenCode Voice Dictation 2026-07-08 05:37:17 +00:00

OpenCode Voice Dictation / Голосовая диктовка для OpenCode

CI Release License: MIT


Русский

Голосовая диктовка для OpenCode web через Whisper (Groq API). Работает на ПК и телефоне.

⚠️ Требуется OpenCode web v1.18.x. В настройках OpenCode web включите «New UI» — в старом интерфейсе скрипт не работает. Совместимо с OpenCode v1.18.x; старые версии поддерживаются через fallback-селекторы.

Установка

Установить скрипт — открой ссылку в браузере с Violentmonkey/Tampermonkey и подтверди установку.

Или скачай с Releases и импортируй вручную.

Совместимость

Платформа Браузер Менеджер скриптов Статус
ПК Vivaldi Violentmonkey
ПК Chrome Tampermonkey
ПК Firefox Tampermonkey
Android Firefox Tampermonkey
Android Vivaldi Нет расширений
Android Chrome Нет расширений

Настройка

  1. Получи бесплатный ключ на console.groq.com/keys
  2. Открой OpenCode web
  3. Меню Violentmonkey/Tampermonkey → Set Groq API Key → вставь gsk_...

Использование

  1. Нажми кнопку микрофона 🎤 в правом верхнем углу поля ввода
  2. Говори — виден таймер записи и кнопка отмены
  3. Нажми ⏹ — запись остановится, текст вставится в поле ввода
  4. Или нажми ✕ — запись отменится, текст не вставляется
  5. На ПК: Ctrl+Space — горячая клавиша

Микрофон также доступен в поле ответа на вопросы OpenCode (когда выбираешь "ввести свой ответ").

Автообновление

Скрипт обновляется сам. При пуше в main CI собирает новую версию и публикует в ветку dist. Пользователи получают обновление автоматически.

Настройки

Пункт меню Описание
Set Groq API Key Ключ gsk_...
Toggle Auto-Submit Отправлять после транскрипции
Set Whisper Model whisper-large-v3 или whisper-large-v3-turbo
Set Language ru, en или пусто (авто)
Set Whisper Prompt Контекст для точности

English

Voice dictation for OpenCode web via Whisper (Groq API). Works on desktop and mobile.

⚠️ Requires OpenCode web v1.18.x. In OpenCode web settings, enable "New UI" — the script does not work in the old interface. Compatible with OpenCode v1.18.x; older versions are supported via fallback selectors.

Install

Install Script — open this link in a browser with Violentmonkey/Tampermonkey and confirm installation.

Or download from Releases and import manually.

Compatibility

Platform Browser Userscript Manager Status
Desktop Vivaldi Violentmonkey
Desktop Chrome Tampermonkey
Desktop Firefox Tampermonkey
Android Firefox Tampermonkey
Android Vivaldi No extensions
Android Chrome No extensions

Setup

  1. Get a free API key at console.groq.com/keys
  2. Open OpenCode web
  3. Violentmonkey/Tampermonkey menu → Set Groq API Key → paste gsk_...

Usage

  1. Click the mic button 🎤 in the top-right corner of the input area
  2. Speak — recording timer and cancel button are displayed
  3. Click ⏹ — recording stops, transcribed text is inserted into the input
  4. Or click ✕ — recording is cancelled, no text inserted
  5. Desktop: Ctrl+Space shortcut

The mic button is also available in OpenCode question prompt answer fields (when choosing "type your own answer").

Auto-Update

The script updates automatically. When code is pushed to main, CI builds and publishes to the dist branch. Users receive updates automatically.

Settings

Menu Item Description
Set Groq API Key API key gsk_...
Toggle Auto-Submit Auto-send after transcription
Set Whisper Model whisper-large-v3 or whisper-large-v3-turbo
Set Language ru, en, or empty (auto-detect)
Set Whisper Prompt Context for accuracy

License

MIT — see LICENSE