diff --git a/DESIGN.md b/DESIGN.md index 87bc38c..077a5e4 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -1,4 +1,4 @@ -# openpicker Design +# OpenPicker Design > An open-source CSS element picker. Ships a browser extension + npm SDK so that > any third-party web page can invoke an element picker through one open protocol @@ -21,7 +21,7 @@ page" lock-in behavior. All code and protocol naming are written from scratch. ### 1.1 Design principle: openness by parameter -openpicker is a platform, not one product's tool. Capabilities are exposed as **parameters and +OpenPicker is a platform, not one product's tool. Capabilities are exposed as **parameters and extensible enums**, not hardcoded behavior, so integrators decide what fits their use case: - **Behavior is opt-in via parameters / string enums**, never baked in. Examples already in v1: @@ -44,7 +44,7 @@ extensible enums**, not hardcoded behavior, so integrators decide what fits thei │ Integrator page (any origin) │ │ │ │ Integrator code │ -│ └── calls openpicker SDK (npm) │ +│ └── calls OpenPicker SDK (npm) │ │ │ window.postMessage (with id) │ │ ▼ │ │ content script ◄── injected by extension │ @@ -171,7 +171,7 @@ after the user confirms with OK. Layout (top → bottom): ``` ┌────────────────────────────────────────┐ -│ ⏏ ⇄ openpicker ? ✕ │ header: dock/eject, swap side, help, close +│ ⏏ ⇄ OpenPicker ? ✕ │ header: dock/eject, swap side, help, close ├────────────────────────────────────────┤ │ [ div[data-hveid] > div:nth-of-type(5) ]⚙│ editable selector input + settings ├────────────────────────────────────────┤ @@ -236,12 +236,12 @@ map onto `@medv/finder` configuration, surfaced as UI. ``` - The generated selector always targets **exactly one element** (finder's unique selector). A - "list / group" mode was considered but cut — openpicker's purpose is targeting a single element, + "list / group" mode was considered but cut — OpenPicker's purpose is targeting a single element, and the feature was borrowed from a reference UI without a real use case (see git history). - **Per-dimension rules** (id / class / attr / tag): each has an **enable** toggle plus an **allow** and an **ignore** regex, mapping onto finder's `idName` / `className` / `attr` / `tagName` predicates. For `attr`, allow/ignore match the attribute **name**. An empty **allow** falls back to - openpicker's stable-name default (skips hashed ids/classes; prefers test hooks + finder's curated + OpenPicker's stable-name default (skips hashed ids/classes; prefers test hooks + finder's curated attribute set). The settings are one resolved `SelectorSettings` object; legacy stored shapes (boolean toggles + a single ignore + an attr name-list) are migrated on load. - **Layering**: built-in defaults < the user's **global default** (options page) < the user's @@ -431,7 +431,7 @@ dashboard.example.com" — for transparency. ## 5d. Source↔target tab mapping, reuse, and continuity (v2.x, designed) This is the heart of robust cross-tab picking. The architecture follows a proven, production cross- -tab pattern (studied for ideas; **all code is openpicker's own — no third-party code is copied, and +tab pattern (studied for ideas; **all code is OpenPicker's own — no third-party code is copied, and no third-party product name appears anywhere in the repo**). Vendor-specific concerns from that pattern — injecting a vendor SDK, CSP adaptation, debugger UI, hardcoded origin allowlists — are deliberately dropped; only the generic "open a tab, pick there, route the result back, survive @@ -454,14 +454,14 @@ op:targetToSource: → { sourceTabId, params, key?, pickId? } ### Target tab reuse (the "flow_id" substitution) When a pick requests a `url` and a target tab is already mapped to this source, decide reuse vs. open-new the same way the reference pattern does — except the business-identity dimension (its -`flow_id`) is replaced by openpicker-native inputs, since openpicker has no business concepts: +`flow_id`) is replaced by openpicker-native inputs, since OpenPicker has no business concepts: ``` reference: different host → new tab; same host but different flow_id → new tab; same host, different pathname → new tab -openpicker: different host → new tab; same host but different `key` → new tab; (optional) different pathname → new tab +OpenPicker: different host → new tab; same host but different `key` → new tab; (optional) different pathname → new tab ``` - `key` is an **optional, caller-supplied** opaque string (e.g. the integrator's own step id). - openpicker never interprets it — it only compares equality, exactly as the reference compares + OpenPicker never interprets it — it only compares equality, exactly as the reference compares flow_id. This keeps the reuse logic identical while staying business-agnostic and open (§1.1). - No `key` → fall back to host/URL comparison alone. - **On reuse, the target tab is only focused — never re-navigated.** Reuse is host-gated, so the @@ -470,7 +470,7 @@ openpicker: different host → new tab; same host but different `key` → new ta where they went. So a reused pick runs on the tab's *current* page, not the requested `url`. (The `url` only opens the tab the first time and decides reuse-vs-new; it matches the reference.) - Why caller-supplied, not internal: "is this the same task?" is a business judgment only the - integrator knows. The reference could read flow_id because it *is* the business; openpicker is a + integrator knows. The reference could read flow_id because it *is* the business; OpenPicker is a tool, so the business identity must come in from the caller. **One target per source (decided: keep it single).** A source holds exactly one target mapping @@ -496,7 +496,7 @@ pick re-arms **in navigate mode**, not select mode — the user may need to hop and re-arming select mode would re-lock the page each time. The user returns to select mode explicitly via "Resume picking"; the flag is cleared then and when the pick ends. -openpicker's twist over the reference: after re-arming on a new page/tab, the result still has to +OpenPicker's twist over the reference: after re-arming on a new page/tab, the result still has to reach the original **source** tab. Delivery is **stateless** — there is no long-lived port and no in-memory registry of pending picks in the background. Instead: - The source sends a one-shot `crossTab:open` (with a `pickId`) and then waits for a `crossTab:deliver` @@ -509,7 +509,7 @@ in-memory registry of pending picks in the background. Instead: Because every step reads the map fresh, a pick **survives the MV3 service worker being recycled mid-pick** (an earlier port + in-memory-resolver design lost the pick when the worker died, and needed the worker kept alive). This matches how the reference stays resilient. (The reference's -injected SDK carried its own connection; openpicker has no resident SDK, so it routes through the map.) +injected SDK carried its own connection; OpenPicker has no resident SDK, so it routes through the map.) ### Phasing 1. **Phase 1:** background bidirectional map + reuse decision (host/URL + optional `key`) + refocus- diff --git a/PRIVACY.md b/PRIVACY.md index 0cb2130..f0805e2 100644 --- a/PRIVACY.md +++ b/PRIVACY.md @@ -1,14 +1,14 @@ -# openpicker — Privacy Policy +# OpenPicker — Privacy Policy _Last updated: 2026-06-03_ -openpicker is an open-source browser extension that lets you point at an element on a web page +OpenPicker is an open-source browser extension that lets you point at an element on a web page and get a stable CSS selector for it. This policy explains what the extension does and does not do with your data. ## The short version -**openpicker does not collect, transmit, or sell your data. There are no openpicker servers.** +**OpenPicker does not collect, transmit, or sell your data. There are no OpenPicker servers.** Everything happens locally in your browser. We have no analytics, no tracking, and no ads. ## What is stored, and where @@ -27,9 +27,9 @@ You can review or clear these at any time from the extension's options page. - The **selector and basic element details** produced by a pick are delivered to the web page that started the pick (or shown to you when you pick from the toolbar). This stays within your browser; - openpicker does not send it to us or any third party. + OpenPicker does not send it to us or any third party. - A **screenshot** is captured only when a pick explicitly requests one. It is returned to the - requesting page / shown in the picker, again only within your browser. openpicker does not upload + requesting page / shown in the picker, again only within your browser. OpenPicker does not upload it anywhere. ## Permissions diff --git a/PROTOCOL.md b/PROTOCOL.md index 9b57823..6f18aa8 100644 --- a/PROTOCOL.md +++ b/PROTOCOL.md @@ -1,4 +1,4 @@ -# openpicker Protocol v1 +# OpenPicker Protocol v1 > The contract between the **page SDK** (npm) and the **browser extension**. It defines how > they discover each other, exchange requests/responses, correlate them, handle consent, and @@ -15,7 +15,7 @@ There are two communication hops; only the first is part of this public protocol ``` [ integrator code ] │ function calls -[ openpicker SDK ] ── window.postMessage ──► [ extension content script ] ◄── THIS PROTOCOL +[ OpenPicker SDK ] ── window.postMessage ──► [ extension content script ] ◄── THIS PROTOCOL │ chrome.runtime.* (extension-internal) [ background service worker ] ``` @@ -80,7 +80,7 @@ Unknown fields MUST be ignored (forward compatibility). Receivers must not assum - `id` format: `op::` - `instanceId` — a short random id (e.g. nanoid) generated once per SDK instance. Disambiguates - multiple SDK instances on the same page (host app + an embedded widget both using openpicker). + multiple SDK instances on the same page (host app + an embedded widget both using OpenPicker). - `seq` — a monotonically increasing counter within that instance. - A response MUST echo the request's `id` exactly. - The SDK keeps a pending map `id → { resolve, reject, timer }`. On a matching `res` it settles diff --git a/README.md b/README.md index 81252d9..80a207c 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ openpicker

-

openpicker

+

OpenPicker

An open-source CSS element picker for the web.

@@ -42,7 +42,7 @@ straight from the toolbar, with no code at all. A web page can already inspect its *own* DOM. What it can't do is ask the user to point at an element on **another tab or origin** and hand the selector back — browsers isolate origins for good reason. That cross-tab/cross-origin hop is exactly what an extension can bridge, and it's the -reason openpicker exists. It's a natural fit for product onboarding/tours, event tagging, no-code +reason OpenPicker exists. It's a natural fit for product onboarding/tours, event tagging, no-code automation, scraping config, and anything that needs "let me point at that element." ## Features @@ -69,7 +69,7 @@ Two communication hops; only the first is the public protocol: ``` [ your code ] │ function calls -[ openpicker SDK ] ── window.postMessage ──► [ extension content script ] ◄── PUBLIC PROTOCOL +[ OpenPicker SDK ] ── window.postMessage ──► [ extension content script ] ◄── PUBLIC PROTOCOL │ chrome.runtime.* (extension-internal) [ background service worker ] ``` @@ -206,7 +206,7 @@ display-only; only the verified `event.origin` is authoritative. See [PROTOCOL.m openpicker/ ├── packages/ │ ├── protocol/ # @openpicker/protocol — shared types & constants (source-only, private) -│ ├── sdk/ # openpicker — the npm SDK integrators install (tsup build) +│ ├── sdk/ # OpenPicker — the npm SDK integrators install (tsup build) │ └── extension/ # @openpicker/extension — the browser extension (WXT + React + Tailwind, MV3) ├── apps/ │ ├── test/ # interactive Ping/Pick test page diff --git a/apps/demo/index.html b/apps/demo/index.html index cfb8e23..1274041 100644 --- a/apps/demo/index.html +++ b/apps/demo/index.html @@ -5,10 +5,10 @@ - openpicker — live demo + OpenPicker — live demo @@ -18,7 +18,7 @@ href="https://openpicker.dev" > - openpicker + OpenPicker