27 KiB
Верстак — браузерное дополнение Firefox / Chrome / Yandex Browser
1. Назначение
Браузерное дополнение нужно потому, что большая часть работы может происходить не в локальных файлах, а в браузере:
- админки сайтов;
- CMS;
- панели клиентов;
- GitHub/Gitea/GitLab;
- webmail;
- личные кабинеты;
- документация;
- витрины сайтов;
- CRM/ERP клиентов;
- облачные панели.
Если пользователь три часа обновлял витрину сайта через web-админку, локальный Верстак может почти ничего не увидеть.
Расширение даёт Верстаку браузерные следы:
Похоже, пользователь работал с admin.romashka.ru с 14:05 до 17:12.
Домен связан с делом “Клиенты / ООО Ромашка / Сайт”.
Можно предложить запись в журнал: “обновление витрины сайта, примерно 3 часа”.
2. Главный принцип
Расширение — не шпион и не самостоятельный таймтрекер.
Оно должно:
- работать локально;
- передавать события только в локальный Верстак;
- не отправлять данные в облако;
- не читать DOM страниц;
- не читать формы;
- не читать cookies;
- не перехватывать сетевые запросы;
- не делать скриншоты;
- не сохранять полную историю браузера;
- уважать allowlist/blocklist;
- иметь pause;
- показывать пользователю, что отслеживается;
- не трекать private windows по умолчанию.
3. Что собирать
MVP-событие активности активной вкладки:
{
"type": "active_tab_interval",
"browser": "firefox|chrome|yandex",
"window_id": 1,
"tab_id": 123,
"domain": "admin.romashka.ru",
"origin": "https://admin.romashka.ru",
"url": "https://admin.romashka.ru/catalog/products",
"title": "Каталог товаров — Админка",
"started_at": "2026-05-30T14:05:00+03:00",
"ended_at": "2026-05-30T14:18:00+03:00",
"duration_seconds": 780,
"source": "browser_extension",
"private_window": false
}
Отправлять батчами:
{
"browser": "firefox",
"device_id": "browser-extension-device-id",
"events": [
{
"type": "active_tab_interval",
"domain": "admin.romashka.ru",
"origin": "https://admin.romashka.ru",
"url": "https://admin.romashka.ru/catalog",
"title": "Каталог товаров",
"started_at": "2026-05-30T14:05:00+03:00",
"ended_at": "2026-05-30T14:18:00+03:00",
"duration_seconds": 780
}
]
}
4. Что не собирать
В MVP запрещено:
- DOM;
- содержимое страниц;
- значения input/textarea;
- cookies;
- localStorage/sessionStorage страниц;
- POST bodies;
- webRequest details;
- screenshot;
- пароли;
- приватные окна без явного включения;
- полную историю браузера;
- query string с токенами.
5. Очистка URL
По умолчанию:
- query string удалять;
- fragment удалять;
- full URL хранить только если пользователь включил;
- для чувствительных доменов хранить только domain/origin.
Пример:
https://client.example.com/admin/orders?token=abc&session=123#x
Безопасный вариант:
https://client.example.com/admin/orders
Приватный вариант:
client.example.com
6. Архитектура расширения
browser-extension/
src/
background.js
popup.html
popup.js
options.html
options.js
shared/
browser-api.js
activity.js
storage.js
transport.js
url.js
manifests/
manifest.chrome.json
manifest.firefox.json
package.json
build.js
dist/
chrome/
firefox/
Yandex Browser использовать Chrome-compatible build.
7. Транспорт в Верстак
Вариант MVP: Local HTTP endpoint
Верстак поднимает локальный endpoint:
http://127.0.0.1:47731/api/browser/events
Расширение шлёт туда события через fetch.
Auth:
Authorization: Bearer <local_browser_token>
Плюсы:
- проще отлаживать;
- одинаково для Firefox/Chrome/Yandex;
- не надо регистрировать native host;
- быстрее сделать MVP.
Минусы:
- endpoint надо защищать токеном;
- bind только на 127.0.0.1;
- нужен pairing flow.
Вариант позже: Native Messaging
Плюсы:
- более официальный канал extension ↔ native app;
- не нужен HTTP endpoint.
Минусы:
- сложнее установка;
- нужно регистрировать native messaging host отдельно для Chrome/Firefox;
- тяжелее для MVP.
Решение: в MVP Local HTTP, Native Messaging оставить на будущее.
8. Pairing flow
- Пользователь открывает Верстак.
- Нажимает “Подключить браузер”.
- Верстак показывает одноразовый pairing token.
- Пользователь вводит token в extension options.
- Extension вызывает:
POST /api/browser/pair
Body:
{
"pairing_token": "123456",
"extension_id": "...",
"browser": "firefox|chrome|yandex",
"device_name": "Firefox on Work PC"
}
Response:
{
"ok": true,
"local_token": "...",
"device_id": "..."
}
- Extension сохраняет
local_tokenв local storage. - Token больше не показывается пользователю.
9. Local Browser API в Верстаке
GET /api/browser/health
POST /api/browser/pair
POST /api/browser/events
GET /api/browser/bindings
POST /api/browser/link-domain-request
POST /api/browser/pause
Health
{
"ok": true,
"app": "Verstak",
"version": "0.1.0"
}
Events
POST /api/browser/events
Authorization: Bearer <local_token>
Content-Type: application/json
Bindings
Ответ:
{
"domains": {
"admin.romashka.ru": {
"node_id": "uuid",
"title": "Клиенты / ООО Ромашка / Сайт"
}
}
}
10. Permissions
Стараться держать permissions минимальными.
Возможные:
{
"permissions": [
"tabs",
"storage",
"idle"
]
}
Host permissions:
- не начинать с агрессивного
<all_urls>, если можно; - по умолчанию режим
allowlist_only; - пользователь явно добавляет домены.
Режимы:
allowlist_only
all_except_blocked
disabled
Default: allowlist_only.
11. Popup UI
Popup:
Верстак
Status: connected / not connected / paused
Current site: admin.romashka.ru
[Track this site]
[Stop tracking this site]
[Link this site to case]
[Pause 1 hour]
[Open settings]
Today:
admin.romashka.ru — 1h 20m
git.mirv.top — 35m
12. Options UI
Настройки:
- endpoint URL;
- pairing token;
- test connection;
- tracking enabled;
- tracking mode:
- allowlist only;
- all except blocked;
- disabled;
- allowlist domains;
- blocklist domains;
- send full URL: yes/no;
- strip query string: yes/no;
- include private windows: no by default;
- idle timeout minutes;
- batch interval seconds;
- max pending events;
- debug mode;
- clear pending events;
- reset pairing.
13. Activity collector
Логика:
- Следить за active tab.
- При смене active tab закрывать текущий interval.
- При смене URL активной вкладки закрывать текущий interval и открывать новый.
- При смене active window закрывать/переключать interval.
- Если tab URL не trackable — не создавать interval.
- Если domain не в allowlist при режиме allowlist_only — не создавать interval.
- Если domain в blocklist — не создавать interval.
- Если пользователь idle — закрыть interval.
- Раз в N секунд отправить pending events в Верстак.
- Если Верстак недоступен — хранить pending events локально.
14. Idle detection
Если пользователь idle дольше заданного времени:
- закрыть текущий interval;
- не считать idle time как работу;
- при возвращении открыть новый interval для текущей вкладки.
15. Domain bindings в Верстаке
Таблица:
CREATE TABLE domain_bindings (
id TEXT PRIMARY KEY,
node_id TEXT NOT NULL REFERENCES nodes(id),
domain TEXT NOT NULL,
match_type TEXT NOT NULL DEFAULT 'exact', -- exact | suffix
created_at TEXT NOT NULL
);
Примеры:
admin.romashka.ru → Клиенты / ООО Ромашка / Сайт
*.romashka.ru → Клиенты / ООО Ромашка / Сайт
git.mirv.top/mirivlad/sshkeeper → Личные проекты / sshkeeper
MVP:
- exact domain;
- suffix match позже.
16. Worklog suggestions
Верстак группирует browser events:
- по node_id;
- по domain;
- по дню;
- intervals with gap <= 20 minutes объединяются.
Suggestion:
Похоже, работа по “ООО Ромашка / Сайт”:
admin.romashka.ru — 2ч 48м
romashka.ru/catalog — 12м
Предлагаемое время: 3ч
Основания:
- 14:05–15:30 admin.romashka.ru/catalog
- 15:40–16:55 admin.romashka.ru/products
- 17:00–17:12 romashka.ru/catalog
[Записать 3ч] [Изменить] [Игнорировать]
17. Safety requirements
- Не использовать content scripts в MVP, если не нужны.
- Не читать DOM.
- Не читать cookies.
- Не читать forms.
- Не использовать
webRequest. - Не использовать
history. - Query strip по умолчанию.
- Private windows ignore по умолчанию.
- Pause в popup.
- Allowlist mode по умолчанию.
- Blocklist есть всегда.
- Pending events ограничены.
- Endpoint только 127.0.0.1.
- Pairing token одноразовый.
- Local token не логировать.
- No external network requests.
Промпты для ИИ-кодера
Prompt 01 — Browser extension skeleton
Создай отдельный модуль `browser-extension` для проекта Верстак.
Цель:
браузерное расширение для Firefox / Chrome / Yandex Browser, которое собирает локальную активность активной вкладки и отправляет её в локальный Верстак.
Важно:
- не собирать DOM;
- не читать формы;
- не читать cookies;
- не делать screenshots;
- не использовать webRequest/history;
- не отправлять данные в интернет;
- только local endpoint `http://127.0.0.1:47731`;
- query string удалять по умолчанию;
- private windows игнорировать по умолчанию.
Структура:
- `browser-extension/src/background.js`
- `browser-extension/src/shared/browser-api.js`
- `browser-extension/src/shared/url.js`
- `browser-extension/src/shared/storage.js`
- `browser-extension/src/shared/transport.js`
- `browser-extension/src/popup.html`
- `browser-extension/src/popup.js`
- `browser-extension/src/options.html`
- `browser-extension/src/options.js`
- `browser-extension/manifests/manifest.chrome.json`
- `browser-extension/manifests/manifest.firefox.json`
- `browser-extension/package.json`
- `browser-extension/build.js`
Сделай manifests для Chrome MV3 и Firefox WebExtension.
Для Yandex Browser использовать Chrome build.
MVP skeleton должен:
- загружаться как unpacked extension в Chrome/Chromium;
- загружаться временно в Firefox через about:debugging;
- показывать popup;
- иметь options page;
- хранить настройки в browser/chrome storage local;
- иметь build script для `dist/chrome` и `dist/firefox`.
Не реализуй пока отправку активности, только skeleton и настройки.
После выполнения дай инструкции запуска.
Prompt 02 — Cross-browser API layer
Добавь compatibility layer для Chrome/Firefox.
Файл:
`src/shared/browser-api.js`
Нужно:
- использовать `globalThis.browser`, если есть;
- fallback на `globalThis.chrome`;
- promisify callback APIs, где нужно;
- методы:
- getActiveTab()
- getCurrentWindow()
- onTabActivated(handler)
- onTabUpdated(handler)
- onWindowFocusChanged(handler)
- storageGet(keys)
- storageSet(values)
- idleQueryState(minutes)
- onIdleStateChanged(handler)
Правила:
- popup/options/background не должны напрямую использовать chrome/browser;
- весь доступ к browser APIs через browserApi;
- не добавлять тяжёлые зависимости без необходимости.
Acceptance criteria:
- работает в Chrome;
- работает в Firefox;
- storage работает;
- active tab можно получить.
Prompt 03 — URL sanitizer
Реализуй URL sanitizer.
Файл:
`src/shared/url.js`
Функции:
- parseUrl(rawUrl)
- sanitizeUrl(rawUrl, options)
- getDomain(rawUrl)
- getOrigin(rawUrl)
- isTrackableUrl(rawUrl)
- isBlockedUrl(rawUrl, settings)
- isAllowedUrl(rawUrl, settings)
Правила:
- не трекать chrome://, about:, moz-extension:, chrome-extension:, edge:, opera:, file:// по умолчанию;
- query string удалять по умолчанию;
- hash удалять по умолчанию;
- full URL хранить только если setting `sendFullUrl=true`;
- режимы:
- allowlist_only;
- all_except_blocked;
- disabled.
Добавь простые unit tests.
Покажи примеры sanitize для URL с token/session/query.
Prompt 04 — Settings storage
Реализуй настройки расширения.
Файл:
`src/shared/storage.js`
Настройки:
- endpointUrl default `http://127.0.0.1:47731`;
- localToken;
- deviceId;
- pairedAt;
- trackingEnabled;
- trackingMode: allowlist_only | all_except_blocked | disabled;
- allowlistDomains;
- blocklistDomains;
- sendFullUrl default false;
- stripQueryString default true;
- includePrivateWindows default false;
- idleTimeoutMinutes default 5;
- batchIntervalSeconds default 30;
- maxPendingEvents default 1000;
- debugMode default false;
- pendingEvents array.
Нужно:
- getSettings()
- saveSettings(partial)
- addPendingEvent(event)
- getPendingEvents()
- removePendingEvents(ids)
- clearPendingEvents()
Acceptance criteria:
- options page может менять настройки;
- pending events переживают перезапуск браузера;
- maxPendingEvents соблюдается.
Prompt 05 — Active tab interval collector
Реализуй сбор интервалов активности активной вкладки.
Файлы:
- `src/background.js`
- `src/shared/activity.js`
Логика:
- при старте background получить active tab;
- при tabs.onActivated закрыть старый interval и открыть новый;
- при tabs.onUpdated для активной вкладки, если URL изменился, закрыть старый interval и открыть новый;
- при windows.onFocusChanged закрывать/переключать interval;
- если URL не trackable — interval не создавать;
- если домен не проходит allowlist/blocklist — interval не создавать;
- interval хранить:
- id
- domain
- origin
- sanitized URL
- title
- started_at
- ended_at
- duration_seconds
- private_window false/true if available
Пока не отправляй на сервер, складывай pending events в storage.
Popup должен в debug mode показывать последние 10 pending events.
Prompt 06 — Local HTTP transport
Добавь отправку событий в локальный Верстак.
Файл:
`src/shared/transport.js`
Endpoint:
`POST http://127.0.0.1:47731/api/browser/events`
Header:
`Authorization: Bearer <localToken>`
Логика:
- раз в batchIntervalSeconds отправлять pending events;
- если нет localToken — не отправлять;
- если endpoint недоступен — оставить pending;
- если 401 — status auth_failed;
- если success — удалить отправленные events;
- body:
{
browser,
device_id,
events
}
Popup должен показывать:
- connected / disconnected / auth failed;
- pending count;
- last successful send.
Acceptance criteria:
- с mock server events отправляются;
- при server down events остаются pending;
- query string не отправляется.
Prompt 07 — Pairing flow
Добавь pairing flow.
Options page:
- endpoint URL;
- pairing token;
- button “Pair with Verstak”;
- button “Test connection”;
- status area.
API:
GET /api/browser/health
POST /api/browser/pair
Pair request:
{
"pairing_token": "...",
"extension_id": "...",
"browser": "chrome|firefox|yandex",
"device_name": "..."
}
Pair response:
{
"ok": true,
"local_token": "...",
"device_id": "..."
}
После pairing:
- сохранить localToken;
- сохранить deviceId;
- сохранить pairedAt;
- очистить pairingToken из UI;
- popup показывает connected.
Не логировать токены.
Prompt 08 — Popup UI
Сделай popup UI.
Показывать:
- статус подключения;
- текущий сайт;
- текущий режим tracking;
- pending events count;
- today summary по доменам.
Кнопки:
- Track this site;
- Stop tracking this site;
- Link this site to case;
- Pause 1 hour;
- Resume;
- Open settings.
Track this site:
- добавляет текущий domain в allowlist.
Stop tracking this site:
- добавляет domain в blocklist или удаляет из allowlist.
Link this site to case:
- отправляет в Верстак:
POST /api/browser/link-domain-request
{
"domain": "...",
"url": "...",
"title": "..."
}
Если Верстак недоступен, показать понятную ошибку.
Prompt 09 — Options UI
Сделай options page.
Настройки:
- endpoint URL;
- pairing/test connection;
- tracking enabled;
- tracking mode:
- allowlist_only
- all_except_blocked
- disabled
- allowlist domains;
- blocklist domains;
- send full URL yes/no;
- strip query string yes/no;
- include private windows yes/no, default false;
- idle timeout minutes;
- batch interval seconds;
- max pending events;
- debug mode.
Функции:
- validate domain;
- import/export settings JSON;
- clear pending events;
- reset pairing;
- show current extension version.
Сделай простой чистый UI без framework.
Prompt 10 — Idle detection
Добавь idle detection.
Использовать browser/chrome idle API через compatibility layer.
Логика:
- если пользователь idle больше idleTimeoutMinutes, закрыть текущий interval;
- пока idle, новые intervals не создавать;
- при возвращении active открыть interval для текущей вкладки;
- idle time не должен попадать в duration.
Popup debug показывает current idle state.
Acceptance criteria:
- idle event закрывает interval;
- duration не растёт во время idle.
Prompt 11 — Verstak desktop receiver
В основном приложении Верстак добавь local browser activity receiver.
Нужно:
- локальный HTTP server на 127.0.0.1:47731;
- endpoints:
- GET /api/browser/health
- POST /api/browser/pair
- POST /api/browser/events
- POST /api/browser/link-domain-request
- local pairing tokens;
- local browser tokens;
- таблица browser_devices;
- сохранение browser events в activity_events.
Безопасность:
- bind только на 127.0.0.1;
- Bearer token required после pairing;
- pairing token одноразовый;
- не логировать token;
- ограничить body size;
- reject requests from non-loopback interface.
Acceptance criteria:
- extension может pair;
- extension может отправить events;
- events появляются в activity_events;
- invalid token rejected.
Prompt 12 — Domain bindings in Verstak
Добавь привязку доменов к делам в Верстаке.
Нужно:
- таблица domain_bindings:
- id
- node_id
- domain
- match_type exact|suffix
- created_at
- UI в карточке дела:
- список связанных доменов;
- добавить домен;
- удалить домен.
- обработка link-domain-request:
- показать запрос в Неразобранном;
- пользователь выбирает дело;
- создаётся binding.
Matching:
- exact first;
- suffix for *.example.com later;
- unmatched browser events попадают в unresolved activity.
Acceptance criteria:
- admin.romashka.ru привязан к делу;
- события этого домена видны в активности дела;
- неизвестные домены попадают в Неразобранное.
Prompt 13 — Browser activity worklog suggestions
Добавь построение worklog suggestions из browser activity.
Логика:
- брать browser activity events за день;
- группировать по node_id/domain;
- объединять интервалы с gap <= 20 минут;
- суммировать duration;
- создавать suggestion:
- node_id
- date
- started_at
- ended_at
- suggested_minutes
- evidence_json
- summary template.
Summary template:
“Работа в браузере по связанному сайту: {domains}.”
UI:
- Today показывает suggestions;
- Case Activity tab показывает suggestions;
- кнопки:
- Записать;
- Изменить;
- Игнорировать.
Acceptance criteria:
- 3 часа активности admin.romashka.ru создают suggestion;
- пользователь может записать suggestion в worklog;
- evidence показывает домены, titles, intervals.
Prompt 14 — Firefox packaging
Подготовь Firefox build.
Нужно:
- npm run build:firefox;
- использовать manifests/manifest.firefox.json;
- output dist/firefox;
- инструкция:
1. открыть about:debugging;
2. This Firefox;
3. Load Temporary Add-on;
4. выбрать manifest.json.
Проверить:
- popup работает;
- options page работает;
- active tab tracking работает;
- local HTTP events отправляются.
Не публиковать в AMO на этом этапе.
Prompt 15 — Chrome/Yandex packaging
Подготовь Chrome/Yandex build.
Нужно:
- npm run build:chrome;
- output dist/chrome;
- инструкция для Chrome:
1. открыть chrome://extensions;
2. включить Developer mode;
3. Load unpacked;
4. выбрать dist/chrome.
- инструкция для Yandex Browser:
- использовать Chrome-compatible build;
- проверить установку как unpacked, если доступно в dev mode;
- иначе оставить ручную инструкцию для совместимой установки.
Проверить:
- popup работает;
- options page работает;
- active tab tracking работает;
- local HTTP events отправляются.
Не публиковать в Chrome Web Store на этом этапе.
Prompt 16 — Privacy/security audit
Проведи privacy/security audit расширения.
Проверь:
- нет сбора DOM;
- нет content scripts без необходимости;
- нет cookies permission;
- нет webRequest permission;
- нет history permission;
- query string strip по умолчанию;
- private windows ignore по умолчанию;
- pause работает;
- allowlist mode работает;
- blocklist работает;
- local token не логируется;
- pending events ограничены;
- endpoint только 127.0.0.1;
- pairing token одноразовый;
- нет external network requests;
- errors не содержат секретов.
Выдай список найденных проблем и исправь критичные.
MVP scope
Входит
- Chrome-compatible build;
- Firefox build;
- Yandex via Chrome-compatible build;
- popup;
- options;
- pairing;
- active tab intervals;
- allowlist/blocklist;
- query stripping;
- local HTTP transport;
- pending queue;
- idle detection;
- domain linking request.
Не входит
- content scripts;
- DOM reading;
- form tracking;
- network request tracking;
- screenshots;
- cloud sync from extension;
- AI summaries;
- password detection;
- automatic client detection by page content;
- browser history import.
Первый полезный сценарий
- Пользователь создаёт дело “ООО Ромашка / Сайт”.
- Добавляет домен
admin.romashka.ru. - Устанавливает extension.
- Pair extension with Verstak.
- Работает в админке сайта.
- Extension отправляет intervals.
- Верстак показывает:
Похоже, работа по “ООО Ромашка / Сайт”:
admin.romashka.ru — 2ч 48м
romashka.ru/catalog — 12м
[Записать 3ч] [Изменить] [Игнорировать]