From dc2c4285b570502dcb59fb15a8da053ae8aac940 Mon Sep 17 00:00:00 2001 From: Dmitrii Kharitonov Date: Tue, 23 Jun 2026 13:33:27 +0200 Subject: [PATCH 1/8] docs: plan extension popup compliance --- .../solution/extension-popup-compliance.md | 167 ++++++++++++++++++ package.json | 2 +- scripts/release-package.json | 2 +- 3 files changed, 169 insertions(+), 2 deletions(-) create mode 100644 docs/internal/solution/extension-popup-compliance.md diff --git a/docs/internal/solution/extension-popup-compliance.md b/docs/internal/solution/extension-popup-compliance.md new file mode 100644 index 0000000..98f7c50 --- /dev/null +++ b/docs/internal/solution/extension-popup-compliance.md @@ -0,0 +1,167 @@ +--- +title: Extension Popup Compliance and Presentation +--- + +Solution note for making the bproxy Chrome extension popup clearer, more transparent, and easier to review against Chrome Web Store expectations. + +**Decisions that constrain this:** [ADR-001](../decisions.md#adr-001-default-instrumentation-strategy--read-mode) (programmatic injection only), [ADR-011](../decisions.md#adr-011-extension-token-bootstrap-via-popup-driven-pairing) (popup-driven pairing), [ADR-016](../decisions.md#adr-016-web_accessible_resources-default-deny) (no WAR by default), [ADR-017](../decisions.md#adr-017-sensoractuator-boundary) (extension is sensor/actuator only), [ADR-024](../decisions.md#adr-024-no-arbitrary-page-eval-and-no-scroll-target-inference) (no eval / no scroll inference), and [ADR-025](../decisions.md#adr-025-security-scanner-findings-are-remediated-in-code) (security findings remediated in code). + +## Inputs + +- Current popup screenshots show a minimal pairing form with title, pairing-code input, pair button, and one status sentence. It is functional, but too terse for a user or reviewer to understand scope, authorship, version compatibility, license, or where to read more. +- Current extension spec: [public extension solution](../../public/solution/extension.md). +- Current public documentation URL: . +- Current root package metadata: version `0.9.0`, license `MIT`, description `Human-in-the-loop browser proxy for AI agents.` +- Current protocol version source: `shared/src/version.ts` exports `PROTOCOL_VERSION = 2`. +- Current license file: [`LICENSE`](../../../LICENSE). User-facing credits use `Dim Kharitonov`. + +## Chrome Web Store requirements observed + +Chrome's policy pages use five themes that apply directly to this popup and listing. + +| Theme | Requirement | Source | +|---|---|---| +| Be honest | Extension functionality should be clearly disclosed to users, with no surprises. | [Program Policies — Chrome Web Store Principles](https://developer.chrome.com/docs/webstore/program-policies) | +| Be useful / quality | Extensions should have a narrow, understandable purpose and a respectful user experience. Persistent UI should help the current task and avoid distraction. | [Quality guidelines](https://developer.chrome.com/docs/webstore/program-policies/quality-guidelines) | +| Minimum functionality | Extension must provide real utility, not just launch/link to another site. | [Minimum Functionality](https://developer.chrome.com/docs/webstore/program-policies/minimum-functionality) | +| Permission minimality | Request the narrowest permissions needed for implemented features; do not future-proof permissions. | [Use of Permissions](https://developer.chrome.com/docs/webstore/program-policies/permissions) | +| Data transparency and handling | Disclose collection/use/sharing of user data, and handle auth/user data securely. | [Disclosure Requirements](https://developer.chrome.com/docs/webstore/program-policies/disclosure-requirements), [Handling Requirements](https://developer.chrome.com/docs/webstore/program-policies/data-handling) | +| Listing metadata | Provide a detailed description, graphics, homepage URL, support URL, and content rating/details. | [Complete your listing information](https://developer.chrome.com/docs/webstore/cws-dashboard-listing) | + +## Presentation goal + +The popup should answer four questions immediately: + +1. **What is this?** The Chrome-side piece of bproxy: a local, human-in-the-loop browser bridge for AI agents and agentic systems. +2. **What does it do?** Pairs this Chrome profile with the local bproxy daemon so an agent can request explicit browser actions through the user's real browser session. +3. **Who made it and under what terms?** Created by Dim Kharitonov, MIT licensed. +4. **Where can I verify details?** Documentation, license, and credits links. + +Avoid decorative product-dashboard language. Do **not** introduce badges, chips, gradients, icon clutter, or trend-driven UI elements. The popup should feel like a small browser utility: restrained, readable, explicit. + +## Proposed popup content + +```text +bproxy +Human-in-the-loop browser bridge for AI agents. + +Status: Not paired + +Pairing code +[ VQE7-KNBM ] +[ Pair extension ] + +Run `bproxy service start` and paste the one-time code shown by the daemon. + +Extension 0.9.0 · Protocol 2 +Documentation · License · Credits +Created by Dim Kharitonov · MIT +``` + +When paired: + +```text +Status: Paired with local daemon +``` + +When pairing fails, preserve the existing specific error messages and codes, but keep them below the form and use plain text color treatment only. + +## Required metadata + +The popup should render these values from source-of-truth constants where practical: + +| Field | Value / source | Notes | +|---|---|---| +| Extension version | `VERSION` from `@bproxy/shared` or WXT/manifest package version | Must match release package version. | +| Protocol version | `PROTOCOL_VERSION` from `@bproxy/shared` | Useful for daemon/extension mismatch support. | +| Documentation | `https://dimdasci.github.io/bproxy/` | Use a normal external link. | +| License | MIT, linked to repository license or docs license page | Link target can be GitHub `LICENSE` until a docs page exists. | +| Credits | Created by Dim Kharitonov | Link to repository or credits section/page. | + +## Copy requirements + +Use copy that is transparent without over-explaining internals: + +- Product subtitle: `Human-in-the-loop browser bridge for AI agents.` +- Purpose text: `This extension pairs Chrome with your local bproxy daemon so AI agents and agentic systems can request explicit browser actions while you stay in control.` +- Human-control sentence: `You handle logins, CAPTCHAs, consent screens, and final submits.` +- Pairing help: `Run bproxy service start and paste the one-time code shown by the daemon.` +- Success text: `Paired with local daemon. You can close this popup.` +- Footer: `Created by Dim Kharitonov · MIT` + +Avoid claims such as "undetectable", "bypass", "stealth", "anti-bot", or anything suggesting circumvention. The public framing should remain human-in-the-loop browser control, not evasion marketing. + +## Layout requirements + +- Use one compact column, about `340px` to `380px` wide. +- Keep native form semantics: real `form`, `label`, `input`, `button`, and `output aria-live="polite"`. +- Use text hierarchy, spacing, and a thin divider for clarity. +- Links live in a simple footer: `Documentation · License · Credits`. +- No badge/chip components. +- No external fonts or remote assets. +- No options page is required for this increment. + +## Compliance notes for manifest and listing + +The popup cannot carry all compliance burden. Store listing and privacy fields should also disclose: + +- bproxy is a companion extension for a local daemon/CLI. +- It can read page text/structure and perform explicit browser actions only after pairing. +- It stores pairing bootstrap material in Chrome extension storage. +- It communicates with `ws://127.0.0.1:9615/ws` / `http://127.0.0.1:9615/pair/claim` only for local daemon pairing/control. +- Broad host access exists because the user/agent may work on arbitrary pages; this must be justified in listing/privacy text. +- Permission rationale: + - `tabs`: route actions to tabs and manage user-requested tab actions. + - `scripting`: programmatically inject the isolated content script on first command. + - `webNavigation`: track top-level navigation state for session/page identity. + - `alarms`: background service-worker keepalive/reconnect scheduling. + - `storage`: persist bootstrap token and bounded session diagnostics. + - ``: support user-selected pages across the web. + +The manifest description should stay clear and non-promotional. Candidate wording: + +> Companion extension for bproxy. Connects Chrome to a local bproxy daemon for explicit human-in-the-loop browser actions. + +## Session and tab visibility check + +The extension does **not** currently know authoritative bproxy session state. + +What it has today: + +- Forwarded daemon requests include `session` and `target.tabId`, so the extension trace can record which sessions/tabs were involved in actions that actually reached the extension. +- `chrome.storage.session` contains operational caches (`session:dedupe`, `session:injectedTabs`, `session:trace`, and `session:pins`), but these are not a session registry. +- The action badge only shows connection state: disconnected, connecting, connected, or error. + +What it deliberately does not have today: + +- `session.create`, `session.list`, `session.bind`, `session.unbind`, `session.resume`, `session.close`, `tab.list`, `debug.last`, and `debug.status` are daemon-local and excluded from extension forwarding. +- The daemon owns generated session ids, logical tab handles, tab ownership, pause state, nick scoping, and the session→Chrome-tab map. +- The extension cannot safely reconstruct active sessions by enumerating Chrome tabs. That would leak operator-opened tabs and violate the daemon-owned logical-tab boundary. +- The popup has no daemon bearer token and no nick; it only stores the extension bootstrap token used for the WebSocket. + +Conclusion: the popup can honestly show pairing/connection state, but it cannot currently show "active sessions" or "tabs per session" as an authoritative value. + +Decision: **discard the dashboard request**. Do not add session counts, tab counts, per-session lists, recent-activity panels, or an operator status surface to the extension popup. Keeping the extension simple preserves the daemon-owned session boundary and avoids creating an unscoped operator API surface that would conflict with [ADR-030](../decisions.md#adr-030-agent-nickname-session-scoping). + +If session visibility becomes necessary later, it must start with a separate ADR/design for a daemon-supplied operator summary. It is explicitly out of scope for this popup polish. + +## Implementation plan + +1. Update `extension/src/entrypoints/popup/index.html` styles and markup only; keep the existing TypeScript flow in `main.ts` thin. +2. Import `VERSION` and `PROTOCOL_VERSION` in popup DOM code and render them into footer fields. +3. Add external links using `target="_blank"` and `rel="noreferrer"`. +4. Keep existing pairing validation/error handling in `pairing.ts` unchanged unless tests require DOM-selector updates. +5. Do not add session/tab counts, recent activity, or dashboard UI; this request is intentionally discarded. +6. Add/adjust popup tests to assert visible metadata and stable user-facing copy. +7. Confirm `pnpm --filter @bproxy/extension test` and relevant typecheck pass. + +## Acceptance criteria + +- Popup clearly states what bproxy is before the user pairs. +- Popup shows extension version and protocol version. +- Popup links to documentation, license, and credits. +- Popup identifies creator and MIT license in plain text. +- Popup does not show active session counts, tab counts, recent activity, or dashboard-like status. +- Pairing success/error behavior remains unchanged. +- No strategy, selector repair, modal solving, fallback chains, or page interaction logic is added to the popup or extension. +- Manifest/listing copy remains honest and does not imply bypassing anti-bot systems. diff --git a/package.json b/package.json index 21debc4..c8a67fb 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "private": true, "type": "module", "version": "0.9.0", - "description": "Browser proxy for code agents.", + "description": "Human-in-the-loop browser proxy for AI agents.", "license": "MIT", "packageManager": "pnpm@9.15.0", "scripts": { diff --git a/scripts/release-package.json b/scripts/release-package.json index 3d2c56e..44a1b1f 100644 --- a/scripts/release-package.json +++ b/scripts/release-package.json @@ -1,7 +1,7 @@ { "name": "@dimdasci/bproxy", "version": "VERSION_PLACEHOLDER", - "description": "Browser proxy for code agents — control your real Chrome from CLI.", + "description": "Human-in-the-loop browser proxy for AI agents — use your real Chrome session via a localhost daemon and extension.", "license": "MIT", "homepage": "https://dimdasci.github.io/bproxy/", "bugs": { From ca115834dd4656ecf4577fb07a76c72c6694f29a Mon Sep 17 00:00:00 2001 From: Dmitrii Kharitonov Date: Tue, 23 Jun 2026 14:11:28 +0200 Subject: [PATCH 2/8] docs: add CWS compliance requirements and privacy policy page - Add missing Chrome Web Store requirements to extension-popup-compliance.md: Limited Use (not applicable to bproxy), Listing Requirements (mandatory assets), Privacy Fields Consistency, Code Readability (MV3). - Add store listing assets plan with cable.svg icon, hero image derivatives, GitHub Issues as support URL. - Clarify that bproxy does not collect user data and Limited Use disclosure is not needed. - Add docs/public/privacy.md: plain-English privacy page for CWS dashboard privacy policy URL field. --- .../solution/extension-popup-compliance.md | 84 ++++++++++++++++++- docs/public/privacy.md | 36 ++++++++ 2 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 docs/public/privacy.md diff --git a/docs/internal/solution/extension-popup-compliance.md b/docs/internal/solution/extension-popup-compliance.md index 98f7c50..cdb9889 100644 --- a/docs/internal/solution/extension-popup-compliance.md +++ b/docs/internal/solution/extension-popup-compliance.md @@ -26,7 +26,10 @@ Chrome's policy pages use five themes that apply directly to this popup and list | Minimum functionality | Extension must provide real utility, not just launch/link to another site. | [Minimum Functionality](https://developer.chrome.com/docs/webstore/program-policies/minimum-functionality) | | Permission minimality | Request the narrowest permissions needed for implemented features; do not future-proof permissions. | [Use of Permissions](https://developer.chrome.com/docs/webstore/program-policies/permissions) | | Data transparency and handling | Disclose collection/use/sharing of user data, and handle auth/user data securely. | [Disclosure Requirements](https://developer.chrome.com/docs/webstore/program-policies/disclosure-requirements), [Handling Requirements](https://developer.chrome.com/docs/webstore/program-policies/data-handling) | -| Listing metadata | Provide a detailed description, graphics, homepage URL, support URL, and content rating/details. | [Complete your listing information](https://developer.chrome.com/docs/webstore/cws-dashboard-listing) | +| Limited Use | Use of collected data must be limited to the disclosed single purpose. No transfer to third parties except for providing/improving that purpose, legal compliance, or security. An affirmative compliance statement must appear on a website belonging to the extension — but this applies specifically to extensions that access Google user account data via OAuth/Google APIs. Extensions that only use Chrome platform APIs (tabs, scripting, storage, etc.) for local functionality and do not collect or transmit user data are not subject to this requirement. | [Program Policies — Limited Use](https://developer.chrome.com/docs/webstore/program-policies/policies#limited_use) | +| Listing requirements (mandatory) | Submissions missing a description, icon, or screenshots are **rejected**. Privacy field declarations must be accurate and consistent with the privacy policy and actual extension behavior. | [Program Policies — Listing Requirements](https://developer.chrome.com/docs/webstore/program-policies/policies#listing_requirements) | +| Listing metadata (quality) | Provide a detailed description, graphics, homepage URL, support URL, and content rating/details. | [Complete your listing information](https://developer.chrome.com/docs/webstore/cws-dashboard-listing), [Creating a great listing page](https://developer.chrome.com/docs/webstore/best-listing) | +| Code readability (MV3) | Full functionality must be easily discernible from submitted code. No obfuscation, no remote logic execution. Minification is allowed. | [Program Policies — Code Readability Requirements](https://developer.chrome.com/docs/webstore/program-policies/policies#code_readability_requirements) | ## Presentation goal @@ -118,10 +121,89 @@ The popup cannot carry all compliance burden. Store listing and privacy fields s - `storage`: persist bootstrap token and bounded session diagnostics. - ``: support user-selected pages across the web. +### Limited Use — does not apply to bproxy + +The Limited Use affirmative statement requirement applies to extensions that access Google user account data via OAuth/Google service APIs (Gmail, Drive, Calendar, etc.). bproxy does not use any Google account APIs. It uses only Chrome extension platform APIs (`tabs`, `scripting`, `webNavigation`, `storage`, `alarms`) for local functionality. + +The extension does not collect user data: + +- Page content is read on demand and passed to a localhost daemon on the same machine — nothing leaves the user's device. +- The only persisted value is the pairing bootstrap token in `chrome.storage.local`. +- No browsing history, page content, or user activity is stored or transmitted externally. + +In the CWS privacy fields, the correct declaration is: **"This extension does not collect user data."** No Limited Use disclosure page is needed. + +### Privacy policy page + +The CWS dashboard requires a privacy policy URL. The page should be published on the docs site at `https://dimdasci.github.io/bproxy/privacy/` and state in plain English: + +- The extension does not collect, store, or transmit any user data. +- It reads page content only when an agent sends a command, and passes it to a daemon running on the same machine (`127.0.0.1`). +- Nothing leaves the user's device. There is no remote server, no analytics, no telemetry. +- The only value the extension persists is a pairing token in Chrome's local storage, used to authenticate the connection to the local daemon. +- The extension communicates exclusively with `localhost:9615`. It never contacts any external service. + +Keep it short. No legal boilerplate. No "we may update this policy" padding. Just the facts about how it works and why there is nothing to disclose. + +### Listing mandatory assets + +The store will **reject** submissions that are missing any of: + +- A non-blank detailed description. +- A 128×128 px store icon. +- At least one 1280×800 px (or 640×400 px) screenshot. + +Screenshots should show the popup in both unpaired and paired states. A third screenshot may show a terminal with `bproxy service start` output to illustrate the pairing flow. + +### Privacy fields consistency + +The CWS developer dashboard privacy fields must: + +- Accurately declare what user data is collected (page text/structure read on demand, pairing token stored locally). +- State that data is not transferred to any third party. +- Be consistent with the privacy policy text and actual extension behavior. + +If privacy field declarations contradict the privacy policy or observed behavior, the extension may be removed from the store. + +### Code readability (MV3) + +bproxy already satisfies this requirement: + +- Source maps are preserved in production output (`sourcemap: true` in `wxt.config.ts`). +- No obfuscation; code is minified only. +- No remote logic execution — all functionality is self-contained in the extension package. +- No `eval()`, no ` -

bproxy pairing

-
- - - -
- +
+

+ + bproxy +

+

Human-in-the-loop browser bridge for AI agents.

+

+
+ +
+
+ + + +
+

Run bproxy service start and paste the one-time code shown by the daemon.

+ +
+ + diff --git a/extension/src/entrypoints/popup/main.ts b/extension/src/entrypoints/popup/main.ts index 25df41a..14dcde1 100644 --- a/extension/src/entrypoints/popup/main.ts +++ b/extension/src/entrypoints/popup/main.ts @@ -1,4 +1,5 @@ -import { bootstrapItem } from "../../background/storage"; +import { PROTOCOL_VERSION, VERSION } from "@bproxy/shared"; +import { bootstrapItem, type PairingBootstrap } from "../../background/storage"; import { type PairingErrorCode, type PairingResult, runPairing } from "./pairing"; // Thin DOM wiring for the popup. Real flow logic lives in small helpers: @@ -23,6 +24,17 @@ const STATUS_FRIENDLY: Record = { PAIR_NOTIFY_FAILED: "Paired, but failed to wake the background worker. Try reopening the popup.", }; +export interface ConnectionStatusViewModel { + text: string; + tone: "muted" | "ok"; + submitLabel: string; +} + +interface PopupInitDeps { + storage: { getValue(): Promise }; + now: () => number; +} + function $(id: string): T { const el = document.getElementById(id); if (!el) throw new Error(`#${id} not found in popup DOM`); @@ -30,14 +42,84 @@ function $(id: string): T { } function setStatus(state: "idle" | "pending" | "success" | "error", text: string): void { - const status = $("status"); + const status = $("status"); status.dataset["state"] = state; status.textContent = text; } +export function formatVersionInfo( + version: unknown = VERSION, + protocolVersion: unknown = PROTOCOL_VERSION, +): string { + const versionText = typeof version === "string" ? version : ""; + const protocolText = + typeof protocolVersion === "number" || typeof protocolVersion === "string" + ? String(protocolVersion) + : ""; + const extensionPart = `Extension ${versionText}`.trimEnd(); + const protocolPart = `Protocol ${protocolText}`.trimEnd(); + return `${extensionPart} · ${protocolPart}`; +} + +export function getConnectionStatusViewModel( + bootstrap: PairingBootstrap | null, + now: number, +): ConnectionStatusViewModel { + if ( + bootstrap && + typeof bootstrap.extensionToken === "string" && + bootstrap.extensionToken.length > 0 && + typeof bootstrap.expiresAt === "number" && + bootstrap.expiresAt > now + ) { + return { + text: "Paired with local daemon", + tone: "ok", + submitLabel: "Re-pair with new code", + }; + } + return { + text: "Not paired", + tone: "muted", + submitLabel: "Pair extension", + }; +} + +function renderVersionInfo(text: string): void { + $("version-info").textContent = text; +} + +function createStatusDot(tone: ConnectionStatusViewModel["tone"]): SVGSVGElement { + const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); + svg.setAttribute("aria-hidden", "true"); + svg.setAttribute("width", "8"); + svg.setAttribute("height", "8"); + svg.setAttribute("viewBox", "0 0 8 8"); + + const circle = document.createElementNS("http://www.w3.org/2000/svg", "circle"); + circle.setAttribute("cx", "4"); + circle.setAttribute("cy", "4"); + circle.setAttribute("r", "4"); + circle.setAttribute("fill", tone === "ok" ? "var(--c-ok)" : "var(--c-muted)"); + svg.append(circle); + + return svg; +} + +function renderConnectionStatus(model: ConnectionStatusViewModel): void { + const status = $("connection-status"); + status.replaceChildren(createStatusDot(model.tone), document.createTextNode(model.text)); + $("submit").textContent = model.submitLabel; +} + function renderResult(result: PairingResult): void { if (result.ok) { - setStatus("success", "Paired. You can close this popup."); + renderConnectionStatus({ + text: "Paired with local daemon", + tone: "ok", + submitLabel: "Re-pair with new code", + }); + setStatus("success", "Paired with local daemon. You can close this popup."); return; } const friendly = STATUS_FRIENDLY[result.code]; @@ -75,9 +157,23 @@ async function onSubmit(ev: SubmitEvent): Promise { } } -document.addEventListener("DOMContentLoaded", () => { - $("pair-form").addEventListener("submit", (ev) => { - void onSubmit(ev); +export async function initializePopup( + deps: PopupInitDeps = { + storage: bootstrapItem, + now: () => Date.now(), + }, +): Promise { + renderVersionInfo(formatVersionInfo()); + const bootstrap = await deps.storage.getValue().catch(() => null); + renderConnectionStatus(getConnectionStatusViewModel(bootstrap, deps.now())); + setStatus("idle", ""); +} + +if (typeof document !== "undefined") { + document.addEventListener("DOMContentLoaded", () => { + $("pair-form").addEventListener("submit", (ev) => { + void onSubmit(ev); + }); + void initializePopup(); }); - setStatus("idle", "Enter the one-time code issued by the daemon."); -}); +} diff --git a/extension/wxt.config.ts b/extension/wxt.config.ts index 91b7f9b..6e797c2 100644 --- a/extension/wxt.config.ts +++ b/extension/wxt.config.ts @@ -24,7 +24,8 @@ export default defineConfig({ }), manifest: { name: "bproxy", - description: "Browser proxy companion extension for bproxy daemon.", + description: + "Companion extension for bproxy. Connects Chrome to a local bproxy daemon for explicit human-in-the-loop browser actions.", permissions: ["tabs", "scripting", "webNavigation", "alarms", "storage"], host_permissions: [""], action: { From da500e810d9999d249f73e83a22dde36d2375e1e Mon Sep 17 00:00:00 2001 From: Dmitrii Kharitonov Date: Tue, 23 Jun 2026 17:50:55 +0200 Subject: [PATCH 5/8] Finalize phase 11 popup compliance plan --- .../phases/11-extension-popup-compliance.md | 457 +++++------------- 1 file changed, 111 insertions(+), 346 deletions(-) diff --git a/docs/internal/plans/phases/11-extension-popup-compliance.md b/docs/internal/plans/phases/11-extension-popup-compliance.md index f1830a4..a1a9a28 100644 --- a/docs/internal/plans/phases/11-extension-popup-compliance.md +++ b/docs/internal/plans/phases/11-extension-popup-compliance.md @@ -1,411 +1,176 @@ --- title: "Phase 11: Extension popup compliance and presentation" -status: planned +status: complete date: 2026-06-23 --- ## Phase 11: Extension popup compliance and presentation -**Motivation:** The current extension popup is a minimal pairing form (title, input, button, status line). It is functional but too terse for Chrome Web Store review or for a user/operator encountering the extension for the first time. The popup does not identify the product's purpose, authorship, version, license, or link to documentation. The solution spec at `docs/internal/solution/extension-popup-compliance.md` defines the target state. +**Motivation:** The extension popup was functional but too minimal for first-run clarity and Chrome Web Store review. It needed to explain what bproxy is, show version/protocol metadata, link to docs and privacy information, and present pairing state more clearly without turning the popup into a dashboard. **Source decisions:** - [ADR-001](../../decisions.md#adr-001-default-instrumentation-strategy--read-mode) — programmatic injection only - [ADR-011](../../decisions.md#adr-011-extension-token-bootstrap-via-popup-driven-pairing) — popup-driven pairing - [ADR-016](../../decisions.md#adr-016-web_accessible_resources-default-deny) — no WAR by default -- [ADR-017](../../decisions.md#adr-017-sensoractuator-boundary) — extension is sensor/actuator only; no strategy in popup -- [ADR-024](../../decisions.md#adr-024-no-arbitrary-page-eval-and-no-scroll-target-inference) — no eval / no scroll inference -- [ADR-025](../../decisions.md#adr-025-security-scanner-findings-are-remediated-in-code) — security findings remediated in code +- [ADR-017](../../decisions.md#adr-017-sensoractuator-boundary) — popup remains a thin pairing surface, not a dashboard +- [ADR-024](../../decisions.md#adr-024-no-arbitrary-page-eval-and-no-scroll-target-inference) — no extra control/eval surface +- [ADR-025](../../decisions.md#adr-025-security-scanner-findings-are-remediated-in-code) — compliance changes land in code/tests --- -## Scope +## Scope shipped -| # | Deliverable | Layer | Risk | -|---|-------------|-------|------| -| 1 | Popup markup/style redesign | extension popup HTML/CSS | Low | -| 2 | Version and protocol metadata rendering | extension popup TS | Low | -| 3 | Footer links (docs, license, credits) | extension popup HTML | Low | -| 4 | Paired/unpaired state presentation | extension popup TS | Low | -| 5 | Privacy policy page for CWS | docs site (views/) | Low | -| 6 | Store listing manifest description | extension wxt.config.ts | Low | -| 7 | Popup test updates | extension test/ | Low | +| # | Deliverable | Outcome | +|---|-------------|---------| +| 1 | Popup markup/style redesign | Shipped | +| 2 | Version and protocol metadata rendering | Shipped | +| 3 | Footer links (docs, license, credits) | Shipped | +| 4 | Paired/unpaired state presentation | Shipped | +| 5 | Privacy policy page for CWS | Shipped | +| 6 | Store listing manifest description | Shipped | +| 7 | Popup test updates | Shipped | -**Explicitly out of scope (per solution spec):** - -- No session counts, tab counts, recent-activity, or dashboard UI in the popup. -- No options page. -- No external fonts, remote assets, badge/chip components, or gradients. -- No strategy, selector repair, modal solving, or fallback chains. -- No store icon or screenshot creation (blocked until popup code is shipped). -- No new permissions or WAR entries. - ---- - -## Deliverable 1: Popup markup and style redesign - -### Intent - -Restructure `extension/src/entrypoints/popup/index.html` to match the proposed content layout from the solution spec. The popup should answer: what is this, what does it do, who made it, where to learn more. - -### Target layout (single column, ~340–380px) - -``` -┌──────────────────────────────────────┐ -│ ⚡ bproxy │ -│ Human-in-the-loop browser bridge │ -│ for AI agents. │ -│ │ -│ ● Not paired │ -│ │ -│ ─────────────────────────────────── │ -│ │ -│ Pairing code │ -│ ┌──────────────────────────────────┐ │ -│ │ │ │ -│ └──────────────────────────────────┘ │ -│ [ Pair extension ] │ -│ │ -│ Run `bproxy service start` and paste │ -│ the one-time code shown by the │ -│ daemon. │ -│ │ -│ ─────────────────────────────────── │ -│ │ -│ Extension 0.9.0 · Protocol 2 │ -│ Documentation · Privacy · MIT │ -│ Created by Dim Kharitonov [GH] │ -└──────────────────────────────────────┘ -``` - -When paired: `● Paired with local daemon` (dot turns green) - -### Typography and visual spec - -**Baseline grid:** 4px. All vertical spacing (margins, paddings, line-heights) must be multiples of 4px. - -**Font stack:** `system-ui, -apple-system, sans-serif` - -**Type scale (two sizes only, anchored to 4px grid):** - -| Size | Line-height | Weight | Where | -|------|-------------|--------|-------| -| 16px | 24px (6×4) | 600 | Product name (h1) | -| 13px | 20px (5×4) | 400 | Everything else | - -Hierarchy within the 13px tier uses **color only**: - -| Color | Where | -|-------|-------| -| `--c-text` | Status text, form label, input, button | -| `--c-muted` | Subtitle, help text, footer, unpaired dot | -| `--c-ok` | Paired dot | -| `--c-error` | Error messages | - -**Vertical rhythm:** - -| Spacing token | Value | Usage | -|---------------|-------|-------| -| `--space-xs` | 4px | Tight internal gaps (between version and links lines) | -| `--space-sm` | 8px | Between label and input, between adjacent elements | -| `--space-md` | 12px | Section separator padding (above/below dividers) | -| `--space-lg` | 16px | Body padding, gap between major sections | - -Body padding: `16px` (4×4) on all sides. -Dividers: `1px solid` — the 1px sits between rhythm rows, not disrupting them (use `padding-top: 12px; margin-top: 12px` on separated sections = 24px total gap with the 1px rule centered visually). - -**Color palette (5 values):** - -| Token | Value | Usage | -|-------|-------|-------| -| `--c-text` | `#1a1a1a` | Headings, input text, button text, form labels | -| `--c-muted` | `#555` | Subtitle, help text, footer, links, borders, unpaired dot | -| `--c-ok` | `#167a30` | Paired dot, success messages | -| `--c-error` | `#a4262c` | Error messages | -| `--c-bg` | `#fff` | Body background | - -Borders and dividers use `--c-muted` at reduced opacity (`border: 1px solid color-mix(in srgb, var(--c-muted) 30%, transparent)`) or just a light value like `#ddd` derived once in the stylesheet. No separate border token. - -Button background: transparent. Button border: `--c-muted`. Links: inherit `--c-muted`, underlined by default. - -**Form elements:** -- Input: `padding: 8px; border-radius: 4px; border: 1px solid #ddd` -- Button: `padding: 8px; border-radius: 4px; border: 1px solid var(--c-muted); background: transparent` -- Both inherit font from body. - -### Implementation - -**File:** `extension/src/entrypoints/popup/index.html` - -Changes: -- Set `` to `bproxy`. -- Add `min-width: 340px; max-width: 380px` to `body`. -- Define CSS custom properties (tokens above) on `:root` in the `<style>` block. -- Replace `<h1>bproxy pairing</h1>` with a header row: - - `<h1><svg ...cable icon.../> bproxy</h1>` — inline SVG (Lucide cable, 20×20, `stroke: currentColor`) sits beside the product name, vertically centered via `display: flex; align-items: center; gap: 8px`. - - `<p class="subtitle">Human-in-the-loop browser bridge for AI agents.</p>` -- The SVG is inlined directly in the HTML (from `assets/brand/cable.svg`), sized to 20×20 to match the h1 line-height optically. No external image request, no `<img>` tag, no WAR entry. -- Add a `<p id="connection-status">` element above the form showing the current pairing/connection state. -- Keep the form semantics intact: `<form>`, `<label>`, `<input>`, `<button>`. -- Change button text from "Pair" to "Pair extension". -- Add a help paragraph below the form: `Run <code>bproxy service start</code> and paste the one-time code shown by the daemon.` -- Add a `<footer>` section below the form with: - - Version line: `<span id="version-info">Extension {VERSION} · Protocol {PROTOCOL_VERSION}</span>` - - Links line: `Documentation · Privacy · MIT` (each an `<a>` with `target="_blank" rel="noreferrer"`; MIT links to the LICENSE file) - - Attribution: `Created by Dim Kharitonov` followed by an inline GitHub SVG icon linking to `https://github.com/dimdasci/bproxy` -- Keep `<output id="status">` for error/success messages from pairing. -- All spacing uses the rhythm tokens. No arbitrary pixel values outside the scale. -- All colors reference CSS custom properties. - -### Constraints - -- No external assets loaded. All styles inline in `<style>`. -- Native form semantics preserved (`<form>`, `<label>`, `<input>`, `<button>`, `<output aria-live>`). -- No JavaScript for layout/style. CSS-only visual changes. -- The help text and attribution are static — they do not depend on pairing state. -- Every vertical measurement must align to the 4px grid. Use browser DevTools grid overlay to verify during manual testing. +Out of scope remained unchanged: +- No session counts, tab counts, recent activity, or operator dashboard UI +- No options page +- No new permissions or `web_accessible_resources` +- No strategy, selector repair, retries, or fallback chains in the popup --- -## Deliverable 2: Version and protocol metadata rendering - -### Intent - -Show the extension version and protocol version in the popup footer, sourced from shared constants. - -### Implementation +## Major changes -**File:** `extension/src/entrypoints/popup/main.ts` +### Popup UI rewrite -Changes: -- Import `VERSION` and `PROTOCOL_VERSION` from `@bproxy/shared` (both already exported from `shared/src/version.ts`). -- On `DOMContentLoaded`, populate the `#version-info` element: `Extension ${VERSION} · Protocol ${PROTOCOL_VERSION}`. -- This is a one-time DOM write during init — no reactive binding needed. +`extension/src/entrypoints/popup/index.html` was redesigned into a compact single-column utility layout with: +- inline cable icon + product name +- subtitle: `Human-in-the-loop browser bridge for AI agents.` +- explicit pairing status line above the form +- pairing help text +- footer metadata and outbound links -### Constraints +The popup stayed fully static in HTML/CSS for presentation: +- inline styles only +- no external assets +- native form semantics preserved +- no new runtime permissions or WAR entries -- Import only from `@bproxy/shared`, preserving the workspace dependency rule (extension → shared only). -- If `VERSION` or `PROTOCOL_VERSION` cannot be imported (build misconfiguration), fall back to empty string rather than throwing. Popup must remain functional. +### Pairing-state presentation ---- - -## Deliverable 3: Footer links +`extension/src/entrypoints/popup/main.ts` now: +- reads local bootstrap state from `chrome.storage.local` +- renders `Not paired` vs `Paired with local daemon` +- shows a gray or green status dot +- updates status immediately after successful pairing -### Intent +Important honesty rule preserved: "Paired" means a valid local bootstrap record exists; it does not claim the daemon is currently reachable. -Provide external links to documentation, license, and privacy policy in the popup footer. +### Version and protocol metadata -### Implementation +The popup now renders: +- `Extension ${VERSION}` +- `Protocol ${PROTOCOL_VERSION}` -**File:** `extension/src/entrypoints/popup/index.html` +Both values come from `@bproxy/shared`. -Static `<a>` elements in the footer: -- Documentation: `https://dimdasci.github.io/bproxy/` -- Privacy: `https://dimdasci.github.io/bproxy/privacy/` -- MIT: `https://github.com/dimdasci/bproxy/blob/main/LICENSE` -- GitHub icon (on attribution line): `https://github.com/dimdasci/bproxy` +### Footer links and attribution -The GitHub icon is an inline SVG (Simple Icons / GitHub mark, 14×14, `fill: currentColor`) placed after "Created by Dim Kharitonov", vertically centered. No text label — the icon alone is the link. +The popup footer now includes static external links to: +- documentation +- privacy page +- MIT license +- GitHub repository (icon link) -All links use `target="_blank" rel="noreferrer"`. +Attribution is shown as plain text: `Created by Dim Kharitonov`. -### Constraints +### Re-pair UX follow-up -- Links are static HTML — no JavaScript click handlers. -- No `chrome.tabs.create` for link opening (unnecessary; `target="_blank"` works from popup). -- URLs must not include tracking parameters. - ---- +After manual review, the paired-state submit button was clarified: +- unpaired: `Pair extension` +- paired: `Re-pair with new code` -## Deliverable 4: Paired/unpaired state presentation +This matches actual architecture behavior: entering a new valid pairing code replaces the stored bootstrap and reconnects the background worker with a fresh extension token. -### Intent +### Manual presentation tweak after review -Show the current connection status at the top of the popup without adding dashboard/session information. +After loading the built extension in Chrome, popup spacing was increased to improve visual separation from the page behind it: +- outer padding increased +- major section spacing increased -### Implementation +This was a presentation-only adjustment; no behavioral changes. -**File:** `extension/src/entrypoints/popup/main.ts` +### Manifest and privacy materials -Changes: -- On `DOMContentLoaded`, check `chrome.storage.local` for the bootstrap record (already available via `bootstrapItem`). -- If a valid bootstrap payload exists (token present, not expired): render `● Paired with local daemon` — dot is an inline `<svg>` circle (8×8, `fill: var(--c-ok)`). -- If no bootstrap or expired: render `● Not paired` — dot uses `fill: var(--c-muted)`. -- After successful pairing (in `renderResult`), update dot color to green and text to `Paired with local daemon`. -- On pairing error, status remains `● Not paired` (gray dot). +- `extension/wxt.config.ts` manifest description was updated to CWS-safe wording: + - `Companion extension for bproxy. Connects Chrome to a local bproxy daemon for explicit human-in-the-loop browser actions.` +- `docs/public/privacy.md` was rewritten as a short factual privacy policy page for `/privacy/` -**What this does NOT show:** -- No WebSocket connection state (that's badge territory, already handled in background SW). -- No session count, tab count, or activity summary. -- No daemon version or remote metadata. +### Tests -### Constraints +Added `extension/src/entrypoints/popup/__tests__/presentation.test.ts` to cover: +- title/subtitle/copy +- footer metadata and links +- attribution and GitHub link +- paired/unpaired view-model behavior +- version/protocol rendering fallback behavior -- Status is based only on local `chrome.storage.local` bootstrap record — no daemon round-trip on popup open. -- Display is honest: "Paired" means the token exists locally. It does not guarantee the daemon is currently running. -- This deliberately mirrors the solution spec's "session and tab visibility check" conclusion: the popup cannot authoritatively show active sessions. +Existing pairing-flow tests remained intact. --- -## Deliverable 5: Privacy policy page - -### Intent - -Publish a minimal privacy policy page at `https://dimdasci.github.io/bproxy/privacy/` for CWS submission. - -### Implementation - -**File:** `docs/public/privacy.md` (Starlight will render this as `/bproxy/privacy/`) - -Content (plain English, no legal boilerplate): - -```markdown ---- -title: Privacy Policy ---- - -## bproxy Extension Privacy Policy - -The bproxy Chrome extension does not collect, store, or transmit any user data. - -**What the extension does:** -- Reads page content only when an AI agent sends a command through the local daemon. -- Passes page content to a daemon running on the same machine (`127.0.0.1`). -- Nothing leaves your device. There is no remote server, no analytics, no telemetry. - -**What the extension stores:** -- A pairing token in Chrome's local storage, used to authenticate the WebSocket connection to the local daemon. -- Bounded session diagnostics (request trace, dedupe cache) in Chrome's session storage, cleared when the browser closes. - -**Network communication:** -- The extension communicates exclusively with `localhost:9615` (`ws://127.0.0.1:9615/ws` and `http://127.0.0.1:9615/pair/claim`). -- It never contacts any external service. - -**Permissions rationale:** -- `tabs`: route actions to specific tabs and manage tab lifecycle. -- `scripting`: programmatically inject the content script on first command. -- `webNavigation`: track top-level navigation for page identity. -- `alarms`: background service-worker keepalive scheduling. -- `storage`: persist bootstrap token and session diagnostics. -- `<all_urls>`: support user-directed actions on any page. -``` - -### Constraints - -- No "we may update this policy" filler. -- Must be consistent with CWS privacy field declarations. -- Must be factually accurate against actual extension behavior. +## Files changed ---- - -## Deliverable 6: Manifest description update - -### Intent - -Ensure the manifest `description` field is honest, clear, and CWS-compliant. - -### Implementation - -**File:** `extension/wxt.config.ts` - -Change the `description` field in the manifest object: - -```ts -description: "Companion extension for bproxy. Connects Chrome to a local bproxy daemon for explicit human-in-the-loop browser actions.", -``` - -### Constraints - -- No promotional language ("best", "powerful", "seamless"). -- No circumvention language ("stealth", "bypass", "undetectable", "anti-bot"). -- Under 132 characters (CWS limit). - ---- - -## Deliverable 7: Popup test updates - -### Intent - -Adjust existing popup tests and add assertions for the new metadata/copy elements. - -### Implementation - -**File:** `extension/src/entrypoints/popup/__tests__/` (existing test files + potentially new test file) - -New assertions: -- Popup renders product subtitle text. -- Popup renders version info containing `VERSION` and `PROTOCOL_VERSION` values. -- Popup renders footer links (Documentation, Privacy, MIT) with correct `href` and `rel="noreferrer"`. -- Popup renders attribution line "Created by Dim Kharitonov" with a GitHub icon linking to the repo. -- Popup renders gray dot + "Not paired" when no bootstrap exists. -- Popup renders green dot + "Paired with local daemon" when valid bootstrap exists. -- Existing pairing flow tests continue to pass (DOM selectors for `#pair-form`, `#code`, `#submit`, `#status` remain stable). - -### Constraints - -- DOM selectors used in existing tests (`#pair-form`, `#code`, `#submit`, `#status`) must remain unchanged or tests must be updated in the same commit. -- No snapshot tests (fragile for styling changes). -- Tests must not import from packages outside `@bproxy/shared` (workspace rule). - ---- - -## Implementation order - -Tasks ordered by dependency: +| Package | File | +|---|---| +| extension | `src/entrypoints/popup/index.html` | +| extension | `src/entrypoints/popup/main.ts` | +| extension | `src/entrypoints/popup/__tests__/presentation.test.ts` | +| extension | `wxt.config.ts` | +| docs/public | `privacy.md` | -1. **Deliverable 6 (manifest description)** — Smallest change, zero dependencies. Quick win. -2. **Deliverable 1 (markup/style redesign)** — Foundation for all other popup deliverables. -3. **Deliverable 2 (version metadata)** — Requires new DOM elements from Deliverable 1. -4. **Deliverable 3 (footer links)** — Requires footer structure from Deliverable 1. -5. **Deliverable 4 (paired state)** — Requires status element from Deliverable 1. -6. **Deliverable 7 (test updates)** — Must follow all popup code changes. -7. **Deliverable 5 (privacy page)** — Independent of popup code; can be done in parallel but ordered last because it's in a different package. +No changes were required in `shared/`, `service/`, `cli/`, the background SW, content scripts, or the protocol wire shape. --- -## Files touched +## Shipped outcome -| Package | File | Change type | -|---------|------|-------------| -| extension | `src/entrypoints/popup/index.html` | Rewrite markup and styles | -| extension | `src/entrypoints/popup/main.ts` | Add imports, init logic for version/status | -| extension | `wxt.config.ts` | Update manifest description | -| extension | `src/entrypoints/popup/__tests__/*.ts` | Update/add test assertions | -| docs/public | `privacy.md` | New file (privacy policy page) | +The popup now presents bproxy as a clear pairing utility instead of a bare form: +- product identity and purpose are visible immediately +- pairing state is visible immediately +- version/protocol metadata is available for support/debugging +- documentation/privacy/license/credits are one click away +- paired users see `Re-pair with new code`, which matches the actual token-rotation flow -No changes to: `shared/`, `service/`, `cli/`, background SW, content scripts, or the protocol wire shape. +This improved first-run clarity and CWS readiness without expanding the popup into an operator surface or violating the extension's sensor/actuator boundary. --- ## Validation -All deliverables must pass before the phase is considered complete: - -- `pnpm --filter @bproxy/extension typecheck` passes. -- `pnpm --filter @bproxy/extension test` passes. -- `pnpm check` passes (typecheck + format + lint + arch + deadcode). -- `pnpm docs:build` passes (privacy page renders correctly). -- Manual verification: load the built extension in Chrome, open popup, confirm: - - Product name, subtitle, and purpose text visible. - - "Not paired" status shown. - - Pairing flow works as before. - - After pairing, status shows "Paired with local daemon". - - Version and protocol version displayed in footer. - - Footer links open correct URLs in new tabs. - - Attribution and license visible. - - Popup width between 340–380px, single column, no scroll needed for typical content. +Validated during implementation: +- `pnpm --filter @bproxy/extension typecheck` +- `pnpm --filter @bproxy/extension test` +- `pnpm docs:build` +- `pnpm check` +- `pnpm --filter @bproxy/extension build` + +Manual verification in Chrome confirmed: +- updated layout and spacing render correctly +- paired/unpaired states display correctly +- successful pairing updates the status line +- paired state shows `Re-pair with new code` +- footer links open correctly --- -## Deferred +## Deferred / rejected | Item | Reason | |---|---| -| Store icon (128×128 PNG) | Requires design decision on rendering `cable.svg` at large size; not code work. | -| CWS screenshots (1280×800) | Blocked until popup redesign ships — screenshots must show final UI. | -| Small/marquee promo tiles | Not required for initial submission; marketing asset. | -| Options page | Solution spec explicitly says none needed for this increment. | -| WebSocket connection state in popup | Badge already handles this; popup shows token presence only. | -| Operator session visibility surface | Requires separate ADR; intentionally discarded per solution spec. | -| Dark mode / `prefers-color-scheme` | No requirement stated; can be added later with CSS media query. | -| Localization / i18n | Single-language (English) for now. | +| Session/tab/activity dashboard in popup | Rejected for this phase; daemon remains the authority and popup is not an operator dashboard | +| WebSocket live connection state in popup | Deferred; badge already covers transport state | +| Options page | Explicitly out of scope | +| Store icons/screenshots/promo assets | Deferred; non-code store assets | +| Dark mode | Deferred; no requirement for this increment | +| Localization | Deferred; English-only for now | From 8c77441fa6134d39de01b375877185b0aaaa2a37 Mon Sep 17 00:00:00 2001 From: Dmitrii Kharitonov <dimds@fastmail.com> Date: Tue, 23 Jun 2026 18:18:52 +0200 Subject: [PATCH 6/8] docs: polish public docs presentation --- docs/public/index.md | 6 +++++- views/astro.config.mjs | 1 + views/public/assets/bproxy-hero.png | 1 + views/src/components/Footer.astro | 33 +++++++++++++++++++++++++++++ 4 files changed, 40 insertions(+), 1 deletion(-) create mode 120000 views/public/assets/bproxy-hero.png create mode 100644 views/src/components/Footer.astro diff --git a/docs/public/index.md b/docs/public/index.md index 02bef16..5e7f2c8 100644 --- a/docs/public/index.md +++ b/docs/public/index.md @@ -2,9 +2,13 @@ title: bproxy --- +<img src="./assets/bproxy-hero.png" alt="bproxy hero illustration: three robot agents on the left connect through a terminal and a shield to a stack of browser tabs on the right, all framed inside a computer monitor. Caption reads 'bproxy: paired browsing for humans and agents.'" width="900"> + ## Why this exists -Most of my work is information research, analysis, and synthesis. Agents are good at the routine part — collecting data, transforming it, distilling patterns — but they need browser access to do it, and the services I use (Google, authenticated feed sites, Medium) actively detect automation. +Most of my work is information research, analysis, and synthesis. Agents are good at the routine part — collecting data, transforming it, distilling patterns — but they need browser access to do it. Many services detect automation and use their terms of service to restrict it. + +I think "automation" is overloaded. At the extreme, I am already sitting at a laptop, using an operating system and a browser to access a service — is that not automation too? I am not trying to scrape service content at scale. I want to automate the copy-paste and routine navigation that are a natural part of desktop research. I have two options: diff --git a/views/astro.config.mjs b/views/astro.config.mjs index ca5fc53..282fdc7 100644 --- a/views/astro.config.mjs +++ b/views/astro.config.mjs @@ -144,6 +144,7 @@ export default defineConfig({ }, ], components: { + Footer: "./src/components/Footer.astro", PageFrame: "./src/components/PageFrame.astro", }, customCss: ["./src/styles/custom.css"], diff --git a/views/public/assets/bproxy-hero.png b/views/public/assets/bproxy-hero.png new file mode 120000 index 0000000..e2e8968 --- /dev/null +++ b/views/public/assets/bproxy-hero.png @@ -0,0 +1 @@ +../../../assets/bproxy-hero.png \ No newline at end of file diff --git a/views/src/components/Footer.astro b/views/src/components/Footer.astro new file mode 100644 index 0000000..d86919e --- /dev/null +++ b/views/src/components/Footer.astro @@ -0,0 +1,33 @@ +--- +import DefaultFooter from "@astrojs/starlight/components/Footer.astro"; +import pkg from "../../package.json"; + +const version = pkg.version; +--- + +<DefaultFooter /> + +<p class="bproxy-site-meta"> + bproxy v{version} · + <a href="https://github.com/dimdasci/bproxy/blob/main/LICENSE" target="_blank" rel="noopener noreferrer"> + MIT License + </a> + · © Dim Kharitonov +</p> + +<style> + .bproxy-site-meta { + margin: 0 auto 1.5rem; + font-size: var(--sl-text-xs); + color: var(--sl-color-gray-3); + text-align: center; + } + + .bproxy-site-meta a { + color: inherit; + } + + .bproxy-site-meta a:hover { + color: var(--sl-color-white); + } +</style> From 583595ab7769a47b6864aa6579a8c9af0167e9bc Mon Sep 17 00:00:00 2001 From: Dmitrii Kharitonov <dimds@fastmail.com> Date: Tue, 23 Jun 2026 18:19:11 +0200 Subject: [PATCH 7/8] chore: bump version to 0.9.1 --- cli/package.json | 2 +- extension/package.json | 2 +- package.json | 2 +- service/package.json | 2 +- shared/package.json | 2 +- shared/src/version.ts | 2 +- skills/bproxy/SKILL.md | 2 +- views/package.json | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/cli/package.json b/cli/package.json index a7a6cb1..9b3817d 100644 --- a/cli/package.json +++ b/cli/package.json @@ -1,6 +1,6 @@ { "name": "@bproxy/cli", - "version": "0.9.0", + "version": "0.9.1", "private": true, "type": "module", "bin": { diff --git a/extension/package.json b/extension/package.json index 6026ea7..99692d6 100644 --- a/extension/package.json +++ b/extension/package.json @@ -1,6 +1,6 @@ { "name": "@bproxy/extension", - "version": "0.9.0", + "version": "0.9.1", "private": true, "type": "module", "scripts": { diff --git a/package.json b/package.json index c8a67fb..5db23f6 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "bproxy", "private": true, "type": "module", - "version": "0.9.0", + "version": "0.9.1", "description": "Human-in-the-loop browser proxy for AI agents.", "license": "MIT", "packageManager": "pnpm@9.15.0", diff --git a/service/package.json b/service/package.json index 0fa3973..6bc1fc1 100644 --- a/service/package.json +++ b/service/package.json @@ -1,6 +1,6 @@ { "name": "@bproxy/service", - "version": "0.9.0", + "version": "0.9.1", "private": true, "type": "module", "bin": { diff --git a/shared/package.json b/shared/package.json index ffbfb63..8a68743 100644 --- a/shared/package.json +++ b/shared/package.json @@ -1,6 +1,6 @@ { "name": "@bproxy/shared", - "version": "0.9.0", + "version": "0.9.1", "private": true, "type": "module", "exports": { diff --git a/shared/src/version.ts b/shared/src/version.ts index 73029a6..4eaa82c 100644 --- a/shared/src/version.ts +++ b/shared/src/version.ts @@ -6,7 +6,7 @@ */ /** Current bproxy package version (semver). */ -export const VERSION = "0.9.0"; +export const VERSION = "0.9.1"; /** Protocol version for the daemon↔CLI↔extension wire format. */ export const PROTOCOL_VERSION = 2; diff --git a/skills/bproxy/SKILL.md b/skills/bproxy/SKILL.md index 426180c..4b91477 100644 --- a/skills/bproxy/SKILL.md +++ b/skills/bproxy/SKILL.md @@ -9,7 +9,7 @@ description: >- compatibility: Node >=24, bproxy installed (npm install -g @dimdasci/bproxy), daemon running, extension paired license: MIT metadata: - version: "0.9.0" + version: "0.9.1" --- # bproxy diff --git a/views/package.json b/views/package.json index 4a323eb..55815f6 100644 --- a/views/package.json +++ b/views/package.json @@ -1,7 +1,7 @@ { "name": "@bproxy/views", "private": true, - "version": "0.9.0", + "version": "0.9.1", "type": "module", "scripts": { "dev": "astro dev", From 87d97a10ef8d138dcb749dbccd49f648efd00794 Mon Sep 17 00:00:00 2001 From: Dmitrii Kharitonov <dimds@fastmail.com> Date: Tue, 23 Jun 2026 18:43:17 +0200 Subject: [PATCH 8/8] =?UTF-8?q?perf(extension):=20fix=20O(n=C2=B2)=20selec?= =?UTF-8?q?tor=20probing=20in=20links=20cap=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add unique id attrs to the 2001 fake anchor elements so createStableSelector resolves on the first candidate (#id) instead of falling through to path-selector probing against all siblings. Extension test suite: 73s → 2.3s. --- extension/src/content/__tests__/reads.test.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/extension/src/content/__tests__/reads.test.ts b/extension/src/content/__tests__/reads.test.ts index d981551..95cf653 100644 --- a/extension/src/content/__tests__/reads.test.ts +++ b/extension/src/content/__tests__/reads.test.ts @@ -230,14 +230,17 @@ describe("read actions", () => { }); it("links returns capped: true when page has more than MAX_COLLECTION_CAP links", () => { - // MAX_COLLECTION_CAP is 2000 — create 2001 links to trigger the cap + // MAX_COLLECTION_CAP is 2000 — create 2001 links to trigger the cap. + // Each element gets a unique id so createStableSelector resolves on the + // first candidate (#id), avoiding O(n²) path-selector probing in the + // fake querySelectorAll implementation. const page = doc( el("html", { children: [ el("body", { children: Array.from({ length: 2001 }, (_, i) => el("a", { - attrs: { href: `https://example.com/${i}` }, + attrs: { id: `lk${i}`, href: `https://example.com/${i}` }, text: `L${i}`, }), ),