fix(deploy): restore namespace and register menu commands at top-level #4

Closed
opened 2026-08-08 16:05:47 +03:00 by slaid098 · 0 comments
Owner

Контекст

Зачем: после миграции с GitHub на Forgejo (PR#3) у пользователя появилось ДВА скрипта в Tampermonkey вместо одного. Причина: @namespace был сменён с https://github.com/slaid098/opencode-voice-dictation на https://git.slaid098.dev/... — Tampermonkey/Violentmonkey идентифицируют скрипт по паре @name + @namespace (Greasemonkey-стандарт). Смена namespace = новый скрипт, не обновление. Дополнительно: 7 menu-команд (Set Groq API Key и др.) регистрируются только после isOpencodePage() → true (src/index.ts:227 внутри init() после gate index.ts:207-211). На не-OpenCode странице меню пустое (только Edit/Delete) — пользователь не может настроить скрипт сразу после установки.

Контекст:

  • @namespace — это идентификатор скрипта (строка-ID для дедупликации), НЕ функциональный URL. Userscript-менеджеры НЕ резолвят его как URL. Менять его при миграции хоста = сломать идентификацию → дубликаты.
  • @updateURL/@downloadURL/@icon — функциональные URL, их смена на Forgejo (PR#3) правильна и работает.
  • Menu-команды (GM_registerMenuCommand) в src/config.ts:49-55 вызываются из registerMenuCommands() (src/index.ts:227-235), который находится ВНУТРИ init() ПОСЛЕ gate isOpencodePage() (index.ts:207-211). На не-OpenCode странице gate возвращает false → init() уходит в setTimeout(init, 1500) retry → registerMenuCommands() не вызывается → меню пустое.
  • Callbacks menu-команд (promptForApiKey, promptForEndpoint, и др.) используют GM_setValue (работает на любой странице) + ui?.toast(...) (optional chaining — no-op если ui ещё null). Безопасно регистрировать на top-level.
  • Текущая версия: 1.0.6 (vite.config.ts:11 + package.json:3, синхронно).

Задача

1. vite.config.ts:10 — вернуть старый @namespace (идентификатор, не URL)

  • namespace: "https://git.slaid098.dev/slaid098/opencode-voice-dictation" → namespace: "https://github.com/slaid098/opencode-voice-dictation"
  • Обоснование: это ID для дедупликации в userscript-менеджерах, не функциональный URL. Возврат = Tampermonkey обновит старый скрипт (1.0.4) вместо создания дубликата.
  • @updateURL/@downloadURL/@icon НЕ трогать — они уже на Forgejo (PR#3, рабочие).

2. Version bump 1.0.6 → 1.0.7 (СИНХРОННО)

  • vite.config.ts:11: version: "1.0.6" → version: "1.0.7"
  • package.json:3: "version": "1.0.6" → "version": "1.0.7"
  • Обоснование: bump триггерит автоапдейт. Старый скрипт (1.0.4, мёртвый @updateURL на GitHub) не автоапдейтнется, но при ручной переустановке по ссылке из README (с @namespace = старый) Tampermonkey предложит «обновить» существующий 1.0.4 → 1.0.7, без дубликата.

3. src/index.ts — вынести registerMenuCommands() из gate на top-level

  • Сейчас: registerMenuCommands({...}) вызывается внутри init() на строках 227-235, ПОСЛЕ gate isOpencodePage() (строки 207-211).
  • Перенести вызов registerMenuCommands({...}) с теми же callback-аргументами на top-level, ДО вызова init() (т.е. между определением init() на строке 206 и вызовом init() на строке 240 — или прямо перед init() вызовом).
  • Удалить вызов registerMenuCommands({...}) из тела init() (строки 227-235).
  • Оставить в init(): setupUI, setupKeyboardShortcut, checkFirstRun — они зависят от OpenCode DOM / ui.
  • Callbacks (promptForApiKey, toggleAutoSubmit, promptForModel, promptForLanguage, promptForWhisperPrompt, promptForEndpoint, promptForTemperature) — проверить что они в scope на top-level. Если они function declarations — hoisted, ОК. Если const/let — должны быть определены до вызова. Прочитать файл и убедиться.
  • ui?.toast(...) в callbacks — optional chaining, no-op если ui ещё null (до gate). GM_setValue работает на любой странице. Безопасно.

4. AGENTS.md — добавить правило про @namespace

В секцию ## Userscript metadata (или создать после ## @icon) добавить:

## `@namespace`

`vite.config.ts` `userscript.namespace` — это ИДЕНТИФИКАТОР скрипта для userscript-менеджеров (Tampermonkey/Violentmonkey используют пару `@name` + `@namespace` для дедупликации, Greasemonkey-стандарт). НЕ функциональный URL — менеджеры НЕ резолвят его. НЕ менять при миграции хоста (GitHub → Forgejo) — смена namespace = userscript-менеджер видит «новый» скрипт → дубликат вместо обновления.

Контракты

  • @name + @namespace = уникальный ID скрипта в Tampermonkey/Violentmonkey/Greasemonkey. Совпадение пары = «обновить существующий», несовпадение = «новый скрипт».
  • @namespace НЕ резолвится как URL — это opaque string (исторически URL по конвенции, но менеджеры НЕ делают HTTP-запрос к нему).
  • GM_registerMenuCommand регистрирует команду в меню Tampermonkey popup. Работает на любой странице (не зависит от DOM). Доступна если @grant GM_registerMenuCommand (есть, vite.config.ts:16).
  • GM_setValue/GM_getValue — persistent storage, работает на любой странице.
  • Version в vite.config.ts и package.json обязаны совпадать (AGENTS.md ## Version bump).

Инварианты

  • @connect * НЕ сужать (AGENTS.md ## @connect).
  • @updateURL/@downloadURL/@icon остаются на Forgejo (PR#3, рабочие) — НЕ откатывать на GitHub.
  • COMPOSER_SELECTORS порядок НЕ менять (ADR 0001–0004).
  • assets/icon.png НЕ удалять, путь НЕ менять.
  • whisperPrompt default "" НЕ трогать (ADR-0007).
  • setupUI/setupKeyboardShortcut/checkFirstRun остаются ВНУТРИ init() после gate — они зависят от OpenCode DOM.
  • ADR append-only: НЕ редактировать docs/decisions/**.

Граничные случаи

  • Старый скрипт 1.0.4 с мёртвым @updateURL (raw.githubusercontent.com → 404) НЕ автоапдейтнется. Пользователь должен один раз перейти по install-ссылке из README → TM предложит «обновить» (namespace совпадёт) → 1.0.4 → 1.0.7, без дубликата. После этого новый @updateURL (Forgejo) заработает для будущих автоапдейтов.
  • Menu-команды на не-OpenCode странице: GM_setValue сработает (config сохранится), ui?.toast no-op (нет UI). Пользователь не увидит подтверждения, но значение сохранится. При переходе на OpenCode страницу сохранённый config подхватится. Это приемлемо — лучше чем вообще не иметь доступа к настройкам вне OpenCode.
  • Если callback functions (promptForApiKey и др.) — const/let и определены ниже top-level вызова: переместить вызов registerMenuCommands после их определений. Прочитать src/index.ts и проверить.

Влияние на связанные компоненты

  • src/config.ts — без изменений (registerMenuCommands function definition не трогается, только call site в index.ts).
  • src/insert.ts — без изменений (isOpencodePage() gate остаётся для setupUI/setupKeyboardShortcut/checkFirstRun).
  • vite.config.ts — только namespace (строка 10) + version (строка 11). @updateURL/@downloadURL/@icon/@connect НЕ трогать.
  • package.json — только version (строка 3).
  • AGENTS.md — добавить секцию ## @namespace.

Вне scope

  • ❌ ADR-0008 о миграции (отдельный PR)
  • ❌ Вынос setupUI/checkFirstRun из gate (они зависят от DOM, оставляются)
  • ❌ Очистка мёртвых GitHub-ссылок в docs/**
  • ❌ Renovate config
  • ❌ Ограничение retry loop init() (отдельная задача, noted в memory gotchas)

Критерии приемки

  • vite.config.ts:10 — namespace = https://github.com/slaid098/opencode-voice-dictation (старый, как ID)
  • vite.config.ts:11 + package.json:3 = 1.0.7 (синхронно)
  • vite.config.ts — @updateURL/@downloadURL/@icon остаются на Forgejo (НЕ откатаны)
  • src/index.ts — registerMenuCommands({...}) вызывается на top-level (до init()), НЕ внутри init() после gate
  • src/index.ts — setupUI/setupKeyboardShortcut/checkFirstRun остаются внутри init() после gate
  • AGENTS.md — добавлена секция ## @namespace с правилом про идентификатор
  • npm run lint → pass
  • npm run typecheck → pass
  • npm run test → pass
  • npm run build → dist/opencode-voice-dictation.user.js header содержит @namespace https://github.com/slaid098/... + @version 1.0.7 + @updateURL https://git.slaid098.dev/...
  • @connect * остался (не сужен)
## Контекст Зачем: после миграции с GitHub на Forgejo (PR#3) у пользователя появилось ДВА скрипта в Tampermonkey вместо одного. Причина: `@namespace` был сменён с `https://github.com/slaid098/opencode-voice-dictation` на `https://git.slaid098.dev/...` — Tampermonkey/Violentmonkey идентифицируют скрипт по паре `@name` + `@namespace` (Greasemonkey-стандарт). Смена namespace = новый скрипт, не обновление. Дополнительно: 7 menu-команд (`Set Groq API Key` и др.) регистрируются только после `isOpencodePage()` → `true` (`src/index.ts:227` внутри `init()` после gate `index.ts:207-211`). На не-OpenCode странице меню пустое (только Edit/Delete) — пользователь не может настроить скрипт сразу после установки. Контекст: - `@namespace` — это идентификатор скрипта (строка-ID для дедупликации), НЕ функциональный URL. Userscript-менеджеры НЕ резолвят его как URL. Менять его при миграции хоста = сломать идентификацию → дубликаты. - `@updateURL`/`@downloadURL`/`@icon` — функциональные URL, их смена на Forgejo (PR#3) правильна и работает. - Menu-команды (`GM_registerMenuCommand`) в `src/config.ts:49-55` вызываются из `registerMenuCommands()` (`src/index.ts:227-235`), который находится ВНУТРИ `init()` ПОСЛЕ gate `isOpencodePage()` (`index.ts:207-211`). На не-OpenCode странице gate возвращает `false` → `init()` уходит в `setTimeout(init, 1500)` retry → `registerMenuCommands()` не вызывается → меню пустое. - Callbacks menu-команд (`promptForApiKey`, `promptForEndpoint`, и др.) используют `GM_setValue` (работает на любой странице) + `ui?.toast(...)` (optional chaining — no-op если `ui` ещё `null`). Безопасно регистрировать на top-level. - Текущая версия: 1.0.6 (vite.config.ts:11 + package.json:3, синхронно). ## Задача ### 1. `vite.config.ts:10` — вернуть старый `@namespace` (идентификатор, не URL) - `namespace: "https://git.slaid098.dev/slaid098/opencode-voice-dictation"` → `namespace: "https://github.com/slaid098/opencode-voice-dictation"` - Обоснование: это ID для дедупликации в userscript-менеджерах, не функциональный URL. Возврат = Tampermonkey обновит старый скрипт (1.0.4) вместо создания дубликата. - `@updateURL`/`@downloadURL`/`@icon` НЕ трогать — они уже на Forgejo (PR#3, рабочие). ### 2. Version bump 1.0.6 → 1.0.7 (СИНХРОННО) - `vite.config.ts:11`: `version: "1.0.6"` → `version: "1.0.7"` - `package.json:3`: `"version": "1.0.6"` → `"version": "1.0.7"` - Обоснование: bump триггерит автоапдейт. Старый скрипт (1.0.4, мёртвый `@updateURL` на GitHub) не автоапдейтнется, но при ручной переустановке по ссылке из README (с `@namespace` = старый) Tampermonkey предложит «обновить» существующий 1.0.4 → 1.0.7, без дубликата. ### 3. `src/index.ts` — вынести `registerMenuCommands()` из gate на top-level - Сейчас: `registerMenuCommands({...})` вызывается внутри `init()` на строках 227-235, ПОСЛЕ gate `isOpencodePage()` (строки 207-211). - Перенести вызов `registerMenuCommands({...})` с теми же callback-аргументами на top-level, ДО вызова `init()` (т.е. между определением `init()` на строке 206 и вызовом `init()` на строке 240 — или прямо перед `init()` вызовом). - Удалить вызов `registerMenuCommands({...})` из тела `init()` (строки 227-235). - Оставить в `init()`: `setupUI`, `setupKeyboardShortcut`, `checkFirstRun` — они зависят от OpenCode DOM / `ui`. - Callbacks (`promptForApiKey`, `toggleAutoSubmit`, `promptForModel`, `promptForLanguage`, `promptForWhisperPrompt`, `promptForEndpoint`, `promptForTemperature`) — проверить что они в scope на top-level. Если они `function` declarations — hoisted, ОК. Если `const`/`let` — должны быть определены до вызова. Прочитать файл и убедиться. - `ui?.toast(...)` в callbacks — optional chaining, no-op если `ui` ещё `null` (до gate). `GM_setValue` работает на любой странице. Безопасно. ### 4. `AGENTS.md` — добавить правило про `@namespace` В секцию `## Userscript metadata` (или создать после `## @icon`) добавить: ``` ## `@namespace` `vite.config.ts` `userscript.namespace` — это ИДЕНТИФИКАТОР скрипта для userscript-менеджеров (Tampermonkey/Violentmonkey используют пару `@name` + `@namespace` для дедупликации, Greasemonkey-стандарт). НЕ функциональный URL — менеджеры НЕ резолвят его. НЕ менять при миграции хоста (GitHub → Forgejo) — смена namespace = userscript-менеджер видит «новый» скрипт → дубликат вместо обновления. ``` ## Контракты - `@name` + `@namespace` = уникальный ID скрипта в Tampermonkey/Violentmonkey/Greasemonkey. Совпадение пары = «обновить существующий», несовпадение = «новый скрипт». - `@namespace` НЕ резолвится как URL — это opaque string (исторически URL по конвенции, но менеджеры НЕ делают HTTP-запрос к нему). - `GM_registerMenuCommand` регистрирует команду в меню Tampermonkey popup. Работает на любой странице (не зависит от DOM). Доступна если `@grant GM_registerMenuCommand` (есть, `vite.config.ts:16`). - `GM_setValue`/`GM_getValue` — persistent storage, работает на любой странице. - Version в `vite.config.ts` и `package.json` обязаны совпадать (AGENTS.md `## Version bump`). ## Инварианты - `@connect *` НЕ сужать (AGENTS.md `## @connect`). - `@updateURL`/`@downloadURL`/`@icon` остаются на Forgejo (PR#3, рабочие) — НЕ откатывать на GitHub. - `COMPOSER_SELECTORS` порядок НЕ менять (ADR 0001–0004). - `assets/icon.png` НЕ удалять, путь НЕ менять. - `whisperPrompt` default `""` НЕ трогать (ADR-0007). - `setupUI`/`setupKeyboardShortcut`/`checkFirstRun` остаются ВНУТРИ `init()` после gate — они зависят от OpenCode DOM. - ADR append-only: НЕ редактировать `docs/decisions/**`. ## Граничные случаи - Старый скрипт 1.0.4 с мёртвым `@updateURL` (raw.githubusercontent.com → 404) НЕ автоапдейтнется. Пользователь должен один раз перейти по install-ссылке из README → TM предложит «обновить» (namespace совпадёт) → 1.0.4 → 1.0.7, без дубликата. После этого новый `@updateURL` (Forgejo) заработает для будущих автоапдейтов. - Menu-команды на не-OpenCode странице: `GM_setValue` сработает (config сохранится), `ui?.toast` no-op (нет UI). Пользователь не увидит подтверждения, но значение сохранится. При переходе на OpenCode страницу сохранённый config подхватится. Это приемлемо — лучше чем вообще не иметь доступа к настройкам вне OpenCode. - Если callback functions (`promptForApiKey` и др.) — `const`/`let` и определены ниже top-level вызова: переместить вызов `registerMenuCommands` после их определений. Прочитать `src/index.ts` и проверить. ## Влияние на связанные компоненты - `src/config.ts` — без изменений (`registerMenuCommands` function definition не трогается, только call site в `index.ts`). - `src/insert.ts` — без изменений (`isOpencodePage()` gate остаётся для `setupUI`/`setupKeyboardShortcut`/`checkFirstRun`). - `vite.config.ts` — только `namespace` (строка 10) + `version` (строка 11). `@updateURL`/`@downloadURL`/`@icon`/`@connect` НЕ трогать. - `package.json` — только `version` (строка 3). - `AGENTS.md` — добавить секцию `## @namespace`. ## Вне scope - ❌ ADR-0008 о миграции (отдельный PR) - ❌ Вынос `setupUI`/`checkFirstRun` из gate (они зависят от DOM, оставляются) - ❌ Очистка мёртвых GitHub-ссылок в `docs/**` - ❌ Renovate config - ❌ Ограничение retry loop `init()` (отдельная задача, noted в memory gotchas) ## Критерии приемки - [ ] `vite.config.ts:10` — `namespace` = `https://github.com/slaid098/opencode-voice-dictation` (старый, как ID) - [ ] `vite.config.ts:11` + `package.json:3` = `1.0.7` (синхронно) - [ ] `vite.config.ts` — `@updateURL`/`@downloadURL`/`@icon` остаются на Forgejo (НЕ откатаны) - [ ] `src/index.ts` — `registerMenuCommands({...})` вызывается на top-level (до `init()`), НЕ внутри `init()` после gate - [ ] `src/index.ts` — `setupUI`/`setupKeyboardShortcut`/`checkFirstRun` остаются внутри `init()` после gate - [ ] `AGENTS.md` — добавлена секция `## @namespace` с правилом про идентификатор - [ ] `npm run lint` → pass - [ ] `npm run typecheck` → pass - [ ] `npm run test` → pass - [ ] `npm run build` → `dist/opencode-voice-dictation.user.js` header содержит `@namespace https://github.com/slaid098/...` + `@version 1.0.7` + `@updateURL https://git.slaid098.dev/...` - [ ] `@connect *` остался (не сужен)
Sign in to join this conversation.
No labels
bug
chore
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
slaid098/opencode-voice-dictation#4
No description provided.