Документация: удалены superpowers-планы, добавлен README.ru.md с шапкой
This commit is contained in:
parent
a8c58ff584
commit
06eff98538
16
README.md
16
README.md
|
|
@ -1,5 +1,19 @@
|
|||
<div align="center">
|
||||
|
||||
# Verstak Browser Extension
|
||||
|
||||
### Send pages, selections, links and files from the browser into Verstak.
|
||||
|
||||
**English** · [Русский](README.ru.md)
|
||||
|
||||
[](https://github.com/mirivlad/verstak-browser-extension/releases)
|
||||

|
||||
[](LICENSE)
|
||||
|
||||
</div>
|
||||
|
||||
> **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
|
||||
|
|
|
|||
|
|
@ -0,0 +1,107 @@
|
|||
<div align="center">
|
||||
|
||||
# Расширение Верстака для браузера
|
||||
|
||||
### Отправляйте страницы, выделенный текст, ссылки и файлы из браузера в Верстак.
|
||||
|
||||
[English](README.md) · **Русский**
|
||||
|
||||
[](https://github.com/mirivlad/verstak-browser-extension/releases)
|
||||

|
||||
[](LICENSE)
|
||||
|
||||
</div>
|
||||
|
||||
> **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).
|
||||
|
|
@ -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
|
||||
<label id="language-label" for="language-select">Language</label>
|
||||
<select id="language-select">
|
||||
<option value="system">System</option>
|
||||
<option value="en">English</option>
|
||||
<option value="ru">Русский</option>
|
||||
</select>
|
||||
```
|
||||
|
||||
- [ ] **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"
|
||||
```
|
||||
|
|
@ -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 <addon-id> <version> <asset-name> <output-path>`.
|
||||
|
||||
- [ ] **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-<version>.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-<version>.xpi` and `updates.json` to release tag
|
||||
`v<version>` 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.
|
||||
|
|
@ -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 `<select>` with
|
||||
`system`, `en`, and `ru` options. Selecting a value:
|
||||
|
||||
1. updates all popup chrome immediately without reopening the popup;
|
||||
2. sends the full settings patch to background storage;
|
||||
3. preserves receiver URL and pairing token values;
|
||||
4. shows the localized saved/error state.
|
||||
|
||||
The `<html lang>` attribute is updated to the resolved locale. User data,
|
||||
receiver URLs, tokens, file names, and backend-provided error text are not
|
||||
translated.
|
||||
|
||||
## Background and Context Menus
|
||||
|
||||
`DEFAULT_SETTINGS` gains `language: "system"`. Context-menu creation resolves
|
||||
the effective locale from stored settings and creates localized page,
|
||||
selection, and link titles.
|
||||
|
||||
After a language setting is saved, background recreates the context menus so
|
||||
the new language applies without reinstalling or reloading the extension.
|
||||
Failure to access the i18n API falls back to English and must not block capture
|
||||
or settings operations.
|
||||
|
||||
## Error Handling
|
||||
|
||||
- Missing catalog keys fall back to English, then to the provided literal/key.
|
||||
- Invalid stored language values behave as `system`.
|
||||
- Catalog or browser-language detection failure falls back to English.
|
||||
- Existing receiver and capture errors continue to be shown; known extension
|
||||
validation/status chrome around them is localized.
|
||||
|
||||
## Verification
|
||||
|
||||
Tests are written before production changes and cover:
|
||||
|
||||
- `ru-*` and non-Russian system-language resolution;
|
||||
- explicit language override and invalid-preference normalization;
|
||||
- English/Russian catalog parity and interpolation;
|
||||
- popup language persistence without losing receiver settings;
|
||||
- live popup translation after selecting Russian/English;
|
||||
- localized online/offline/unknown and capture status messages;
|
||||
- localized context-menu creation and recreation after language changes;
|
||||
- build contents for both Chromium and Firefox.
|
||||
|
||||
Final verification commands:
|
||||
|
||||
```bash
|
||||
npm test
|
||||
npm run build
|
||||
```
|
||||
|
||||
Manual installation or store signing is outside this milestone.
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
# GitHub Releases for Firefox Updates
|
||||
|
||||
## Decision
|
||||
|
||||
Firefox XPI releases are published as ordinary public GitHub Releases in
|
||||
`mirivlad/verstak-browser-extension`. During the alpha phase every such release
|
||||
is marked as the repository's latest release, including alpha versions. There
|
||||
are no other Verstak users whose automatic updates need a separate stable
|
||||
channel yet.
|
||||
|
||||
## Update protocol
|
||||
|
||||
The Firefox manifest uses this stable endpoint:
|
||||
|
||||
```text
|
||||
https://github.com/mirivlad/verstak-browser-extension/releases/latest/download/updates.json
|
||||
```
|
||||
|
||||
Each release uploads two assets:
|
||||
|
||||
- `verstak-firefox-<version>.xpi`, signed by Mozilla as an unlisted addon;
|
||||
- `updates.json`, whose `update_link` points to that same release tag's XPI
|
||||
asset.
|
||||
|
||||
For version `2.0.3`, the versioned link is:
|
||||
|
||||
```text
|
||||
https://github.com/mirivlad/verstak-browser-extension/releases/download/v2.0.3/verstak-firefox-2.0.3.xpi
|
||||
```
|
||||
|
||||
The stable `latest/download` endpoint lets already installed extensions discover
|
||||
the next version without changing the manifest on each release.
|
||||
|
||||
## Release commands
|
||||
|
||||
`npm run release:firefox` remains the local sign-and-package command. A new
|
||||
explicit `npm run publish:firefox` command runs it, verifies `gh`
|
||||
authentication, creates or reuses the `v<manifest-version>` GitHub Release,
|
||||
and uploads the XPI plus `updates.json`. Re-running the command replaces only
|
||||
those two assets, so a failed network upload can be retried safely.
|
||||
|
||||
Publishing requires the existing AMO credentials used for signing and a
|
||||
GitHub-authenticated `gh` CLI. It does not use the invalid `mirv.top` update
|
||||
endpoint.
|
||||
|
||||
## Bootstrap
|
||||
|
||||
There is currently no GitHub Release in this repository. After the code change,
|
||||
the first `v2.0.3` signed release must be published before shipping a manifest
|
||||
that points to `latest/download/updates.json`; until then that URL is a 404.
|
||||
The current package, Chromium manifest and Firefox manifest all use version
|
||||
`2.0.3`, so the initial tag and XPI asset name are unambiguous.
|
||||
|
||||
## Documentation
|
||||
|
||||
The README links users to the GitHub Releases page for downloading the signed
|
||||
Firefox XPI and documents that releases are currently the auto-update channel.
|
||||
|
||||
## Verification
|
||||
|
||||
- add a focused script test for versioned GitHub URLs and generated
|
||||
`updates.json`;
|
||||
- run the existing extension test suite and build;
|
||||
- run shell syntax validation for the publish script;
|
||||
- verify a published release's assets and `latest/download/updates.json` with
|
||||
unauthenticated HTTPS requests.
|
||||
Loading…
Reference in New Issue