# Верстак — браузерное дополнение Firefox / Chrome / Yandex Browser ## 1. Назначение Браузерное дополнение нужно потому, что большая часть работы может происходить не в локальных файлах, а в браузере: - админки сайтов; - CMS; - панели клиентов; - GitHub/Gitea/GitLab; - webmail; - личные кабинеты; - документация; - витрины сайтов; - CRM/ERP клиентов; - облачные панели. Если пользователь три часа обновлял витрину сайта через web-админку, локальный Верстак может почти ничего не увидеть. Расширение даёт Верстаку браузерные следы: ```text Похоже, пользователь работал с admin.romashka.ru с 14:05 до 17:12. Домен связан с делом “Клиенты / ООО Ромашка / Сайт”. Можно предложить запись в журнал: “обновление витрины сайта, примерно 3 часа”. ``` ## 2. Главный принцип Расширение — не шпион и не самостоятельный таймтрекер. Оно должно: - работать локально; - передавать события только в локальный Верстак; - не отправлять данные в облако; - не читать DOM страниц; - не читать формы; - не читать cookies; - не перехватывать сетевые запросы; - не делать скриншоты; - не сохранять полную историю браузера; - уважать allowlist/blocklist; - иметь pause; - показывать пользователю, что отслеживается; - не трекать private windows по умолчанию. ## 3. Что собирать MVP-событие активности активной вкладки: ```json { "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 } ``` Отправлять батчами: ```json { "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. Пример: ```text https://client.example.com/admin/orders?token=abc&session=123#x ``` Безопасный вариант: ```text https://client.example.com/admin/orders ``` Приватный вариант: ```text client.example.com ``` ## 6. Архитектура расширения ```text 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: ```text http://127.0.0.1:47731/api/browser/events ``` Расширение шлёт туда события через `fetch`. Auth: ```http Authorization: Bearer ``` Плюсы: - проще отлаживать; - одинаково для 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 1. Пользователь открывает Верстак. 2. Нажимает “Подключить браузер”. 3. Верстак показывает одноразовый pairing token. 4. Пользователь вводит token в extension options. 5. Extension вызывает: ```http POST /api/browser/pair ``` Body: ```json { "pairing_token": "123456", "extension_id": "...", "browser": "firefox|chrome|yandex", "device_name": "Firefox on Work PC" } ``` Response: ```json { "ok": true, "local_token": "...", "device_id": "..." } ``` 6. Extension сохраняет `local_token` в local storage. 7. Token больше не показывается пользователю. ## 9. Local Browser API в Верстаке ```http 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 ```json { "ok": true, "app": "Verstak", "version": "0.1.0" } ``` ### Events ```http POST /api/browser/events Authorization: Bearer Content-Type: application/json ``` ### Bindings Ответ: ```json { "domains": { "admin.romashka.ru": { "node_id": "uuid", "title": "Клиенты / ООО Ромашка / Сайт" } } } ``` ## 10. Permissions Стараться держать permissions минимальными. Возможные: ```json { "permissions": [ "tabs", "storage", "idle" ] } ``` Host permissions: - не начинать с агрессивного ``, если можно; - по умолчанию режим `allowlist_only`; - пользователь явно добавляет домены. Режимы: ```text allowlist_only all_except_blocked disabled ``` Default: `allowlist_only`. ## 11. Popup UI Popup: ```text Верстак 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 Логика: 1. Следить за active tab. 2. При смене active tab закрывать текущий interval. 3. При смене URL активной вкладки закрывать текущий interval и открывать новый. 4. При смене active window закрывать/переключать interval. 5. Если tab URL не trackable — не создавать interval. 6. Если domain не в allowlist при режиме allowlist_only — не создавать interval. 7. Если domain в blocklist — не создавать interval. 8. Если пользователь idle — закрыть interval. 9. Раз в N секунд отправить pending events в Верстак. 10. Если Верстак недоступен — хранить pending events локально. ## 14. Idle detection Если пользователь idle дольше заданного времени: - закрыть текущий interval; - не считать idle time как работу; - при возвращении открыть новый interval для текущей вкладки. ## 15. Domain bindings в Верстаке Таблица: ```sql 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 ); ``` Примеры: ```text 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: ```text Похоже, работа по “ООО Ромашка / Сайт”: 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 1. Не использовать content scripts в MVP, если не нужны. 2. Не читать DOM. 3. Не читать cookies. 4. Не читать forms. 5. Не использовать `webRequest`. 6. Не использовать `history`. 7. Query strip по умолчанию. 8. Private windows ignore по умолчанию. 9. Pause в popup. 10. Allowlist mode по умолчанию. 11. Blocklist есть всегда. 12. Pending events ограничены. 13. Endpoint только 127.0.0.1. 14. Pairing token одноразовый. 15. Local token не логировать. 16. No external network requests. --- # Промпты для ИИ-кодера ## Prompt 01 — Browser extension skeleton ```markdown Создай отдельный модуль `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 ```markdown Добавь 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 ```markdown Реализуй 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 ```markdown Реализуй настройки расширения. Файл: `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 ```markdown Реализуй сбор интервалов активности активной вкладки. Файлы: - `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 ```markdown Добавь отправку событий в локальный Верстак. Файл: `src/shared/transport.js` Endpoint: `POST http://127.0.0.1:47731/api/browser/events` Header: `Authorization: Bearer ` Логика: - раз в 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 ```markdown Добавь 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 ```markdown Сделай 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 ```markdown Сделай 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 ```markdown Добавь 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 ```markdown В основном приложении Верстак добавь 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 ```markdown Добавь привязку доменов к делам в Верстаке. Нужно: - таблица 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 ```markdown Добавь построение 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 ```markdown Подготовь 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 ```markdown Подготовь 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 ```markdown Проведи 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. ## Первый полезный сценарий 1. Пользователь создаёт дело “ООО Ромашка / Сайт”. 2. Добавляет домен `admin.romashka.ru`. 3. Устанавливает extension. 4. Pair extension with Verstak. 5. Работает в админке сайта. 6. Extension отправляет intervals. 7. Верстак показывает: ```text Похоже, работа по “ООО Ромашка / Сайт”: admin.romashka.ru — 2ч 48м romashka.ru/catalog — 12м [Записать 3ч] [Изменить] [Игнорировать] ```