diff --git a/CHANGELOG.md b/CHANGELOG.md index e057c21..5be779b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ extension (`extension/manifest.json`). ### Added - Open-source contributor docs and GitHub community files (issues, PRs, CI, code of conduct) +- Popup keyboard shortcuts: `Esc` closes the composer or goes back to the library; `Alt+Shift+S` opens Add a bookmark and prefills the current http(s) tab ### Changed diff --git a/README.md b/README.md index ff901b3..058e167 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ The public site lives in [`landing/`](landing/). Sign in, the library, and accou - **Private by default** — links stay on your account. - **Light and dark** theme. - **Library first** — the home screen is your collections and bookmarks. Forms appear when you add or edit. +- **Keyboard shortcuts** — `Alt+Shift+S` saves the current tab; `Esc` closes a form or goes back. See [extension/README.md](extension/README.md). ## Tech stack diff --git a/docs/architecture.md b/docs/architecture.md index d7915e5..53c2436 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -48,7 +48,7 @@ Popup --Bearer JWT--> /api/v1 --> MongoDB Options page (same API, developer host override) ``` -There are **no content scripts**. The extension does not inject into web pages and does not read browsing history. Permissions are `storage` plus host access to the Curate API (production and localhost). +There are **no content scripts**. The extension does not inject into web pages and does not read browsing history. Permissions are `storage`, `activeTab` (current tab URL/title after a toolbar click or the add-bookmark command), plus host access to the Curate API (production and localhost). ## Auth in brief diff --git a/docs/development.md b/docs/development.md index ba9ae04..26ce4ea 100644 --- a/docs/development.md +++ b/docs/development.md @@ -121,6 +121,8 @@ database). It covers: - `tests/api/bookmarks.test.js` - bookmark CRUD, ownership, validation - `tests/api/collections.test.js` - collection CRUD, ownership, validation - `tests/unit/validators.test.js` - `normalizeUrl`, `parseTags`, Joi schemas +- `tests/unit/shortcuts.test.mjs` - popup shortcut helpers (Esc routing, savable tab URLs) +- `tests/unit/extension-manifest.test.js` - add-bookmark command and `activeTab` Tests run against a throwaway MongoDB started in memory by `mongodb-memory-server` - no local MongoDB, `MONGO_URI`, or other setup is diff --git a/docs/roadmap.md b/docs/roadmap.md index cb0ed26..b714af9 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -9,6 +9,7 @@ This is the direction of the project, not a contract. Features start as [issues] - Accounts in the popup (register, sign in, profile, password, delete) - `/api/v1` JSON API with Bearer JWT - Automated API tests (`node --test`) for auth, bookmarks, and collections, running in CI +- Keyboard shortcuts in the popup (`Esc` to cancel/back, `Alt+Shift+S` to add the current tab) - Light and dark theme - Product landing page and privacy policy - [Chrome Web Store](https://chromewebstore.google.com/detail/curate/nlkfmdiphacjgicdcagonbfnpcdjfapo) @@ -30,7 +31,6 @@ These need an issue and a design discussion before a PR: - Search and filter across bookmarks - Import / export a personal library -- Keyboard shortcuts in the popup - Firefox, if MV3 APIs and store review stay aligned with Chrome/Edge Curate stays private. A public feed or social sharing is out of scope unless that decision changes in an issue. diff --git a/docs/security-audit.md b/docs/security-audit.md index 297c359..d474bae 100644 --- a/docs/security-audit.md +++ b/docs/security-audit.md @@ -7,7 +7,7 @@ | Area | Status | Notes | |------|--------|-------| -| Manifest permissions | Pass | Only `storage` + scoped host permissions | +| Manifest permissions | Pass | `storage`, `activeTab`, and scoped host permissions | | Secrets in bundle | Pass | No `.env`, JWT secret, or DB credentials in extension | | CSP | Pass | `script-src 'self'`, no inline scripts | | XSS / innerHTML | Mitigated | User content escaped before DOM insertion in popup/options | @@ -22,6 +22,7 @@ | Permission | Justification | |------------|---------------| | `storage` | Persist auth token, theme, API URL preference | +| `activeTab` | Read the current tab URL/title after a user gesture so Add bookmark can prefill http(s) pages | | `host_permissions` (production URL) | HTTPS API calls to deployed Curate backend | | `host_permissions` (localhost) | Local development only | diff --git a/docs/store-readiness.md b/docs/store-readiness.md index cc961c9..9d20c51 100644 --- a/docs/store-readiness.md +++ b/docs/store-readiness.md @@ -26,13 +26,14 @@ The extension **does not**: - Sell data - Run analytics SDKs - Inject content scripts into arbitrary pages -- Read browsing history (no `tabs` / `history` permission) +- Read browsing history (no `tabs` / `history` permission; `activeTab` only exposes the current tab after a user gesture) ## Permissions justification (for store review) Copy into store submission: > **storage** - Saves your login token and extension preferences on your device. +> **activeTab** - Reads the current tab title and URL after you click the toolbar icon or use the add-bookmark shortcut, so the composer can prefill http(s) pages. > **host_permissions** - Allows the extension to sync bookmarks with the Curate server. ## Privacy policy requirements diff --git a/extension/README.md b/extension/README.md index e4f20b7..52001a6 100644 --- a/extension/README.md +++ b/extension/README.md @@ -73,9 +73,21 @@ API connection (environment and base URL) is developer-only. Open it from `chrom See [docs/architecture.md](../docs/architecture.md) and [docs/development.md](../docs/development.md). +## Keyboard shortcuts + +These fit the popup's actual views. Existing mouse and touch actions are unchanged. + +| Shortcut | Where | Action | Why this binding | +|----------|--------|--------|------------------| +| `Alt+Shift+S` (`Option+Shift+S` on macOS) | Browser (toolbar command) | Open the popup on **Add a bookmark** and prefill title/URL from the current tab when it is `http` or `https` | Unused by Chrome/Edge chrome. Avoids `Ctrl+T`, `Ctrl+W`, `Ctrl+L`, and `Ctrl+Shift+B` (bookmarks bar). Remap or disable at `chrome://extensions/shortcuts` or `edge://extensions/shortcuts`. | +| `Esc` | Popup | Close the add/edit composer (`addForm` / `collectionAddForm`). If no composer is open, go back from a collection or Account to the library. On the library with no form open, the browser still closes the popup. | Standard cancel/back. `preventDefault` runs only when Curate handles the key. | + +`activeTab` is used so the command (and the + Add composer) can read the current tab's URL and title after a user gesture. The extension does not request `tabs` or `history`, and it does not prefill `chrome://`, `edge://`, `about:`, or other non-http(s) pages. + ## Permissions - `storage` - auth token and preferences +- `activeTab` - current tab URL and title, only after you click the toolbar icon or use the add-bookmark command - Host permissions - Curate API (production + localhost for dev) -No content scripts. No broad site access. +No content scripts. No broad site access. No `tabs` or `history` permission. diff --git a/extension/background/service-worker.js b/extension/background/service-worker.js index f232890..60cf5f8 100644 --- a/extension/background/service-worker.js +++ b/extension/background/service-worker.js @@ -1,13 +1,53 @@ /** - * MV3 service worker - auth coordination and install lifecycle. + * MV3 service worker - auth coordination, install lifecycle, and commands. * Does not assume persistent execution. */ +import { + COMMANDS, + MESSAGE_TYPES as SHORTCUT_MESSAGES, + PENDING_ADD_KEY, + createPendingAddPayload, + queryActiveTabPage, +} from '../shared/shortcuts.mjs'; + const MESSAGE_TYPES = { GET_AUTH_STATE: 'GET_AUTH_STATE', CLEAR_AUTH: 'CLEAR_AUTH', }; +function pendingStorage() { + return chrome.storage.session || chrome.storage.local; +} + +async function stashPendingAddBookmark(page) { + await pendingStorage().set({ + [PENDING_ADD_KEY]: createPendingAddPayload(page), + }); +} + +chrome.commands.onCommand.addListener(async (command) => { + if (command !== COMMANDS.ADD_BOOKMARK) return; + + const page = await queryActiveTabPage(chrome.tabs); + await stashPendingAddBookmark(page); + + try { + await chrome.action.openPopup(); + } catch { + // Older Chromium, or the popup is already visible. + } + + try { + await chrome.runtime.sendMessage({ + type: SHORTCUT_MESSAGES.OPEN_ADD_BOOKMARK, + ...page, + }); + } catch { + // No listener when the popup is closed; boot reads storage instead. + } +}); + chrome.runtime.onInstalled.addListener((details) => { if (details.reason === 'install' || details.reason === 'update') { console.info('[Curate] Extension installed/updated:', details.reason); diff --git a/extension/manifest.json b/extension/manifest.json index 8ae54c7..7ee954c 100644 --- a/extension/manifest.json +++ b/extension/manifest.json @@ -29,8 +29,18 @@ "type": "module" }, "permissions": [ - "storage" + "storage", + "activeTab" ], + "commands": { + "add-bookmark": { + "suggested_key": { + "default": "Alt+Shift+S", + "mac": "Alt+Shift+S" + }, + "description": "Add a bookmark from the current tab" + } + }, "host_permissions": [ "http://localhost:3000/*", "https://curate-h0ga.onrender.com/*" diff --git a/extension/options/options.html b/extension/options/options.html index b74b7ea..7962c01 100644 --- a/extension/options/options.html +++ b/extension/options/options.html @@ -37,6 +37,21 @@
Popup shortcuts are listed in the extension Account view. The save command can be remapped or disabled from chrome://extensions/shortcuts or edge://extensions/shortcuts.