diff --git a/README.md b/README.md index 57203d5..5571233 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,19 @@ +
+ # Verstak Browser Extension +### Send pages, selections, links and files from the browser into Verstak. + +**English** · [Русский](README.ru.md) + +[![Release](https://img.shields.io/github/v/release/mirivlad/verstak-browser-extension?include_prereleases\&label=release)](https://github.com/mirivlad/verstak-browser-extension/releases) +![Status](https://img.shields.io/badge/status-alpha-orange) +[![License](https://img.shields.io/github/license/mirivlad/verstak-browser-extension)](LICENSE) + +
+ +> **Alpha software.** Use with a matching Verstak Desktop alpha release. + Verstak Browser Extension captures pages, selected text, links, and selected files and sends them to a local Verstak browser inbox receiver. @@ -7,8 +21,6 @@ The extension does not know Notes, Files, Activity, or Journal internals. It only sends capture events through the public local receiver protocol. If the receiver is offline, captures stay in the extension pending queue. -> **Alpha software.** Use with a matching Verstak Desktop alpha release. - ## Build ```bash diff --git a/README.ru.md b/README.ru.md new file mode 100644 index 0000000..bd75e9a --- /dev/null +++ b/README.ru.md @@ -0,0 +1,107 @@ +
+ +# Расширение Верстака для браузера + +### Отправляйте страницы, выделенный текст, ссылки и файлы из браузера в Верстак. + +[English](README.md) · **Русский** + +[![Релиз](https://img.shields.io/github/v/release/mirivlad/verstak-browser-extension?include_prereleases\&label=release)](https://github.com/mirivlad/verstak-browser-extension/releases) +![Статус](https://img.shields.io/badge/status-alpha-orange) +[![Лицензия](https://img.shields.io/github/license/mirivlad/verstak-browser-extension)](LICENSE) + +
+ +> **Alpha-версия.** Используйте с совместимой alpha-сборкой Verstak Desktop. + +Расширение Верстака захватывает страницы, выделенный текст, ссылки и выбранные +файлы и отправляет их в локальный приёмник «Входящие из браузера». + +Расширение не знает внутреннего устройства Заметок, Файлов, Активности или +Журнала. Оно только отправляет события захвата через публичный протокол +локального приёмника. Если приёмник недоступен, захваты остаются в очереди +расширения. + +## Сборка + +```bash +npm ci +npm test +npm run build +``` + +Результат сборки: + +- `dist/chromium` +- `dist/firefox` + +Загрузите `dist/chromium` как распакованное расширение в браузеры на базе +Chromium или временно загрузите `dist/firefox` в Firefox при разработке. + +## Пассивный учёт активности по доменам + +Пассивный учёт **выключен по умолчанию**. При первом использовании расширение +объясняет, что оно записывает; пользователь должен явно включить его в +настройках. + +Когда учёт включён, расширение наблюдает только за активной вкладкой и +отправляет ограниченные интервалы как каноническое доменное имя плюс +длительность. Оно никогда не отправляет URL-пути, заголовки страниц, текст +страниц, выделенный текст, нажатия клавиш, историю посещений или время +неактивных вкладок. + +Ручные действия «Отправить страницу», «Отправить выделенный текст», «Отправить +ссылку» и «Отправить файл» отделены от пассивного учёта. Они создают записи во +«Входящих из браузера», а не дела или записи журнала. + +## Загрузка и обновления в Firefox + +Подписанный XPI публикуется на странице +[GitHub Releases](https://github.com/mirivlad/verstak-browser-extension/releases). +Скачайте Firefox-ассет `verstak-firefox-<версия>.xpi` из последнего релиза и +откройте его в Firefox для установки. + +После первого публичного релиза установленные копии проверяют GitHub Releases +на наличие обновлений через ассет `updates.json` релиза. + +## Сопряжение + +1. В Verstak Desktop откройте настройки «Входящих из браузера». +2. Скопируйте адрес приёмника и токен сопряжения. +3. Вставьте оба значения в настройки расширения и сохраните. + +Смена токена в Desktop делает недействительным значение, сохранённое в расширении. + +## Язык + +Настройки расширения предлагают выбор языка: `System / English / Russian`. +`System` следует языку интерфейса браузера: русская локаль выбирает русский, +остальные — английский. + +Расширение и desktop-приложение хранят выбор языка независимо. Изменение одного +не меняет другой. + +## Протокол локального приёмника + +Эндпоинт по умолчанию: + +```text +POST http://127.0.0.1:47731/api/browser-inbox/v1/captures +``` + +Заголовки: + +- `Content-Type: application/json` +- `X-Verstak-Receiver-Token: <токен>` — обязателен, когда приёмник в paired mode + +Поддерживаемые виды захвата: + +- `page` — страница (url, title, domain) +- `selection` — выделенный текст +- `link` — ссылка (url, опционально текст) +- `file` — файл (имя, base64 или текст до 8 МБ) + +## Лицензия + +Copyright © 2026 Verstak contributors. Распространяется на условиях +[GNU AGPLv3 или новее](LICENSE). diff --git a/docs/superpowers/plans/2026-07-11-browser-extension-localization.md b/docs/superpowers/plans/2026-07-11-browser-extension-localization.md deleted file mode 100644 index e4668cf..0000000 --- a/docs/superpowers/plans/2026-07-11-browser-extension-localization.md +++ /dev/null @@ -1,328 +0,0 @@ -# Browser Extension Localization Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** Add persisted `System / English / Russian` language selection to the browser extension, localizing popup chrome and context menus in Chromium and Firefox. - -**Architecture:** A framework-independent `shared/i18n.js` resolves preferences and loads extension-owned JSON catalogs. Popup and background both consume it; background owns persistence and context-menu recreation, while popup performs immediate DOM translation and sends settings updates. - -**Tech Stack:** WebExtension APIs, plain ES5-compatible JavaScript, JSON catalogs, Node `assert`/`vm` tests, existing Node build script. - -## Global Constraints - -- Store only `system`, `en`, or `ru`; missing/invalid values normalize to `system`. -- `ru` and `ru-*` browser UI locales resolve to Russian; all other system locales resolve to English. -- Keep the extension language independent from desktop settings. -- Add no runtime dependency and no manifest-localization scope. -- Never translate receiver URLs, tokens, file names, user content, or raw receiver error text. -- Preserve receiver URL/token values when language changes. -- Both browser distributions must contain `i18n.js`, `locales/en.json`, and `locales/ru.json`. - ---- - -### Task 1: Shared Locale Runtime and Catalog Contract - -**Files:** - -- Create: `shared/i18n.js` -- Create: `shared/locales/en.json` -- Create: `shared/locales/ru.json` -- Create: `scripts/test-i18n.js` -- Modify: `package.json` - -**Interfaces:** - -- Produces: `globalThis.VerstakBrowserI18n.normalizePreference(value)`. -- Produces: `globalThis.VerstakBrowserI18n.resolveLocale(preference, systemLocale)`. -- Produces: `globalThis.VerstakBrowserI18n.loadCatalogs(loadJSON)` returning `Promise<{en,ru}>`. -- Produces: `globalThis.VerstakBrowserI18n.createTranslator(catalogs, locale)` returning `t(key, params, fallback)`. - -- [ ] **Step 1: Write the failing runtime/catalog test** - -Create `scripts/test-i18n.js` that loads `shared/i18n.js` in `vm`, reads both JSON catalogs, and asserts: - -```js -assert.strictEqual(i18n.normalizePreference('de'), 'system'); -assert.strictEqual(i18n.resolveLocale('system', 'ru-RU'), 'ru'); -assert.strictEqual(i18n.resolveLocale('system', 'uk-UA'), 'en'); -assert.strictEqual(i18n.resolveLocale('en', 'ru-RU'), 'en'); -assert.deepStrictEqual(Object.keys(ru).sort(), Object.keys(en).sort()); -assert.ok(Object.values(en).every((value) => typeof value === 'string')); -assert.ok(Object.values(ru).every((value) => typeof value === 'string')); -assert.strictEqual(i18n.createTranslator({ en, ru }, 'ru')('status.queued'), 'В очереди до запуска Верстака'); -assert.strictEqual(i18n.createTranslator({ en, ru }, 'en')('error.value', { error: 'offline' }), 'Error: offline'); -``` - -- [ ] **Step 2: Run the test and confirm RED** - -Run: `node scripts/test-i18n.js` - -Expected: FAIL because `shared/i18n.js` and catalogs do not exist. - -- [ ] **Step 3: Implement the minimal shared runtime** - -Implement an IIFE exporting the four functions. `loadCatalogs(loadJSON)` must call `loadJSON('en')` and `loadJSON('ru')`; translation lookup order is selected catalog, English catalog, explicit fallback, key. Interpolation replaces `{name}` only when `params` owns that property. - -Catalogs must define identical keys for: - -```text -popup.subtitle -label.receiver -label.pending -label.url -label.file -label.receiverUrl -label.pairingToken -label.language -action.sendPage -action.sendFile -action.retryPending -action.save -hint.contextMenu -receiver.online -receiver.offline -receiver.unknown -language.system -language.en -language.ru -status.sending -status.queued -status.done -status.readingFile -status.saved -error.chooseFile -error.fileTooLarge -error.invalidReceiverUrl -error.value -context.sendPage -context.sendSelection -context.sendLink -``` - -- [ ] **Step 4: Add the test to `npm test` and verify GREEN** - -Set: - -```json -"test": "node scripts/test-protocol.js && node scripts/test-i18n.js && node scripts/test-popup-settings.js" -``` - -Run: `node scripts/test-i18n.js` - -Expected: `browser extension localization runtime tests passed`. - -- [ ] **Step 5: Commit the shared contract** - -```bash -git add shared/i18n.js shared/locales scripts/test-i18n.js package.json -git commit -m "feat: add browser extension localization runtime" -``` - ---- - -### Task 2: Persisted Language and Localized Context Menus - -**Files:** - -- Modify: `shared/background.js` -- Modify: `chromium/manifest.json` -- Modify: `firefox/manifest.json` -- Create: `scripts/test-background-i18n.js` - -**Interfaces:** - -- Consumes: `VerstakBrowserI18n.loadCatalogs`, `normalizePreference`, `resolveLocale`, `createTranslator`. -- Produces: persisted `settings.language` and context-menu recreation after `saveSettings`. - -- [ ] **Step 1: Write the failing background test** - -Load `protocol.js`, `queue.js`, `i18n.js`, and `background.js` into `vm` with fake storage/context-menu APIs. Seed `settings.language = 'ru'`, trigger `runtime.onInstalled`, and assert: - -```js -assert.deepStrictEqual(menuTitles, [ - 'Отправить страницу в Верстак', - 'Отправить выделение в Верстак', - 'Отправить ссылку в Верстак', -]); -``` - -Send a `saveSettings` message with `{ receiverUrl, receiverToken, language: 'en' }`, then assert stored receiver fields are unchanged and menu titles become English. - -- [ ] **Step 2: Run the test and confirm RED** - -Run: `node scripts/test-background-i18n.js` - -Expected: FAIL because background defaults contain no language and menu titles are literal English. - -- [ ] **Step 3: Implement background localization** - -Add `language: 'system'` to `DEFAULT_SETTINGS`. Normalize it in `getSettings`/`saveSettings`. Load catalogs with: - -```js -function loadLocaleCatalogs() { - return localeCatalogs || i18n.loadCatalogs(function (locale) { - return fetch(ext.runtime.getURL('locales/' + locale + '.json')).then(function (response) { - if (!response.ok) throw new Error('catalog load failed: ' + locale); - return response.json(); - }); - }); -} -``` - -Make `setupContextMenus()` resolve settings plus catalogs, choose -`ext.i18n.getUILanguage()` with English fallback, and create the three menu -titles via `t('context.sendPage')`, `t('context.sendSelection')`, and -`t('context.sendLink')`. Call it after language settings are saved. - -- [ ] **Step 4: Load `i18n.js` before background code in both manifests** - -- Chromium service worker remains bundled by the build concatenation order. -- Firefox `background.scripts` becomes `protocol.js`, `api.js`, `queue.js`, `i18n.js`, `background.js`. - -Also append `node scripts/test-background-i18n.js` to the `test` script in -`package.json` now that the test file exists. - -- [ ] **Step 5: Verify and commit** - -Run: `node scripts/test-background-i18n.js` - -Expected: `browser extension background localization tests passed`. - -```bash -git add shared/background.js chromium/manifest.json firefox/manifest.json scripts/test-background-i18n.js -git commit -m "feat: localize browser extension context menus" -``` - ---- - -### Task 3: Popup Language Selector and Live Translation - -**Files:** - -- Modify: `shared/popup/popup.html` -- Modify: `shared/popup/popup.js` -- Modify: `shared/popup/popup.css` -- Modify: `scripts/test-popup-settings.js` - -**Interfaces:** - -- Consumes: state `settings.language` from background and the shared translator. -- Produces: immediate popup translation plus a `saveSettings` message containing receiver URL, token, and language. - -- [ ] **Step 1: Extend the popup test and confirm RED** - -Add fake elements for every localized ID plus `language-select`, mock catalog fetches, `browser.i18n.getUILanguage()`, and `document.documentElement.lang`. Assert initial `system` with `ru-RU` renders Russian. Change select value to `en`, dispatch `change`, flush promises, then assert: - -```js -assert.strictEqual(elements['capture-page'].textContent, 'Send Page'); -assert.strictEqual(elements['receiver-state'].textContent, 'Unknown'); -assert.strictEqual(document.documentElement.lang, 'en'); -assert.strictEqual(savedSettings.language, 'en'); -assert.strictEqual(savedSettings.receiverUrl, initialState.settings.receiverUrl); -assert.strictEqual(savedSettings.receiverToken, initialState.settings.receiverToken); -``` - -- [ ] **Step 2: Add popup markup** - -Load `../i18n.js` before `popup.js`, give all static user-visible elements stable IDs, and add: - -```html - - -``` - -- [ ] **Step 3: Implement asynchronous popup initialization and live switch** - -Load both catalogs through `ext.runtime.getURL`, resolve the locale from saved -preference plus browser UI language, and apply all static text through one -`applyLocale(preference)` function. `render(state)` localizes -online/offline/unknown. Known statuses and validations use catalog keys; raw -error messages are wrapped only when needed by `error.value`. - -The select `change` handler must call `applyLocale` before awaiting background -storage, then send all three settings fields. The existing Save button also -sends the selected language. - -- [ ] **Step 4: Style the select consistently and verify GREEN** - -Share the existing input box model/colors with `select` and keep the popup -minimum width unchanged. - -Run: `node scripts/test-popup-settings.js` - -Expected: `browser extension popup localization/settings tests passed`. - -- [ ] **Step 5: Commit** - -```bash -git add shared/popup scripts/test-popup-settings.js -git commit -m "feat: add browser extension language selector" -``` - ---- - -### Task 4: Distribution Packaging, Documentation, and Full Verification - -**Files:** - -- Modify: `scripts/build-extension.js` -- Modify: `README.md` -- Test: `scripts/test-i18n.js` -- Test: `scripts/test-background-i18n.js` -- Test: `scripts/test-popup-settings.js` - -**Interfaces:** - -- Consumes: shared runtime/catalogs and updated manifests. -- Produces: complete `dist/chromium` and `dist/firefox` extension directories. - -- [ ] **Step 1: Add failing build-content assertions** - -Extend `scripts/test-i18n.js` to run after build when `dist` exists and assert -both targets contain: - -```text -i18n.js -locales/en.json -locales/ru.json -``` - -- [ ] **Step 2: Update the build** - -For Chromium, concatenate `shared/i18n.js` before `shared/background.js` and -also copy it as `dist/chromium/i18n.js` for popup use. For Firefox, copy -`shared/i18n.js` beside background scripts. Copy both JSON catalogs to each -target's `locales/` directory. - -- [ ] **Step 3: Document the selector** - -Add a README section stating that popup settings provide persisted -`System / English / Russian`, that System uses browser UI language, and that -extension/desktop preferences are independent. - -- [ ] **Step 4: Run complete verification** - -```bash -npm test -npm run build -npm test -git diff --check -``` - -Expected: - -- protocol, runtime, popup, and background tests pass; -- both browser distributions build; -- post-build content assertions pass; -- no whitespace errors. - -- [ ] **Step 5: Commit packaging and docs** - -```bash -git add scripts/build-extension.js README.md scripts/test-i18n.js -git commit -m "build: package browser extension locale catalogs" -``` diff --git a/docs/superpowers/plans/2026-07-12-github-firefox-updates.md b/docs/superpowers/plans/2026-07-12-github-firefox-updates.md deleted file mode 100644 index a839958..0000000 --- a/docs/superpowers/plans/2026-07-12-github-firefox-updates.md +++ /dev/null @@ -1,329 +0,0 @@ -# GitHub Firefox Updates Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** Publish signed Firefox XPI releases to GitHub Releases and let installed alpha extensions discover them through a GitHub-hosted update manifest. - -**Architecture:** Firefox keeps one immutable `update_url` targeting the current GitHub Release's `updates.json` asset. A small Node helper owns the tag, asset URL and update-manifest formatting so it has focused unit tests. The existing local signing command remains local; an explicit publishing command uploads its two artifacts to the public GitHub release. - -**Tech Stack:** Manifest V2 JSON, Node.js CommonJS tests, Bash, `web-ext`, GitHub CLI (`gh`). - -## Global Constraints - -- Publish repository: `mirivlad/verstak-browser-extension`. -- Firefox addon ID: `verstak-bridge@verstak.app`. -- Initial GitHub release: `v2.0.3`; package and both browser manifests are version `2.0.3`. -- Every alpha GitHub release is ordinary/latest, not a prerelease. -- XPI remains Mozilla-signed as an unlisted/self-distributed addon before upload. -- `npm run release:firefox` remains a local sign-and-package action. -- `npm run publish:firefox` is the only command allowed to create or modify a GitHub Release. -- Generated `release/` output remains untracked. - ---- - -### Task 1: Make GitHub update metadata testable - -**Files:** -- Create: `scripts/firefox-github-release.js` -- Create: `scripts/test-firefox-github-release.js` -- Modify: `package.json:8-15` - -**Interfaces:** -- Produces `releaseTag(version)`, `releaseAssetURL(version, assetName)`, and `updateManifest(addonID, version, assetName)` from `scripts/firefox-github-release.js`. -- `updateManifest` returns the object written as `updates.json` and uses the fixed GitHub repository. -- Later shell scripts invoke `node scripts/firefox-github-release.js write-updates `. - -- [ ] **Step 1: Write the failing metadata test** - -Create `scripts/test-firefox-github-release.js` with: - -```js -const assert = require('assert'); -const release = require('./firefox-github-release'); - -const version = '2.0.3'; -const asset = 'verstak-firefox-2.0.3.xpi'; -assert.equal(release.releaseTag(version), 'v2.0.3'); -assert.equal( - release.releaseAssetURL(version, asset), - 'https://github.com/mirivlad/verstak-browser-extension/releases/download/v2.0.3/verstak-firefox-2.0.3.xpi', -); -assert.deepEqual(release.updateManifest('verstak-bridge@verstak.app', version, asset), { - addons: { - 'verstak-bridge@verstak.app': { - updates: [{ - version: '2.0.3', - update_link: 'https://github.com/mirivlad/verstak-browser-extension/releases/download/v2.0.3/verstak-firefox-2.0.3.xpi', - }], - }, - }, -}); -console.log('Firefox GitHub release metadata tests passed'); -``` - -- [ ] **Step 2: Run the focused test and verify it fails** - -Run: `node scripts/test-firefox-github-release.js` - -Expected: failure because `scripts/firefox-github-release.js` does not yet exist. - -- [ ] **Step 3: Implement the metadata helper** - -Create `scripts/firefox-github-release.js` with these exact exports and CLI: - -```js -const fs = require('fs'); -const REPOSITORY = 'mirivlad/verstak-browser-extension'; - -function releaseTag(version) { return `v${version}`; } -function releaseAssetURL(version, assetName) { - return `https://github.com/${REPOSITORY}/releases/download/${releaseTag(version)}/${assetName}`; -} -function updateManifest(addonID, version, assetName) { - return { addons: { [addonID]: { updates: [{ version, update_link: releaseAssetURL(version, assetName) }] } } }; -} - -if (require.main === module) { - const [, , command, addonID, version, assetName, outputPath] = process.argv; - if (command !== 'write-updates' || !addonID || !version || !assetName || !outputPath) process.exitCode = 2; - else fs.writeFileSync(outputPath, `${JSON.stringify(updateManifest(addonID, version, assetName), null, 2)}\n`); -} - -module.exports = { REPOSITORY, releaseTag, releaseAssetURL, updateManifest }; -``` - -- [ ] **Step 4: Register and run the test** - -Append `node scripts/test-firefox-github-release.js` to the existing `test` -script in `package.json`, then run: - -```bash -npm test -``` - -Expected: all existing tests and `Firefox GitHub release metadata tests passed`. - -- [ ] **Step 5: Commit and push** - -```bash -git add package.json scripts/firefox-github-release.js scripts/test-firefox-github-release.js -git commit -m "test: cover Firefox GitHub release metadata" -git push origin main -``` - -### Task 2: Point Firefox and local release artifacts at GitHub - -**Files:** -- Modify: `firefox/manifest.json:8-17` -- Modify: `scripts/release-firefox-xpi.sh:29-70` -- Test: `scripts/test-firefox-github-release.js` - -**Interfaces:** -- Firefox reads `https://github.com/mirivlad/verstak-browser-extension/releases/latest/download/updates.json`. -- `release-firefox-xpi.sh` invokes the Task 1 helper to write a versioned update manifest. -- It continues producing `release/firefox/verstak-firefox-.xpi` and `release/firefox/updates.json` locally. - -- [ ] **Step 1: Extend the failing test with the stable manifest URL** - -Add this assertion to `scripts/test-firefox-github-release.js` before its final -success message: - -```js -const firefoxManifest = require('../firefox/manifest.json'); -assert.equal( - firefoxManifest.browser_specific_settings.gecko.update_url, - 'https://github.com/mirivlad/verstak-browser-extension/releases/latest/download/updates.json', -); -``` - -- [ ] **Step 2: Run the focused test and verify it fails** - -Run: `node scripts/test-firefox-github-release.js` - -Expected: assertion failure because the manifest still uses `mirv.top`. - -- [ ] **Step 3: Replace the update endpoint and JSON writer** - -In `firefox/manifest.json`, replace only the Gecko `update_url` value with: - -```json -"update_url": "https://github.com/mirivlad/verstak-browser-extension/releases/latest/download/updates.json" -``` - -In `scripts/release-firefox-xpi.sh`, remove `UPDATE_BASE_URL` and replace the -heredoc that writes `updates.json` with: - -```bash -node scripts/firefox-github-release.js write-updates \ - "$ADDON_ID" "$VERSION" "$RELEASE_XPI" "$RELEASE_DIR/updates.json" -``` - -- [ ] **Step 4: Run targeted checks** - -Run: - -```bash -node scripts/test-firefox-github-release.js -bash -n scripts/release-firefox-xpi.sh -npm test -npm run build -``` - -Expected: all commands succeed; `dist/firefox/manifest.json` contains the -GitHub update URL after the build. - -- [ ] **Step 5: Commit and push** - -```bash -git add firefox/manifest.json scripts/release-firefox-xpi.sh scripts/test-firefox-github-release.js -git commit -m "feat: host Firefox updates on GitHub Releases" -git push origin main -``` - -### Task 3: Add explicit GitHub publishing and public instructions - -**Files:** -- Create: `scripts/publish-firefox-github-release.sh` -- Modify: `package.json:8-15` -- Modify: `README.md:42-68` -- Test: `scripts/publish-firefox-github-release.sh` - -**Interfaces:** -- `npm run publish:firefox` calls `scripts/publish-firefox-github-release.sh`. -- The script runs the existing local release command, then uploads - `verstak-firefox-.xpi` and `updates.json` to release tag - `v` in `mirivlad/verstak-browser-extension`. -- A pre-existing tag receives asset replacement through `gh release upload --clobber`. - -- [ ] **Step 1: Write the shell-contract test** - -Create a Node test that reads the publish script and asserts the mandatory -commands are present: - -```js -const assert = require('assert'); -const fs = require('fs'); -const source = fs.readFileSync('scripts/publish-firefox-github-release.sh', 'utf8'); -assert.match(source, /gh auth status/); -assert.match(source, /gh release create/); -assert.match(source, /gh release upload/); -assert.match(source, /--clobber/); -assert.match(source, /--latest/); -``` - -Place those assertions in `scripts/test-firefox-github-release.js` and run the -test. Expected: it fails before the publish script exists. - -- [ ] **Step 2: Implement an explicit idempotent publisher** - -Create `scripts/publish-firefox-github-release.sh` with this behavior: - -1. Set `ROOT_DIR`, `cd` to it and run `gh auth status`. -2. Run `./scripts/release-firefox-xpi.sh`. -3. Read `VERSION` from `dist/firefox/manifest.json`, set `TAG="v${VERSION}"`, - `REPOSITORY="mirivlad/verstak-browser-extension"`, and locate the two local - release assets by exact names. -4. Run `gh release view "$TAG" --repo "$REPOSITORY"`; when it is absent, run - `gh release create "$TAG" "$XPI" "$UPDATES" --repo "$REPOSITORY" --title - "Verstak Browser Extension $VERSION" --generate-notes --latest --target - "$(git rev-parse HEAD)"`. -5. When the release already exists, run `gh release upload "$TAG" "$XPI" - "$UPDATES" --repo "$REPOSITORY" --clobber`. -6. Print the GitHub release URL with `gh release view "$TAG" --repo - "$REPOSITORY" --json url --jq .url`. - -- [ ] **Step 3: Register the command and document it** - -Add to `package.json`: - -```json -"publish:firefox": "./scripts/publish-firefox-github-release.sh" -``` - -Rewrite the Firefox Release README section so it documents: - -- the public [GitHub Releases](https://github.com/mirivlad/verstak-browser-extension/releases) page; -- `VERSTAK_BROWSER_ENV=/path/to/.env npm run publish:firefox`; -- that signing remains unlisted with Mozilla but distribution and automatic - updates use GitHub Release assets; -- that the first `v2.0.3` publish bootstraps the `latest/download/updates.json` - endpoint. - -- [ ] **Step 4: Run checks without publishing** - -Run: - -```bash -bash -n scripts/publish-firefox-github-release.sh -node scripts/test-firefox-github-release.js -npm test -npm run build -git diff --check -``` - -Expected: all commands succeed. Do not invoke `npm run publish:firefox` in this -task; it creates a public GitHub Release and needs available AMO credentials. - -- [ ] **Step 5: Commit and push** - -```bash -git add README.md package.json scripts/publish-firefox-github-release.sh scripts/test-firefox-github-release.js -git commit -m "feat: publish Firefox releases to GitHub" -git push origin main -``` - -### Task 4: Bootstrap and verify the first public update release - -**Files:** -- Generated: `release/firefox/verstak-firefox-2.0.3.xpi` (untracked) -- Generated: `release/firefox/updates.json` (untracked) - -**Interfaces:** -- Consumes the Task 3 `npm run publish:firefox` command and valid AMO signing credentials. -- Produces GitHub Release tag `v2.0.3` and public XPI/update-manifest assets. - -- [ ] **Step 1: Verify release credentials before write operations** - -Run: - -```bash -gh auth status -test -n "${VERSTAK_BROWSER_ENV:-}" || test -f .env -``` - -Expected: authenticated GitHub CLI and a configured environment file path for -the existing AMO signing command. - -- [ ] **Step 2: Publish the first release** - -Run: - -```bash -npm run publish:firefox -``` - -Expected: Mozilla signing succeeds, GitHub Release `v2.0.3` is latest, and it -contains `verstak-firefox-2.0.3.xpi` plus `updates.json`. - -- [ ] **Step 3: Verify public delivery** - -Run: - -```bash -gh release view v2.0.3 --repo mirivlad/verstak-browser-extension --json isLatest,url,assets -curl -fsSL https://github.com/mirivlad/verstak-browser-extension/releases/latest/download/updates.json -``` - -Expected: the release is latest and the JSON contains addon ID -`verstak-bridge@verstak.app`, version `2.0.3`, and the versioned GitHub XPI -asset URL. - -- [ ] **Step 4: Record release verification without committing assets** - -Run: - -```bash -git status --short -``` - -Expected: no generated `release/` assets are staged or committed. diff --git a/docs/superpowers/specs/2026-07-11-browser-extension-localization-design.md b/docs/superpowers/specs/2026-07-11-browser-extension-localization-design.md deleted file mode 100644 index 41352da..0000000 --- a/docs/superpowers/specs/2026-07-11-browser-extension-localization-design.md +++ /dev/null @@ -1,114 +0,0 @@ -# Browser Extension Localization Design - -## Goal - -Add English and Russian localization to the Verstak browser extension with a -persisted manual `System / English / Russian` language selector. The extension -remains independent from the desktop application's language preference. - -## Scope - -The change covers: - -- all user-visible popup labels, hints, statuses, validation messages, and - receiver-state labels; -- the three browser context-menu titles; -- persisted language selection; -- Chromium and Firefox build packaging; -- automated tests for locale resolution, persistence, live popup updates, and - context-menu localization. - -Manifest name and description localization is not part of this milestone. The -existing English manifest values remain the fallback in both browser builds. - -## Locale Model - -The stored setting is named `language` and accepts only: - -- `system` (default); -- `en`; -- `ru`. - -`system` resolves the browser UI language through -`browser.i18n.getUILanguage()` / `chrome.i18n.getUILanguage()`. Popup code may -fall back to `navigator.language` when the extension i18n API is unavailable. -`ru` and `ru-*` resolve to Russian; every other value resolves to English. -Invalid or missing stored preferences normalize to `system`. - -## Catalogs and Runtime - -The extension owns framework-independent JSON catalogs: - -```text -shared/locales/en.json -shared/locales/ru.json -``` - -A small shared runtime provides: - -- preference normalization; -- system-locale resolution; -- key lookup with English and literal fallback; -- `{parameter}` interpolation. - -The runtime has no dependency on desktop, the plugin SDK, DOM APIs, or external -packages. It is loaded by both popup and background scripts. - -Both catalogs must contain identical string-only keys. The build copies them -into `dist/chromium/locales` and `dist/firefox/locales` together with the shared -runtime. - -## Popup Behavior - -The existing settings section gains a labeled language `