diff --git a/05_Official_Plugins.md b/05_Official_Plugins.md index f151424..944b0cc 100644 --- a/05_Official_Plugins.md +++ b/05_Official_Plugins.md @@ -249,8 +249,10 @@ search.provider sidebar view and a workspace item. Workspace tabs keep their own pending queue; the global sidebar view aggregates queues from all workspaces plus unscoped global captures. The local receiver now has an opt-in paired mode that requires -`X-Verstak-Receiver-Token` before publishing browser capture events. Domain -binding and conversion into notes/links/files/activity are still future work. +`X-Verstak-Receiver-Token` before publishing browser capture events. Browser +Inbox stores plugin-owned `domainBindings` and routes unscoped captures with an +exact domain match into the bound workspace queue. Conversion into +notes/links/files/activity is still future work. ## 9. `official.search` diff --git a/07_Full_Implementation_Roadmap.md b/07_Full_Implementation_Roadmap.md index 8056e7a..4176619 100644 --- a/07_Full_Implementation_Roadmap.md +++ b/07_Full_Implementation_Roadmap.md @@ -55,8 +55,9 @@ Known remaining gaps: - File/image preview exists as a basic provider with bounded inline image rendering through the public Files API. - Browser extension repository has protocol, queue, and Chromium/Firefox build - scaffold; desktop has a local receiver and mounted-view inbox plugin, but no - pairing model, domain binding, or conversion workflow yet. + scaffold; desktop has a local receiver and mounted-view inbox plugin; receiver + pairing and basic Browser Inbox domain binding are implemented, but no + conversion workflow yet. - Packaging/update/release workflow is not product-grade yet. ## 4. Implementation Phases @@ -163,7 +164,7 @@ Tasks: - [x] implement browser extension capture scaffold for URL, selected text, page title, and link captures; - [x] define local receiver permission/pairing model; -- add domain-to-workspace binding; +- [x] add domain-to-workspace binding; - convert inbox entries into notes/links/files/activity events through public plugin APIs. @@ -235,8 +236,7 @@ Verification: 4. [x] Notes trash/delete UX in `verstak-official-plugins`. 5. [x] Sync hardening pass with expanded real two-vault smoke. 6. [~] Browser inbox protocol design, extension scaffold, local receiver, and - minimal inbox plugin are implemented; pairing, domain binding, and conversion - workflows remain. + minimal inbox plugin are implemented; conversion workflows remain. This order finishes generic platform surfaces before building product features that depend on them. diff --git a/docs/superpowers/plans/2026-06-29-browser-inbox-domain-binding.md b/docs/superpowers/plans/2026-06-29-browser-inbox-domain-binding.md new file mode 100644 index 0000000..f12258d --- /dev/null +++ b/docs/superpowers/plans/2026-06-29-browser-inbox-domain-binding.md @@ -0,0 +1,119 @@ +# Browser Inbox Domain Binding Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Route unscoped Browser Inbox captures into workspace queues through plugin-owned domain bindings. + +**Architecture:** Keep domain binding in `verstak.browser-inbox`. The plugin reads `domainBindings` from its own settings namespace, annotates unscoped incoming captures before storage, and preserves desktop core as a capture-event publisher only. + +**Tech Stack:** Plain JavaScript official plugin bundle, Node smoke test harness, Markdown docs. + +## Global Constraints + +- Do not move Browser Inbox queues or conversion workflows into desktop core. +- Do not import Notes, Files, Activity, or Journal from Browser Inbox. +- Route only captures that do not already include `workspaceRootPath`. +- Domain matching is exact and case-insensitive for this slice. +- Use TDD: write the failing smoke test first, run it red, then implement. + +--- + +### Task 1: Browser Inbox Domain Routing + +**Files:** +- Modify: `/home/mirivlad/git/verstak2/verstak-official-plugins/scripts/smoke-browser-inbox-plugin.js` +- Modify: `/home/mirivlad/git/verstak2/verstak-official-plugins/plugins/browser-inbox/frontend/src/index.js` + +**Interfaces:** +- Consumes: Browser Inbox plugin settings key `domainBindings`. +- Produces: unscoped captures annotated with `workspaceRootPath` and `workspaceName` when an exact domain binding exists. + +- [ ] **Step 1: Write the failing smoke test** + +Add a scenario to `scripts/smoke-browser-inbox-plugin.js`: + +```js +const bindingApi = makeApi({ + domainBindings: { + 'client.example.com': 'ClientA', + 'project.example.com': 'Project' + } +}); +const bindingGlobal = await mountWithApi(bindingApi, {}); +await bindingApi.handlers['browser.capture.page']({ + name: 'browser.capture.page', + timestamp: '2026-06-29T00:00:00Z', + payload: { + captureId: 'bound-client-capture', + capturedAt: '2026-06-29T00:00:00.000Z', + kind: 'page', + url: 'https://client.example.com/page', + title: 'Bound Client Page', + domain: 'client.example.com' + } +}); +await flush(); +if (bindingApi.getStoredCaptures('captures:workspace:ClientA').length !== 1) { + throw new Error('domain-bound capture was not stored under ClientA workspace key'); +} +``` + +Also prove explicit `workspaceRootPath: "Project"` wins over a binding for +`client.example.com`. + +- [ ] **Step 2: Run RED** + +Run: + +```bash +cd /home/mirivlad/git/verstak2/verstak-official-plugins +PATH=/tmp/verstak2-tools:/home/mirivlad/.lmstudio/.internal/utils:$PATH node scripts/smoke-browser-inbox-plugin.js +``` + +Expected: fails because domain-bound captures are still stored in the receiving +view scope. + +- [ ] **Step 3: Implement minimal routing** + +In `plugins/browser-inbox/frontend/src/index.js`, add helper functions to +normalize binding keys, derive a domain from capture fields, and annotate +captures without `workspaceRootPath` before storage. + +- [ ] **Step 4: Run GREEN** + +Run: + +```bash +cd /home/mirivlad/git/verstak2/verstak-official-plugins +PATH=/tmp/verstak2-tools:/home/mirivlad/.lmstudio/.internal/utils:$PATH node scripts/smoke-browser-inbox-plugin.js +PATH=/tmp/verstak2-tools/venv/bin:/tmp/verstak2-tools:/home/mirivlad/.lmstudio/.internal/utils:$PATH ./scripts/check.sh +``` + +Expected: both commands exit 0. + +### Task 2: Roadmap Documentation + +**Files:** +- Modify: `/home/mirivlad/git/verstak2/verstak-docs/05_Official_Plugins.md` +- Modify: `/home/mirivlad/git/verstak2/verstak-docs/07_Full_Implementation_Roadmap.md` + +**Interfaces:** +- Consumes verified plugin routing behavior. +- Produces docs that distinguish implemented domain binding from future conversion workflows. + +- [ ] **Step 1: Update docs** + +Mark domain binding as implemented in the Browser Inbox status text and roadmap +while leaving conversion workflow as future work. + +- [ ] **Step 2: Verify docs** + +Run: + +```bash +cd /home/mirivlad/git/verstak2/verstak-docs +git diff --check +rg -n "domain-to-workspace binding|Domain binding|conversion" 05_Official_Plugins.md 07_Full_Implementation_Roadmap.md +``` + +Expected: `git diff --check` exits 0 and `rg` shows the updated status lines. diff --git a/docs/superpowers/specs/2026-06-29-browser-inbox-domain-binding-design.md b/docs/superpowers/specs/2026-06-29-browser-inbox-domain-binding-design.md new file mode 100644 index 0000000..a689f47 --- /dev/null +++ b/docs/superpowers/specs/2026-06-29-browser-inbox-domain-binding-design.md @@ -0,0 +1,74 @@ +# Browser Inbox Domain Binding Design + +## Purpose + +Browser captures often arrive while the user is not focused on the workspace +that should receive them. Domain binding routes unscoped browser captures into a +workspace queue by matching the capture domain to a plugin-owned binding table. + +This keeps Browser Inbox behavior in the official plugin. Desktop core still +only publishes browser capture events and may annotate the current workspace +when it knows one. + +## Scope + +This slice implements domain-to-workspace routing inside `verstak.browser-inbox`: + +- store bindings in the Browser Inbox plugin settings namespace; +- match capture domains case-insensitively; +- route only captures that do not already include `workspaceRootPath`; +- preserve the global aggregate view; +- keep the binding model independent from Notes, Files, Activity, and Journal. + +It does not implement conversion into notes/links/files/activity, browser UI for +editing bindings, or a desktop core domain binding API. + +## Settings Contract + +The plugin reads `domainBindings` from its settings object: + +```json +{ + "domainBindings": { + "example.com": "Project", + "client.example.com": "ClientA" + } +} +``` + +Keys are hostnames. Values are top-level `workspaceRootPath` strings. + +Normalization rules: + +- trim whitespace around keys and values; +- lowercase domains for matching; +- strip one or more leading dots from binding keys; +- ignore empty domains and empty workspace roots. + +## Routing Rules + +For each incoming `browser.capture.page`, `browser.capture.selection`, or +`browser.capture.link` event: + +1. If payload already has `workspaceRootPath`, keep it unchanged. +2. Otherwise, derive a domain from `payload.domain` or `payload.url`. +3. If an exact normalized domain binding exists, set `workspaceRootPath` and + `workspaceName` to the bound workspace root before storage. +4. If no binding exists, keep current behavior and store the capture in the + receiving view scope. + +Subdomain fallback is intentionally out of scope for this first slice. A +binding for `example.com` does not match `docs.example.com` until a later design +adds explicit wildcard or suffix semantics. + +## Testing + +`scripts/smoke-browser-inbox-plugin.js` must prove: + +- an unscoped capture with `domain: "client.example.com"` and a + `domainBindings` entry for that domain is stored under + `captures:workspace:ClientA`; +- the routed capture appears in the ClientA workspace view; +- the global view still aggregates the routed capture; +- an event that already includes `workspaceRootPath: "Project"` is not + overwritten by a domain binding for another workspace.