diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index fe9330e..c185b22 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -1,102 +1,17 @@
## Description
-Provide a brief description of the changes made in this pull request.
+
-## Type of Change
+## Validation
-- [ ] Bug fix (non-breaking change which fixes an issue)
-- [ ] New feature (non-breaking change which adds functionality)
-- [ ] Breaking change (fix or feature that would cause existing functionality to change)
-- [ ] Documentation update
-- [ ] Code refactoring
-- [ ] Performance improvement
-- [ ] Test coverage improvement
+
-## Related Issues
+## Risks and rollback
-- Closes #(issue number)
-- Fixes #(issue number)
-- Relates to #(issue number)
-
-## Changes Made
-
-### Core Changes
-- [ ] List specific changes made to core functionality
-- [ ] Include any architectural changes
-- [ ] Note any new dependencies added
-
-### UI/UX Changes
-- [ ] Describe any user interface changes
-- [ ] Include screenshots if applicable
-- [ ] Note any accessibility improvements
-
-### API Changes
-- [ ] Document any API changes
-- [ ] Note breaking changes
-- [ ] Update version numbers if needed
-
-## Testing
-
-### Automated Testing
-- [ ] Unit tests pass (`npm test`)
-- [ ] E2E tests pass (`npm run test:e2e`)
-- [ ] Coverage threshold met (`npm run test:coverage`)
-- [ ] Linting passes (`npm run lint`)
-- [ ] Type checking passes (`npm run typecheck`)
-
-### Manual Testing
-- [ ] Extension loads without errors in Chrome
-- [ ] Extension loads without errors in Edge
-- [ ] Context menus function correctly
-- [ ] Popup opens and functions correctly
-- [ ] Background service worker initializes
-- [ ] Content scripts inject properly
-- [ ] AI providers respond correctly
-- [ ] Error handling works as expected
-
-### Browser Compatibility
-- [ ] Chrome (version 88+)
-- [ ] Edge (version 88+)
-- [ ] Tested in incognito/private mode
-
-## Security Considerations
-
-- [ ] Input validation implemented
-- [ ] XSS prevention measures in place
-- [ ] API keys handled securely
-- [ ] Content Security Policy updated if needed
-- [ ] No sensitive data exposed in logs
-
-## Performance Impact
-
-- [ ] No significant performance degradation
-- [ ] Memory usage is reasonable
-- [ ] API calls are optimized
-- [ ] Caching implemented where appropriate
-- [ ] Bundle size impact is minimal
-
-## Documentation
-
-- [ ] README.md updated if needed
-- [ ] CONTRIBUTING.md updated if needed
-- [ ] Code comments added for complex logic
-- [ ] JSDoc comments added for new functions
-- [ ] CHANGELOG.md updated
+
## Screenshots
-
-
-## Additional Notes
-
-
-
-## Checklist
-
-- [ ] I have performed a self-review of my code
-- [ ] I have commented my code, particularly in hard-to-understand areas
-- [ ] I have made corresponding changes to the documentation
-- [ ] My changes generate no new warnings
-- [ ] I have added tests that prove my fix is effective or that my feature works
-- [ ] New and existing unit tests pass locally with my changes
-- [ ] Any dependent changes have been merged and published
\ No newline at end of file
+
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 383bf07..9f5673f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
+- Replace fictional contribution examples, consolidate setup and validation
+ guidance, and exclude development configs from the npm package (#35).
- Pin verified current CI action releases, select Ubuntu 26.04 explicitly, and
avoid retaining checkout credentials while preserving the Node test matrix (#32).
- Preserve saved choices when settings reads fail: suppress optional notices and
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index cec4a46..760e9ab 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,332 +1,113 @@
# Contributing to GenAI Browser Tool
-
-
-
-
-
-
-[Quick Start](#quick-start) • [Dev Setup](#development-setup) • [Commit Rules](#commit-guidelines) • [Code Style](#code-style-guidelines) • [Testing](#testing-requirements) • [PR Process](#pull-request-workflow)
-
-
-
-## Table of Contents
-
-
-Expand
-
-- [Quick Start](#quick-start)
-- [Development Setup](#development-setup)
-- [Project Structure](#project-structure)
-- [Branching Model](#branching-model)
-- [Commit Guidelines](#commit-guidelines)
-- [Code Style Guidelines](#code-style-guidelines)
-- [Security & Privacy](#security--privacy)
-- [Testing Requirements](#testing-requirements)
-- [Pull Request Workflow](#pull-request-workflow)
-- [Review Process](#review-process)
-- [Release Process](#release-process)
-- [Issue Triage](#issue-triage)
-- [Community Expectations](#community-expectations)
-
-
-
## Quick Start
-1. Fork the repo and create your branch from `main`:
- ```bash
- git clone https://github.com/YOUR_USERNAME/GenAI-Browser-Tool
- cd GenAI-Browser-Tool
- git checkout -b feature/awesome-change
- ```
-2. Install dependencies and prepare hooks:
- ```bash
- npm ci
- npm run prepare
- ```
-3. Load the unpacked extension from the **project root** at `chrome://extensions`.
- No build step is needed — the manifest points at the source files.
-4. Write tests, run `npm run verify`, then open a PR.
+Fork the repository, clone your fork, and create a short-lived branch from current
+`main`. The extension loads directly from the repository root; see the
+[installation guide](README.md#start-here).
## Development Setup
-### Prerequisites
-
-- Node.js >= 20.19, npm >= 10
-- Chrome >= 116 (MV3 service worker modules)
-- Git
+Use Node >=20.19, npm >=10, and Chrome >=116. Run `npm ci` to install
+development tools and prepare Git hooks. Provider keys and model overrides belong
+in the extension's options page, not an `.env` file.
-### Configuration
-
-There is no `.env` file. Provider selection, API keys, and model overrides all
-live on the extension's options page and are stored in `chrome.storage.sync`.
-
-### Common scripts
-
-```bash
-npm run verify # lint + typecheck + test — run before every PR
-npm test # Unit and integration tests (Vitest)
-npm run test:e2e # Playwright E2E
-npm run lint # ESLint
-npm run typecheck# tsc --noEmit over shipped source (must be zero errors)
-npm run format # Prettier
-```
-
-There is no build step. The repository root is the loadable extension, and
-packaging is a zip of the root. Requires Node 20.19 or newer.
+[Local validation](docs/DEVELOPMENT.md#local-validation) is the authoritative
+command list. Run `npm run verify` before a PR and the coverage/browser checks
+appropriate to the change. Avoid repository-wide formatting unrelated to your work.
## Project Structure
-```
-GenAI-Browser-Tool/
-├── background.js # MV3 service worker — message router, all AI calls
-├── content.js # content script, read-only DOM extraction
-├── popup.html # popup markup (logic in scripts/popup-main.js)
-├── options.html/.css/.js # settings UI
-├── core/
-│ ├── configuration-manager.js # settings + AI client construction
-│ └── tasks.js # prompt construction and response parsing
-├── providers/
-│ └── ai-client.js # one fetch client, shaped per provider
-├── scripts/popup-main.js # popup logic
-├── services/ # storage, notifications
-├── utils/ # logger, validation
-├── styles/ # popup CSS
-├── tests/ # unit, integration, and e2e tests
-├── docs/ # DEVELOPMENT.md, SECURITY.md
-└── manifest.json # MV3 manifest
-```
+See the [source map and message contract](docs/DEVELOPMENT.md#source-map).
+All provider calls go through the worker and `providers/ai-client.js`.
-### Adding a provider
-
-Add one entry to `PROVIDERS` in `providers/ai-client.js` (url, headers, body,
-parse), add its host to `host_permissions` in `manifest.json`, and add the key
-and model inputs to `options.html`. Everything else — options wiring, status
-badge, and all seven tasks — picks it up automatically. Add request-shaping
-tests alongside the existing ones in `tests/providers/ai-client.test.js`.
+To add a provider, update `PROVIDERS`, its manifest host permission, and the
+options key/model inputs. Exercise request shaping and response/error handling in
+[the provider tests](tests/providers/ai-client.test.js).
## Branching Model
-Create short-lived feature, fix, documentation, or maintenance branches from
-current `main`, and target the final integration PR at `main`. This repository
-does not use a long-lived `develop` branch. A stacked PR may target an unmerged
-feature branch; retarget it at `main` when its base lands. Branch protection is
-a repository setting, not a guarantee made by this guide.
+Target final integration PRs at `main`; there is no long-lived `develop` branch.
+A stacked PR may target an unmerged feature branch. Retarget when its base lands;
+the [ancestry check](docs/DEVELOPMENT.md#pull-requests-and-packaging) describes
+what CI detects.
## Commit Guidelines
-Follow Conventional Commits. Examples:
-
-```
-feat(provider): add Gemini fallback when OpenAI is rate-limited
-fix(content): sanitize HTML before summarization to avoid XSS
-perf(cache): memoize provider health checks
-docs(readme): add manual install steps for Firefox
-chore(ci): add CodeQL security scan
-```
-
-Rules:
-
-- Imperative mood, max 50 chars subject, no trailing period
-- Optional body wraps at 72 cols; explain motivation, approach, tradeoffs
-- Reference issues: `Closes #123`, `Refs #456`
-- Breaking changes in footer: `BREAKING CHANGE: ...`
+Use Conventional Commits, for example `fix(content): handle empty pages` or
+`docs(readme): clarify unpacked installation`. Keep the subject imperative,
+at most 50 characters, without a trailing period. Wrap an optional body at 72
+columns, explain consequential trade-offs, and link issues. Mark incompatible
+changes with a `BREAKING CHANGE:` footer.
## Code Style Guidelines
-### JavaScript/TypeScript
-
-- Use TypeScript types in new/changed files when possible
-- Prefer pure functions, avoid side effects in providers/services
-- Validate message boundaries and escape rendered output; see [the security guide](docs/SECURITY.md)
-- No direct DOM injection; sanitize with DOMPurify before rendering
-- Avoid long functions (>40 lines). Extract helpers.
-
-Example patterns:
-
-```ts
-// Typed provider request
-export interface AIRequest {
- kind: 'summary' | 'qa' | 'translate' | 'analyze';
- text: string;
- locale?: string;
-}
-export interface AIResponse {
- content: string;
- tokens?: number;
- latencyMs?: number;
- sources?: string[];
-}
-
-export async function summarize(req: AIRequest): Promise {
- if (req.kind !== 'summary') throw new Error('invalid kind');
- // ... impl
- return { content: result.text, tokens: result.usage?.total_tokens };
-}
-```
-
-### UI/UX
-
-- Keep popup UI responsive and accessible (ARIA labels, tab order)
-- Respect prefers-color-scheme; ensure contrast and keyboard navigation
-- No blocking operations on UI thread; use async boundaries and spinners
-
-### Security & Privacy
-
-- Do not log API keys or user content
-- Treat `chrome.storage.sync/local` as plaintext; keep API keys out of logs and exports
-- Enforce CSP in manifest; never eval or inline scripts
-- Strip PII before sending to external APIs where feasible
+Follow adjacent JavaScript and JSDoc conventions; ESLint, TypeScript's
+`--noEmit` check, and Prettier are already configured. Prefer cohesive helpers
+and test observable behavior. Keep UI controls keyboard accessible and show
+progress/errors for asynchronous actions.
## Security & Privacy
-All contributions must comply with:
-
-- Manifest V3 policies (no remote code, background service worker)
-- Chrome Web Store / Edge Add-ons policies
-- Minimum-permission principle
-
-Checklist before PR:
-
-- [ ] No secrets in code or history
-- [ ] Inputs validated and sanitized
-- [ ] External requests over HTTPS with proper hosts
-- [ ] Respect user opt-outs and incognito isolation
+Follow the [security policy](docs/SECURITY.md). Validate message boundaries,
+preserve the manifest CSP, and escape untrusted output before adding the fixed
+Markdown subset. Rendering uses a local escaping function, with no DOMPurify.
+Keep API keys out of logs and exports, and user content out of logs; storage is plaintext at the
+API level. Review permissions, provider hosts, and privacy opt-outs when changing
+those paths. Report vulnerabilities privately using the policy's contact.
## Testing Requirements
-CI enforces coverage over all shipped JavaScript: 88% lines, 85% statements,
-85% functions, and 78% branches, plus a 75% branch floor for `background.js`.
-See [vitest.config.js](vitest.config.js). Additional goals:
-
-- Unit: ≥ 80% lines on changed modules
-- E2E: happy paths for popup, summary, Q&A, translate
+Use the existing Vitest and Playwright suites. For example, this case in
+[rendering.test.js](tests/popup/rendering.test.js) exercises the shipped renderer:
-Examples:
-
-```ts
-// Vitest unit example
-import { describe, it, expect } from 'vitest';
-import { sanitize } from '@/services/security-manager';
-
-describe('security', () => {
- it('removes scripts from HTML', () => {
- expect(sanitize('
')).not.toMatch(/onerror/);
- });
+```js
+it('escapes tags in model output before adding markup', () => {
+ const html = renderMarkdown('
');
+ expect(html).not.toContain('
{
- await page.goto('https://example.com');
- // open extension UI per your test harness
- await expect(page.locator('.genai-popup')).toBeVisible();
- await page.click('#summarize');
- await expect(page.locator('.summary-result')).toHaveText(/summary/i, {
- timeout: 10000,
- });
-});
-```
-
-CI checks must pass: lint, typecheck, unit, e2e (where configured).
+Run that file with `npx vitest run tests/popup/rendering.test.js`.
+For browser tests, reuse the [persistent Chromium fixture](tests/e2e/fixtures.js)
+and [workspace examples](tests/e2e/learning-workspace.spec.js).
+Coverage thresholds live in [vitest.config.js](vitest.config.js);
+[validation limits](docs/DEVELOPMENT.md#local-validation) distinguish intercepted
+provider tests from manual/live checks.
## Pull Request Workflow
-Target `main` for the final integration PR. Stacked PRs can target a feature
-branch while it has unmerged work; CI rejects a base already contained in the
-default branch and asks you to retarget. See
-[the development guide](docs/DEVELOPMENT.md#pull-requests-and-packaging) for the
-check's scope and packaging rules.
-
-Pre‑flight:
-
-- [ ] Lint, typecheck, tests pass locally
-- [ ] Updated README/docs for user‑facing changes
-- [ ] Added/updated tests
-- [ ] Coverage did not regress (`npm run test:coverage` enforces a threshold)
-
-PR Description Template:
-
-```markdown
-## Summary
-
-What changed and why.
-
-## Changes
-
-- ...
-
-## Tests
-
-- Unit:
-- E2E:
-
-## Screenshots
-
-(If UI changed)
-
-## Risks & Rollback
-
-Potential impact and how to revert.
-
-## Related
-
-Closes #123, Refs #456
-```
-
-Labels:
-
-- `bug`, `enhancement`, `documentation`, `dependencies`, `security`, `breaking change`, `good first issue`
+Use the [PR template](.github/pull_request_template.md). Describe the problem,
+linked issue, actual validation, gaps, and material risks. Include screenshots
+when the UI changes. Add regression coverage for behavior changes and update the
+relevant canonical guide.
## Review Process
-For reviewers:
-
-- Focus on correctness, security, privacy, and maintainability
-- Suggest concrete improvements; prefer questions over directives
-- Approve if concerns are addressed even if further polish is possible
-
-For authors:
-
-- Respond to all comments
-- Keep commits clean (squash before merge unless history is valuable)
-- Provide benchmarks when touching performance‑critical paths
+Review correctness, security, privacy, compatibility, and maintainability.
+Support findings with concrete behavior and tight source/test references.
+Authors should address each actionable comment and provide measurements for
+performance claims. Squash before merge unless the commit history is useful.
## Release Process
-We use SemVer and align `manifest.json` version with `package.json`.
-
-Steps:
-
-1. Update versions: `npm version patch|minor|major`
-2. Update CHANGELOG.md
-3. Smoke test: `npm run verify && npm run test:e2e`
-4. Load the repository root unpacked in Chrome and summarize a real page —
- `activeTab` is granted only on a real toolbar click, which the E2E suite
- cannot perform
-5. Tag and push: `git push --follow-tags`
-6. Download the ZIP from the CI `package` job's Actions artifact; create a GitHub
- release separately if distributing it
+1. Align versions in `package.json`, `package-lock.json`, and `manifest.json`.
+ `npm version` does not update the Chrome manifest.
+2. Update [CHANGELOG.md](CHANGELOG.md) and run the
+ [release checks and packaging workflow](docs/DEVELOPMENT.md#pull-requests-and-packaging).
+3. Load the extension in Chrome and summarize a real page after a toolbar click;
+ automated tests cannot grant that exact `activeTab` interaction.
+4. Tag/push an approved release and distribute the inspected CI ZIP through a
+ separate GitHub release. A merge is not publication to the Chrome Web Store.
## Issue Triage
-Triage labels we use:
-
-- `needs repro`, `blocked`, `security`, `performance`, `help wanted`, `good first issue`
-
-When filing a bug, include:
-
-- Repro steps, expected vs actual, console logs (redacted), browser version, OS, extension version.
+Report bugs with reproduction steps, expected/actual results, extension/browser
+versions, OS, and redacted logs. Link existing issues rather than duplicating
+them; keep security disclosures private.
## Community Expectations
-- Be kind, be specific, be constructive.
-- Follow our [Code of Conduct](CODE_OF_CONDUCT.md).
-- Respect privacy and ethical AI usage in proposals and code.
-
----
-
-Thank you for contributing to GenAI Browser Tool. Your improvements help thousands browse smarter and safer.
+Be specific and constructive, respect privacy, and follow the
+[Code of Conduct](CODE_OF_CONDUCT.md).
diff --git a/README.md b/README.md
index 1cc8103..48db2c5 100644
--- a/README.md
+++ b/README.md
@@ -1,148 +1,118 @@
# GenAI Browser Tool
-Read a page, ask questions with source passages, and turn the material into a
-study guide or a quiz. A Chrome 116+ extension using the AI provider and API key
-you choose. Page text goes directly to that provider; there is no application
-server or telemetry.
+Read a page, ask questions with source passages, and turn it into a study guide
+or quiz. This Chrome 116+ extension uses the AI provider and key you choose.
+Page text goes directly to that provider; there is no application server or telemetry.

-The screenshot is from an automated browser test with an intercepted AI response.
-It demonstrates the interface, not live model quality.
+The screenshot uses an intercepted test response; it demonstrates the interface,
+not live model quality.
## Start here
+Clone or download this repository:
+
```bash
git clone https://github.com/aaron-seq/GenAI-Browser-Tool.git
-cd GenAI-Browser-Tool
-npm ci
```
1. Open `chrome://extensions`, enable **Developer mode**, and choose **Load
- unpacked** → the repository root. No build step is needed.
-2. In options, select a provider and enter its API key. **Test connection** sends
- one small request using the entered key and model. Settings save automatically
- when a field changes; connection testing is optional.
-3. Open an article and click the extension. Check the word count and page title,
- then generate a summary or ask a question.
-4. Choose **Open workspace** for a full-page reading area that stays open. You can
- also import a UTF-8 `.txt` or `.md` document there. Importing sends no AI request.
-5. Use **Make a study guide** or **Quiz me** in Chat, then ask follow-up questions.
- Expand the sources to check the evidence used for each answer.
-
-Development needs Node >=20.19 and npm >=10. The extension ships no runtime
-dependencies. It is currently installed unpacked, not published in the Web Store.
+ unpacked** → the repository root containing `manifest.json`. No build or
+ dependency installation is needed to use the extension.
+2. In options, select a provider and enter its API key. Fields save automatically.
+ **Test connection** optionally sends a small request with the entered key/model.
+3. Open an article, click the extension, and check its title and word count.
+ Generate a summary or ask a question.
+4. Choose **Open workspace** for a persistent reading tab. Import UTF-8 `.txt`
+ or `.md` files there; importing alone sends no provider request.
+5. In Chat, choose **Make a study guide** or **Quiz me**, ask follow-up questions,
+ and expand the source passages to check the evidence.
+
+Installation is currently unpacked; there is no published Web Store release.
## What works
-| Action | Behavior |
-| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| Summary | Key points, TL;DR, executive, or technical; four lengths. Matching saved results are reused without another provider call. |
-| Chat and study prompts | Search passages across the extracted document, send up to six passages to the model, and show their source labels and excerpts. |
-| Remember this document | Restore the latest summary and up to 30 completed exchanges for the same URL and unchanged text. **Clear this chat** deletes that document's saved exchanges. |
-| Import text | Workspace accepts `.txt`/`.md`, up to 2,000,000 characters and 8 MB. Reimporting the same filename and text restores its thread. |
-| Translate | Choose source and target languages; preferences persist. Translate the page or an opening excerpt. |
-| Analyze | Sentiment, insights, entities, and topic tags; local readability score. |
-| Tools | Local word counts, reading time, headings, links, and JSON export of saved history/settings without API keys. |
-| Quick actions | Right-click a selection/page; `Ctrl+Shift+S` (`Command+Shift+S` on Mac) requests a quick page summary. |
+- **Summary:** four styles and four lengths; matching saved results avoid another
+ provider call.
+- **Chat:** local keyword retrieval selects up to six passages across the document.
+ Answers display source labels and excerpts.
+- **Document memory:** restore the latest summary and up to 30 completed exchanges
+ for the same URL and unchanged text. **Clear this chat** removes its exchanges.
+- **Import:** text/Markdown up to 2,000,000 characters and 8 MB; the same filename
+ and text identify a returning imported document.
+- **Translate/analyze:** saved language preferences, sentiment, insights, entities,
+ and tags. Local tools provide readability, counts, headings, links, and JSON
+ export of history/settings without keys.
+- **Quick actions:** right-click a page/selection or use `Ctrl+Shift+S`
+ (`Command+Shift+S` on Mac) for a quick page summary.
### Long documents and speed
-Chat uses local **BM25 keyword retrieval** over the document, including its end.
-The displayed passage count shows how much evidence reached the model. Broad
-questions without matching words use representative passages from across the
-document. This is retrieval followed by generation, not model training. Synonyms,
-some languages, and questions requiring all chapters can exceed its recall.
-
-Summaries process up to **eight sections of at most 24,000 characters**, with up
-to three section requests in flight and one final merge: at most nine requests
-before retries. Coverage is displayed, including any text omitted. A matching
-summary cache includes the document, provider, model, style, and length.
+Summaries process up to **eight sections of 24,000 characters**, with three
+section requests in flight and a final merge (up to nine requests before retries).
+Coverage notices identify omitted text. Translation, other analyses, and quick
+actions still use the first 24,000 characters; see [#17](https://github.com/aaron-seq/GenAI-Browser-Tool/issues/17).
-Translation, other AI analyses, and quick context-menu actions still use the
-first 24,000 characters. Popup translation/analysis reports partial coverage.
-The quick notification actions are best for short text; use the Summary tab for
-sectioned long-page summaries. Remaining long-task work is tracked in
-[#17](https://github.com/aaron-seq/GenAI-Browser-Tool/issues/17).
-
-AI response time depends on your provider, model, network, and document. The
-[local benchmark](docs/product/RESEARCH.md#performance-evidence) measures retrieval
-only; it does not establish instant book comprehension. PDF, EPUB, scanned books,
-and OCR are [planned separately (#26)](https://github.com/aaron-seq/GenAI-Browser-Tool/issues/26).
+Chat uses local BM25 keyword retrieval, not model training. Synonyms, some languages,
+and questions requiring every chapter can exceed its recall. The
+[retrieval benchmark](docs/product/RESEARCH.md#performance-evidence) does not measure
+provider latency or establish instant book comprehension. PDF/EPUB/OCR are
+[planned separately](https://github.com/aaron-seq/GenAI-Browser-Tool/issues/26).
## Providers and privacy
-| Provider | Default model | Key |
-| --------- | ----------------------- | --------------------------------------------------- |
-| Anthropic | `claude-haiku-5-5` | [Claude Console](https://console.anthropic.com) |
-| OpenAI | `gpt-6-luna` | [API keys](https://platform.openai.com/api-keys) |
-| Google | `gemini-3.5-flash-lite` | [AI Studio](https://aistudio.google.com/app/apikey) |
-
-Model IDs are configurable. Defaults were checked against official documentation;
-live account access, cost, latency, and answer quality have not been measured.
-See [provider evidence and selection](docs/product/RESEARCH.md#provider-selection).
-AI actions require your key and may incur provider charges. Connection testing
-does not gate saving; without a key, local extraction/statistics still work.
-
-API keys are in `chrome.storage.sync`, which is not an encrypted secret store.
-History is local to the browser profile: at most 1,000 summaries and 500 completed
-exchanges, with daily cleanup of entries older than 90 days. **Save history** in
-options disables new saves, cache reuse, and thread restoration. It does not erase
-existing records; clear a document's chat before disabling it if needed. Imported
-full documents and retrieval indexes are not persisted, but saved answers and
-source excerpts may contain sensitive text. JSON export includes saved material.
-If local storage fills, the generated result stays visible with a save warning;
-copy it before closing the tab.
+Choose Anthropic, OpenAI, or Google and optionally override the model in options.
+See [default models and provider evidence](docs/product/RESEARCH.md#provider-selection).
+AI requests require your key and may incur charges. Local extraction/statistics
+work without one; live account access and answer quality have not been evaluated.
+
+Keys live in `chrome.storage.sync`, which is not an encrypted secret store.
+History stays in the browser profile: up to 1,000 summaries and 500 exchanges,
+with daily cleanup of entries older than 90 days. **Save history** off disables
+new saves, summary-cache reuse, and restoration; it does not erase old records.
+Saved answers/source excerpts and JSON exports can contain sensitive text.
+Full imported documents and retrieval indexes are not persisted.
+
+Options apply immediately. Disabling context menus removes right-click actions;
+the shortcut remains available. Disabling notifications suppresses desktop
+notices, including shortcut results; popup/workspace output remains visible.
+Unreadable preferences suppress optional notices/history and cannot be replaced
+with defaults by an update. If saving fails, copy the visible result before closing.
See [security and data handling](docs/SECURITY.md).
-An optional private semantic retrieval service is being researched in
-[#27](https://github.com/aaron-seq/GenAI-Browser-Tool/issues/27). No backend, local
-model, or semantic embedding service is installed by this release.
-
-Options apply immediately: disabling context menus removes the right-click
-actions; the keyboard shortcut remains available. Disabling notifications
-suppresses desktop notices, including errors and shortcut results. Popup and
-workspace results remain visible. See the [preference fix](https://github.com/aaron-seq/GenAI-Browser-Tool/issues/31).
-
-If saved settings cannot be read, the extension reports the failure instead of
-replacing your choices with defaults. It suppresses desktop notices and optional
-history writes. A result already generated stays visible with a save warning;
-copy it before closing. If Settings cannot load, reload that page to retry.
+No semantic backend or local model is installed. Optional private retrieval is
+[research work](https://github.com/aaron-seq/GenAI-Browser-Tool/issues/27).
## Troubleshooting
-| Symptom | Next step |
-| ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
-| No API key / 401 / 403 | Check the selected provider, key, model access, and **Test connection** in options. |
-| 429 / provider unavailable | Check provider quota; transient failures retry twice. Authentication/configuration errors and timeouts are not retried. |
-| Cannot read a page | Browser pages, the Web Store, and PDF viewers are restricted. Dynamic pages may require reloading after the extension is installed. |
-| Changed page starts a new chat | Expected: text and URL identify a document, so another version cannot inherit unrelated answers. |
-| Summary is incomplete | Read the coverage notice. Documents over the eight-section cap need smaller parts. |
-| Changes are missing after an update | Reload at `chrome://extensions`, then reopen the popup/workspace. |
+| Symptom | Next step |
+| ------------------------------ | ------------------------------------------------------------------------------------------------------ |
+| Missing key / 401 / 403 | Check the selected provider, key, model access, and **Test connection**. |
+| 429 / provider unavailable | Check quota. Transient failures retry twice; authentication/configuration errors and timeouts do not. |
+| Cannot read a page | Browser pages, the Web Store, and PDF viewers are restricted. Reload dynamic pages after installation. |
+| Changed page starts a new chat | URL and text identify a document; changed versions start another thread. |
+| Incomplete summary | Read the coverage notice; split documents beyond the eight-section cap. |
+| Settings cannot load | Reload Settings to retry; do not assume defaults reflect saved choices. |
+| Update is missing | Reload the extension at `chrome://extensions`, then reopen its UI. |
## Develop and contribute
+Development needs Node >=20.19 and npm >=10:
+
```bash
+npm ci
npm run verify
-npm run test:coverage
npx playwright install chromium
npm run test:e2e
-npm audit --audit-level=high
-npm run benchmark:retrieval
```
-Browser tests load the actual extension and intercept provider requests; no real
-API key is required. CI checks Node 20/22/24, browser behavior, and an explicitly
-allowlisted runtime ZIP. See [development](docs/DEVELOPMENT.md) and
-[contributing](CONTRIBUTING.md).
-
-The dedicated [product brief](docs/product/PRODUCT.md) records the goal, delivered
-behavior, and next steps. Supporting research and verification are in
-[RESEARCH.md](docs/product/RESEARCH.md); the supplied instructions are preserved
-in [ENGINEERING-BRIEF.md](docs/product/ENGINEERING-BRIEF.md).
-
-Issues: [GitHub](https://github.com/aaron-seq/GenAI-Browser-Tool/issues).
-Current work: [GitHub dashboard](https://github.com/users/aaron-seq/projects/6)
-(owner access), [continuation checkpoint](docs/product/PROJECT-STATUS.md), and
-[settings-failure evidence](docs/security-testing-evidence.md).
-License: [MIT](LICENSE).
+Browser tests load the actual extension with intercepted provider responses.
+See [development](docs/DEVELOPMENT.md) for coverage, audit, benchmark, CI, and
+packaging commands, and [contributing](CONTRIBUTING.md) for change/review rules.
+
+[Product brief](docs/product/PRODUCT.md) · [Research](docs/product/RESEARCH.md) ·
+[Supplied engineering brief](docs/product/ENGINEERING-BRIEF.md) ·
+[Current work](docs/product/PROJECT-STATUS.md) ·
+[GitHub issues](https://github.com/aaron-seq/GenAI-Browser-Tool/issues) ·
+[Private dashboard](https://github.com/users/aaron-seq/projects/6) · [MIT license](LICENSE)
diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md
index 4b8a03d..52e37b1 100644
--- a/docs/DEVELOPMENT.md
+++ b/docs/DEVELOPMENT.md
@@ -1,198 +1,120 @@
# Development Guide
-This guide provides detailed information for developers working on the GenAI Browser Tool extension.
-
## Architecture Overview
-Every AI call originates in the service worker. The popup and the options page
-never talk to a provider, and the content script has no network access at all —
-so there is exactly one place where a request can be shaped, retried, or leak a
-key.
-
-```mermaid
-flowchart TD
- popup["popup.html
scripts/popup-main.js"]
- options["options.html
options.js"]
- menus["Context menus
Ctrl+Shift+S"]
- bg["background.js
service worker — message router"]
- content["content.js
read-only DOM extraction"]
- config["core/configuration-manager.js
chrome.storage.sync"]
- tasks["core/tasks.js
prompt construction"]
- client["providers/ai-client.js
one fetch client, shaped per provider"]
- store["services/storage-service.js
chrome.storage.local history"]
- api["Provider HTTPS API"]
-
- popup -->|chrome.runtime.sendMessage| bg
- options -->|writes settings| config
- options -->|connection test| bg
- menus --> bg
- bg -->|chrome.tabs.sendMessage| content
- content -->|page text| bg
- bg --> config
- bg --> tasks
- config -->|builds a client for
the selected provider| client
- tasks -->|system + user prompt| client
- client -->|fetch, with retry| api
- bg --> store
-```
-
-### Request lifecycle: one summary
+The repository root is a loadable MV3 extension with no build step or runtime
+dependencies. All provider calls originate in the worker; the content script
+only reads the page.
```mermaid
-sequenceDiagram
- participant U as User
- participant P as Popup
- participant B as background.js
- participant C as content.js
- participant A as Provider API
-
- U->>P: Click "Generate Summary"
- P->>B: EXTRACT_PAGE_CONTENT { tabId }
- B->>C: chrome.tabs.sendMessage
- C-->>B: title, mainText, headings
- B-->>P: page content
- P->>B: GENERATE_CONTENT_SUMMARY { content, style, length }
- B->>B: chunkContent() — split if over 24,000 chars
-
- alt page fits one request
- B->>A: one completion
- A-->>B: summary
- else long page (map-reduce, 3 sections in flight)
- loop each section, capped at 8
- B->>A: summarize section
- A-->>B: section notes
- end
- B->>A: merge the notes
- A-->>B: whole-page summary
- end
-
- B->>B: save to local history
- B-->>P: text, provider, model, sections, droppedChars
- P->>U: rendered summary + coverage statement
+flowchart LR
+ UI["Popup / workspace / options"] -->|runtime messages| Worker["background.js"]
+ Menu["Menus / shortcut"] --> Worker
+ Worker -->|tab message| Content["content.js"]
+ Worker --> Config["ConfigurationManager"]
+ Worker --> Tasks["core/tasks.js"]
+ Tasks --> Client["providers/ai-client.js"]
+ Config --> Client
+ Client -->|HTTPS| API["Selected provider"]
+ Worker --> History["StorageService"]
```
-A failure at any provider step is returned with its own message and an error
-code (`MISSING_API_KEY`, `AUTH_ERROR`, `TIMEOUT`, …). Nothing degrades into a
-plausible-looking fake result, and a section that fails after retries fails the
-whole summary rather than producing a partial one presented as complete.
+A summary extracts page text, checks the saved-result key, then either makes one
+request or summarizes up to eight 24,000-character sections (three concurrently)
+and merges the notes. Any failed section fails the task; partial results are not
+presented as complete. Errors retain their code and message.
## Source map
-| File | Responsibility |
-| ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
-| `background.js` | `BackgroundService`: registers listeners synchronously, routes messages, runs AI tasks, and handles menus, commands, and cleanup |
-| `providers/ai-client.js` | `AIClient` and `PROVIDERS`: provider-specific HTTPS requests, response parsing, retries, and timeouts |
-| `core/tasks.js` | Prompt construction, content fencing, chunking, and response parsing |
-| `core/document.js` | Shared 2,000,000-character limit and URL/content SHA-256 identity |
-| `core/retrieval.js` | Local BM25 passage indexing, ranking, and source excerpts |
-| `core/configuration-manager.js` | Settings defaults and persistence; constructs a client for the selected provider |
-| `content.js` | Reads page text, metadata, links, images, and selection without modifying the page |
-| `scripts/popup-main.js` | Popup/workspace controls, text import, scoped restoration, and escaped result rendering |
-| `options.js` | Provider selection, keys, model overrides, appearance, and feature settings |
-| `services/storage-service.js` | Local summary and conversation history |
-| `services/notification-manager.js` | Desktop notifications for background actions |
-
-The selected provider is explicit. `ConfigurationManager.createAIClient()` either
-constructs that provider's `AIClient` or reports a missing key. Default models and
-wire formats live in `PROVIDERS`; there is no provider scoring or automatic
-fallback. See [the provider client](../providers/ai-client.js) before adding an API.
+| Path | Responsibility |
+| ------------------------------- | ------------------------------------------------------------------------------------ |
+| `background.js` | Synchronously registered listeners, message routing, tasks, menus, commands, cleanup |
+| `content.js` | Read-only DOM text, metadata, links, images, selection |
+| `core/configuration-manager.js` | Settings/defaults and construction of the selected client |
+| `core/tasks.js` | Prompts, content fencing, chunking, response parsing |
+| `core/document.js` | Shared document limit and URL/text SHA-256 identity |
+| `core/retrieval.js` | Local BM25 indexing, ranking, source excerpts |
+| `providers/ai-client.js` | Provider wire formats, HTTPS, retries, timeouts |
+| `scripts/popup-main.js` | Popup/workspace, imports, restoration, escaped rendering |
+| `options.js` | Settings UI |
+| `services/` | History persistence and desktop notifications |
+
+Default models and wire formats live in `PROVIDERS`. Selection is explicit:
+a missing key is an error, with no scoring or automatic provider fallback.
## Message contract
Extension pages send `{ actionType, requestId, payload }` through
-`chrome.runtime.sendMessage`. `requestId` is optional and echoed in the response.
-`validateMessage()` requires a non-empty action string and this extension's sender
-ID. The router rejects unsupported actions.
-
-| Action | Input / result |
-| ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
-| `GENERATE_CONTENT_SUMMARY` | `content`, `summaryType`, `targetLength`; returns summary and coverage information |
-| `ANSWER_CONTEXTUAL_QUESTION` | `question`, `context`, optional `conversationHistory`, `url`, `title`; returns answer, sources, and retrieval coverage |
-| `TRANSLATE_CONTENT` | `text`, `targetLanguage`, optional `sourceLanguage` |
-| `ANALYZE_SENTIMENT` | `text`; returns sentiment and reason |
-| `EXTRACT_KEY_INSIGHTS`, `GENERATE_SMART_TAGS`, `EXTRACT_ENTITIES` | `text`; returns the requested analysis |
-| `EXTRACT_PAGE_CONTENT` | `tabId`, falling back to the sender tab; returns content-script extraction |
-| `GET_USER_PREFERENCES`, `UPDATE_USER_PREFERENCES` | Read settings or merge a settings payload |
-| `GET_PROVIDER_STATUS` | Configured status for the selected provider and each provider key |
-| `TEST_PROVIDER_CONNECTION` | Entered `provider`, `apiKey`, optional `model`; one 16-token request, 10-second timeout, zero retries; returns model, never saves key |
-| `GET_DOCUMENT_HISTORY` | `url`, `content`; returns one summary, up to 30 exchanges for that identity, and `memoryEnabled` |
-| `CLEAR_DOCUMENT_CHAT` | `url`, `content`; removes only matching exchanges; returns count |
-| `GET_HISTORY` | Saved summaries and conversations |
-| `EXPORT_USER_DATA` | Schema-v2 object containing settings without API keys and saved history |
-
-Success responses contain `{ success: true, data, requestId, processingTime }`.
-Failures contain `{ success: false, error, errorCode, requestId, processingTime }`.
-The popup passes an explicit tab ID because popup messages have no `sender.tab`.
-The content script uses its separate `{ action: 'extractContent' }` protocol.
+`chrome.runtime.sendMessage`. The optional request ID is echoed.
+[Validation](../utils/validation-service.js) requires an object, a nonempty action,
+and this extension's sender ID; the [router](../background.js) rejects unknown actions.
+
+| Action | Input / result |
+| ----------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
+| `GENERATE_CONTENT_SUMMARY` | `content`, `summaryType`, `targetLength`; summary and coverage |
+| `ANSWER_CONTEXTUAL_QUESTION` | `question`, `context`, optional `conversationHistory`, `url`, `title`; answer, sources, coverage |
+| `TRANSLATE_CONTENT` | `text`, `targetLanguage`, optional `sourceLanguage` |
+| `ANALYZE_SENTIMENT` | `text`; sentiment and reason |
+| `EXTRACT_KEY_INSIGHTS`, `GENERATE_SMART_TAGS`, `EXTRACT_ENTITIES` | `text`; requested analysis |
+| `EXTRACT_PAGE_CONTENT` | `tabId`, falling back to sender tab; extraction |
+| `GET_USER_PREFERENCES`, `UPDATE_USER_PREFERENCES` | Read settings or merge a settings payload |
+| `GET_PROVIDER_STATUS` | Selected provider and configured-key status |
+| `TEST_PROVIDER_CONNECTION` | Entered `provider`, `apiKey`, optional `model`; 16-token request, 10s timeout, no retries; never saves key |
+| `GET_DOCUMENT_HISTORY` | `url`, `content`; summary, up to 30 exchanges, `memoryEnabled` |
+| `CLEAR_DOCUMENT_CHAT` | `url`, `content`; removes matching exchanges, returns count |
+| `GET_HISTORY` | Saved summaries and conversations |
+| `EXPORT_USER_DATA` | Schema-v2 settings/history object, excluding API keys |
+
+Success: `{ success: true, data, requestId, processingTime }`.
+Failure: `{ success: false, error, errorCode, requestId, processingTime }`.
+Popup messages have no `sender.tab`, so send an explicit tab ID.
+The content script separately accepts `{ action: 'extractContent' }`.
## Storage and security
-Settings live in `chrome.storage.sync.user_preferences`, including `apiKeys`,
-`models`, `preferredProvider`, summary preferences, source/target languages, theme,
-and feature flags.
-API keys are plaintext at the storage API level and sync with the browser profile.
-They are sent only to the selected provider in HTTPS headers and excluded from
-exports. See [SECURITY.md](SECURITY.md) for the trust boundaries.
-
-History lives in `chrome.storage.local` under `genai_summary_history` (up to 1,000
-entries) and `genai_conversation_history` (up to 500). A daily alarm removes entries
-older than 90 days; entries without timestamps are retained. Settings have one
-reader/writer, `ConfigurationManager`; history belongs to `StorageService`.
+`ConfigurationManager` owns `chrome.storage.sync.user_preferences`:
+keys, models, provider, summary/language/theme preferences, and feature flags.
+Keys are plaintext at the API level, sync with the browser profile, and are
+excluded from exports. Unreadable preferences cannot authorize optional effects
+or replacement updates. See [security and data handling](SECURITY.md).
-Model output is escaped before the popup's small Markdown subset is applied.
-Page text is fenced as untrusted data. Questions are limited to 1,000 characters;
-single-request tasks use at most 24,000 context characters. Summaries can process
-up to eight sections with three section requests in flight, then merge their notes.
-There is no telemetry backend or production bundle.
+`StorageService` owns local `genai_summary_history` (1,000 entries) and
+`genai_conversation_history` (500). A daily alarm prunes entries older than
+90 days; entries without timestamps remain. Mutations serialize per key in the
+worker, including clear-chat and cleanup. A failed mutation read propagates
+instead of overwriting history with an empty list; read-only access retains its
+logged fallback.
### Document retrieval, cache, and memory
-The worker hashes URL plus trimmed text to identify a document. A changed page or
-different URL starts a new thread. Older unscoped history remains exportable but
-is not restored into a page. Full text imports and indexes are not persisted.
+Identity hashes URL plus trimmed text. Changed text/URLs start another thread;
+old unscoped records remain exportable without being restored. Full imports and
+indexes are not persisted.
-```mermaid
-flowchart LR
- doc["Page or text import ≤2M characters"] --> id["URL/content identity"]
- id --> index["BM25 index, up to 3 cached documents"]
- question["Question + previous user wording"] --> index
- index --> passages["Up to 6 passages, source labels"]
- passages --> prompt["Fenced question prompt"]
- prompt --> api["Selected provider"]
- api --> saved["Answer + focused source excerpts in local history"]
-```
+Retrieval selects up to six 2,800-character passages with 200-character overlap.
+It uses question and prior user wording; no-match queries sample across the
+document. This is lexical retrieval, with no guarantee of semantic or exhaustive
+recall. The three-document cache evicts the oldest insertion and disappears on
+worker suspension. Restoration returns at most 30 exchanges; prompts use bounded
+recent conversation. See [retrieval research](product/RESEARCH.md).
+
+Summary keys include `summary-v1`, document identity, provider, model, style,
+and length. Bump the version when summary prompt meaning changes. Save history
+off disables saves, cache reuse, and restoration; it does not delete records.
+The worker checks the preference again before saving an in-flight result.
-Passages are at most 2,800 characters with 200-character overlap. Retrieval is
-lexical; no-match queries sample across the document. It does not guarantee
-semantic recall or whole-book coverage. The index cache evicts the oldest inserted
-document at three entries and is lost on worker suspension. The next question
-rebuilds it. Restored exchanges use at most 30 turns; prompts use the bounded
-recent conversation in `core/tasks.js`.
-
-Summary cache keys include `summary-v1`, identity, provider, model, style, and
-length. Bump the cache version if summary prompt meaning changes. The request
-lifecycle above is skipped on a matching saved result. Save-history off disables
-new saves, summary cache reuse, and restoration; it does not erase past records.
-The preference is read again before persisting an in-flight result.
-
-Storage read-modify-write operations are serialized per history key in the single
-worker. Clear-chat and daily cleanup use the same queues. A failed mutation read
-propagates rather than overwriting history with an empty list. Read-only history
-access retains its logged fallback behavior.
-Generated summaries are stored once as `summary`; `text` is reconstructed for the
-response contract. If a history write fails (including storage quota), summary
-and chat results still reach the UI with a visible warning to copy the result.
-No second AI request is made just to retry persistence.
-
-`popup.html?workspace=1&tabId=123` uses an explicit source tab; ordinary popup
-startup queries the active tab. Workspace text/Markdown import validates an 8 MB
-file limit and the shared character limit. Import and extraction trigger no AI
-request. Imported material uses a `local-document:` filename identity plus its
-content hash. File content is read only after the user chooses it.
+Summaries are stored once as `summary`; response `text` is reconstructed.
+History/quota failures leave generated summary/chat output visible with a save
+warning, without another provider request.
+
+`popup.html?workspace=1&tabId=123` binds a workspace to its source tab; ordinary
+popup startup queries the active tab. User-selected UTF-8 text/Markdown imports
+validate 8 MB and 2,000,000-character limits. Their identity uses
+`local-document:`, filename, and content hash. Extraction/import sends no AI request.
## Local validation
-Use Node >=20.19 and npm >=10, as declared in [package.json](../package.json).
+Use Node >=20.19 and npm >=10.
```bash
npm ci
@@ -204,49 +126,47 @@ npm audit --audit-level=high
npm run benchmark:retrieval
```
-Vitest imports the shipped controllers and mocks `chrome.*` and provider fetches.
-Coverage includes all shipped JavaScript; the global branch threshold is 78%,
-with a separate 75% floor for `background.js`. Other thresholds are in
-[vitest.config.js](../vitest.config.js).
+`verify` runs lint, typecheck, and Vitest. Tests import shipped controllers and
+mock Chrome APIs/provider fetches. [Coverage floors](../vitest.config.js) cover
+all shipped JavaScript: statements/functions 85%, lines 88%, branches 78%, and
+worker branches 75%.
-Playwright loads the actual extension in persistent Chromium contexts. Fixture
-pages and provider responses are intercepted. A real toolbar-icon click granting
-`activeTab` remains a manual release check; intercepted tests do not demonstrate
-live provider credentials, model availability, or output quality.
+Playwright uses the actual unpacked extension in persistent Chromium contexts,
+with intercepted fixture pages/provider responses. A real toolbar click granting
+`activeTab` remains a manual release check. These tests do not verify live keys,
+model access/quality, native OS notices, or minimum-browser compatibility.
## Pull requests and packaging
-CI runs lint, typechecking, and coverage on Node 20/22/24; Node 22 also runs the
-dependency audit. Browser tests run before the package job.
-
-The workflow selects `ubuntu-26.04` explicitly and pins action release commits.
-Action runtimes are separate from the Node versions tested by the project.
-Checkout credentials are not retained after checkout; the ancestry guard uses
-the full fetched history. npm caching uses `package-lock.json` explicitly, and
+[CI](../.github/workflows/ci.yml) runs lint, types, and coverage on Node 20/22/24;
+Node 22 also audits dependencies. Browser tests precede packaging.
+Ubuntu 26.04 and action release SHAs are explicit. Action execution runtimes are
+separate from the application's Node matrix. Checkouts do not persist credentials;
+the ancestry guard fetches full history. Install jobs cache npm using the lockfile;
the dependency-free guard disables automatic package-manager caching.
-See [migration decisions and validation](ci-modernization-evidence.md) before
-updating these pins. Codecov remains optional; a green job alone does not prove
-that coverage reached the service.
-
-The PR-base job rejects a non-default base branch already contained in the default
-branch. Retarget the PR at the default branch when this fails. The check runs on
-opening, reopening, head updates, and edits (including retargeting). It checks Git
-ancestry at workflow time; squash/rebase equivalence and future base merges are
-not continuously monitored. Tests in `tests/ci/pr-base.test.js` exercise the guard
-against temporary Git histories.
-
-The repository root is the loadable extension. Load it unpacked at
-`chrome://extensions`, then reload after source edits. Legacy `dist/` and `build/`
-output are ignored by Git and ESLint and can be left in an older checkout.
-
-The CI ZIP explicitly includes `manifest.json`, the runtime root JavaScript and
-HTML/CSS files, `assets/`, `core/`, `providers/`, `scripts/`, `services/`, `styles/`,
-`utils/`, `README.md`, and `LICENSE`. Keep that list in
-[ci.yml](../.github/workflows/ci.yml) current when adding runtime files. CI uploads
-the ZIP as an Actions artifact; Chrome Web Store publishing is a separate release
-step. Install and configure the unpacked extension using [README.md](../README.md).
-
-For consolidation decisions and verification evidence, see
-[INTEGRATION-REVIEW.md](INTEGRATION-REVIEW.md).
-For product scope, research, and retrieval measurements, see
-[product/PRODUCT.md](product/PRODUCT.md) and [product/RESEARCH.md](product/RESEARCH.md).
+Codecov is optional: green status alone does not establish upload success.
+See [CI migration evidence](ci-modernization-evidence.md) before changing pins.
+
+The PR-base guard rejects a non-default base already contained in the default
+branch. Retarget after its base lands. It runs on opening, reopening, head updates,
+and edits; it checks workflow-time ancestry, not squash/rebase equivalence or
+future merges. [Tests](../tests/ci/pr-base.test.js) use temporary Git histories.
+The guard is independent of the verify job; required-check policy is a separate
+repository setting.
+
+Load the repository root at `chrome://extensions` and reload after edits.
+Legacy ignored output is not a runtime input.
+The CI ZIP allowlist contains the manifest, root runtime JS/HTML/CSS, assets,
+runtime module directories, README, and LICENSE.
+The npm `files` list in [package.json](../package.json) covers the same runtime;
+npm also includes package metadata. Check both lists when adding runtime files:
+
+```bash
+npm pack --dry-run --json --ignore-scripts
+```
+
+Verify manifest targets, relative imports, and HTML assets in the resulting
+package. CI uploads an Actions artifact; publication/store release is separate.
+See [contribution release steps](../CONTRIBUTING.md#release-process),
+[historical integration evidence](INTEGRATION-REVIEW.md), and the
+[product brief](product/PRODUCT.md).
diff --git a/docs/INTEGRATION-REVIEW.md b/docs/INTEGRATION-REVIEW.md
index e1ae361..528131e 100644
--- a/docs/INTEGRATION-REVIEW.md
+++ b/docs/INTEGRATION-REVIEW.md
@@ -1,124 +1,61 @@
# Integration review
-Reviewed 17 October 2026 against `main` at
+Historical review: 17 October 2026, base
[`b915866`](https://github.com/aaron-seq/GenAI-Browser-Tool/commit/b91586665941d3456f7411557edabc51842892d9).
+The [full original record](https://github.com/aaron-seq/GenAI-Browser-Tool/blob/6e8288550ef4935684a6a44c87cbbed77b818d14/docs/INTEGRATION-REVIEW.md)
+preserves the detailed inventory and run notes; results below are historical,
+not tests rerun by each reader.
## Decision and scope
-No PRs were open when this review began. [PR #24](https://github.com/aaron-seq/GenAI-Browser-Tool/pull/24)
-had already recovered PRs #14 and #15 into `main`. Start the integration branch
-from current `main`, preserve that implementation, and fix validation findings in
-one PR targeting `main`.
+[PR #24](https://github.com/aaron-seq/GenAI-Browser-Tool/pull/24) had recovered
+PRs #14/#15; no PR was open when the review began. Integrate from current main,
+preserve the source-loaded MV3 runtime, and fix demonstrated validation problems.
-Older refs were inspected and preserved. The old `feat/close-open-issues` branch
-contains an alternative test suite superseded by the controllers and coverage
-work in 5.2.0. The January `fix/critical-stub-implementations-and-provider-interface`
-branch adds an unused abstract provider class for the removed provider system.
-Neither represents an open PR or a reason to restore that architecture.
-
-## Engineering review before implementation
-
-| Area | Finding / decision |
-| ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| Known facts | Latest `main` contains the recovered functionality; the local checkout was behind. Baseline typecheck and 219 tests passed. |
-| Unknown facts | Live credential validity, provider model availability, and real toolbar permission behavior were not demonstrated. |
-| Assumptions | `main`, confirmed as the repository default, is the requested integration destination. Preserve existing extension behavior. |
-| Clarification questions | None required for this integration; merge authorization was explicit. |
-| Success criteria | Lint, types, coverage, browser tests, audit, package inspection, and PR CI pass before merging. |
-| Root cause | Earlier stacked work targeted a base already merged; CI validated code without checking whether its destination still needed integration. |
-| Architecture | Keep the source-loaded MV3 extension and one selected-provider client. No build system or runtime dependencies added. |
-| Security | Patch vulnerable development dependencies; use read-only CI permissions; keep branch names out of shell interpolation; package explicit runtime paths. |
-| Performance / scalability | Keep request concurrency, chunk limits, history caps, and retry behavior. No runtime performance changes or new services. |
-| Maintainability | Ignore old generated files, document the real interfaces, and test worker lifecycle behavior through registered listeners. |
-| Testing strategy | Real temporary Git histories for the base guard, existing mocked worker integration tests, coverage floors, and persistent Chromium E2E. |
-| Dependencies | Compatible transitive updates only; no major upgrades, new dependencies, or changes to the shipped extension. |
-| Alternatives | Re-merging superseded branches adds dead code; a validation-only report leaves demonstrated audit and packaging problems unresolved. |
-| Recommendation | Apply the focused fixes and merge one tested integration PR. |
-| Risk | CI ancestry checks observe workflow-time Git history, not patch equivalence or future events; the explicit ZIP input list needs updating for new runtime paths. |
-| Rollback | Revert the integration merge on `main` and rerun CI. Existing settings/history and old branches are preserved. |
-
-Five whys: changes missed `main` because later PRs landed on an old feature branch;
-that branch remained a target after its parent PR merged; stacking was valid when
-work began; checks verified code but not the target's ancestry; there was no
-automated destination check. [Issue #22](https://github.com/aaron-seq/GenAI-Browser-Tool/issues/22)
-records the history and acceptance criteria.
-
-The guard uses [Git's documented `--is-ancestor` status codes](https://git-scm.com/docs/git-merge-base),
-[full-history checkout](https://github.com/actions/checkout#fetch-all-history-for-all-tags-and-branches),
-and [GitHub's read-only workflow permissions](https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#permissions).
-It runs on PR opening, reopening, head updates, and edits. It does not continuously
-monitor future base merges or detect squash/rebase equivalence. Branch protection
-was absent at inspection; this PR adds a check without changing repository policy.
+Preserve older refs without restoring their superseded test suite or unused
+abstract provider implementation. The delivery problem was a stacked base already
+merged into main, not missing runtime functionality.
+[Issue #22](https://github.com/aaron-seq/GenAI-Browser-Tool/issues/22) records the
+ancestry guard; [#23](https://github.com/aaron-seq/GenAI-Browser-Tool/issues/23)
+records worker regression coverage. Current behavior and guard limits belong in
+[DEVELOPMENT.md](DEVELOPMENT.md#pull-requests-and-packaging).
## Validation personally run
-Windows, Node 22.18.0, npm 11.6.2. Provider HTTP calls were mocked/intercepted.
-
-| Check | Baseline | Integration result |
-| ------------------------- | ---------------------------------------------- | -------------------------------------------------- |
-| Dependency installation | `npm ci` succeeded | Compatible lockfile updates installed successfully |
-| Lint | Failed on pre-existing generated `dist/` files | Passed with old output preserved and ignored |
-| Typecheck | Passed | Passed |
-| Unit + integration tests | 219 passed, 11 files | 241 passed, 12 files |
-| Statement / line coverage | 91.28% / 93.88% | 92.80% / 95.59% |
-| Global branch coverage | 77.00%, floor 72% | 80.47%, floor 78% |
-| Worker branch coverage | 57.14% | 83.92%, separate floor 75% |
-| Worker function coverage | 91.66% | 100% |
-| Chromium E2E | Earlier published results are in PR #24 | 13 passed on this branch |
-| `npm audit` | 3 high-severity vulnerable packages | 0 vulnerabilities |
-
-The local extension ZIP contained 22 runtime/documentation files. All manifest
-targets and 16 relative JavaScript/HTML references were present; tooling and
-legacy output were excluded. GitHub's ZIP will also be inspected before merge.
+The original session ran on Windows / Node 22.18.0 / npm 11.6.2, with
+mocked/intercepted provider requests.
-The 22 additional cases cover merged/equal/open/missing Git bases and a renamed
-default branch; context-menu page extraction, absent input and notification size;
-commands with/without an active tab; install/update, startup failure, cleanup and
-unrelated alarms; and unsuccessful extraction and sender-tab fallback. Sources:
-[guard tests](../tests/ci/pr-base.test.js),
-[worker tests](../tests/integration/extension-workflow.test.js), and
-[coverage configuration](../vitest.config.js). These address
-[issue #23](https://github.com/aaron-seq/GenAI-Browser-Tool/issues/23).
+| Check | Before | Integration result |
+| ------------------------ | ------------------------------------------- | ----------------------------------------------- |
+| Lint / types | Lint failed on legacy `dist/`; types passed | Both passed; legacy output retained and ignored |
+| Unit/integration | 219 tests / 11 files | 241 / 12 |
+| Statements / lines | 91.28% / 93.88% | 92.80% / 95.59% |
+| Global / worker branches | 77.00% / 57.14% | 80.47% / 83.92%; floors 78% / 75% |
+| Chromium E2E | Earlier evidence on PR #24 | 13 passed, no retries |
+| npm audit | Three high-severity vulnerable packages | Zero vulnerabilities reported |
-The first Chromium launch was cold; all 13 cases passed without retries. The
-runner emitted an environment warning because both `NO_COLOR` and `FORCE_COLOR`
-were set. This affects terminal coloring, not browser behavior.
+The local ZIP had 22 files and all manifest targets/16 relative runtime references;
+tooling and legacy output were excluded. This is the historical package, not the
+current release count. Added tests used real temporary Git histories and worker
+listeners for menu/command/lifecycle/alarm/extraction branches.
+Run current checks from [local validation](DEVELOPMENT.md#local-validation).
## Dependency decisions
-All changes remain within existing dependency ranges, add no runtime cost or
-bundle content, retain upstream licenses, and require no application migration.
-Rollback restores the prior lockfile, including its known vulnerabilities.
-
-| Dependency | Before → after | Evidence |
-| ----------------- | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `brace-expansion` | 5.0.9 → 5.0.12 | [Expansion DoS advisory](https://github.com/advisories/GHSA-q2hr-2g5m-vwhr), [recursion advisory](https://github.com/advisories/GHSA-qhr7-859c-m2p7) |
-| `source-map-js` | 1.2.1 → 1.2.2 | [Indexed source-map DoS advisory](https://github.com/advisories/GHSA-68fv-2mgg-jv7q) |
-| `undici` | 7.29.0 → 7.30.1 | [TLS validation advisory](https://github.com/advisories/GHSA-w293-vg96-wgc3); the fresh npm audit also reported retry/WebSocket/cache advisories |
-
-## Changed files and operational notes
-
-| Files | Reason |
-| ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
-| `.github/workflows/ci.yml`, `.github/scripts/check-pr-base.mjs` | Base ancestry guard, read-only permissions, dependency audit, explicit ZIP inputs |
-| `.gitignore`, `eslint.config.js` | Preserve and ignore legacy output; recognize the Node-based CI script |
-| `package-lock.json` | Three security updates and root-version alignment with existing package/manifest version 5.2.0 |
-| `tests/ci/pr-base.test.js`, `tests/integration/extension-workflow.test.js`, `vitest.config.js` | Regression cases and higher enforced branch floors |
-| `CONTRIBUTING.md`, `docs/DEVELOPMENT.md`, `CHANGELOG.md`, this review | Accurate prerequisites, actual runtime contracts, integration and validation evidence |
-
-Run the commands in [the development guide](DEVELOPMENT.md#local-validation).
-The ZIP packages runtime inputs explicitly; it must contain every manifest,
-relative import, and HTML asset target. CI produces an Actions artifact, not a
-published store release. No data migration is required.
-
-No performance benchmark, load/stress test, accessibility audit, Edge test, live
-provider evaluation, or real toolbar-click smoke test was performed. Passing the
-available automated checks does not establish those outcomes.
-
-Remaining feature work stays in existing issues:
-[#16](https://github.com/aaron-seq/GenAI-Browser-Tool/issues/16) provider key verification,
-[#17](https://github.com/aaron-seq/GenAI-Browser-Tool/issues/17) long-page task coverage,
-[#18](https://github.com/aaron-seq/GenAI-Browser-Tool/issues/18) translation preferences,
-[#19](https://github.com/aaron-seq/GenAI-Browser-Tool/issues/19) import,
-[#20](https://github.com/aaron-seq/GenAI-Browser-Tool/issues/20) repository formatting,
-and [#21](https://github.com/aaron-seq/GenAI-Browser-Tool/issues/21) persistent chat UI.
+Compatible transitive updates retained licenses, added no runtime dependency,
+and needed no application migration:
+
+- `brace-expansion` 5.0.9 → 5.0.12:
+ [expansion DoS](https://github.com/advisories/GHSA-q2hr-2g5m-vwhr) and
+ [recursion advisory](https://github.com/advisories/GHSA-qhr7-859c-m2p7).
+- `source-map-js` 1.2.1 → 1.2.2:
+ [indexed source-map DoS](https://github.com/advisories/GHSA-68fv-2mgg-jv7q).
+- `undici` 7.29.0 → 7.30.1:
+ [TLS validation advisory](https://github.com/advisories/GHSA-w293-vg96-wgc3);
+ npm also reported retry/WebSocket/cache advisories.
+
+Reverting restores the prior workflow/lockfile, including known vulnerabilities.
+No benchmark, load test, accessibility audit, Edge/live-provider test, or real
+toolbar-click check was performed. CI artifacts are not store publication.
+Remaining product work is maintained in the [product brief](product/PRODUCT.md)
+and [current checkpoint](product/PROJECT-STATUS.md).
diff --git a/docs/SECURITY.md b/docs/SECURITY.md
index e477101..dbf9fa3 100644
--- a/docs/SECURITY.md
+++ b/docs/SECURITY.md
@@ -2,299 +2,142 @@
## Supported Versions
-We actively support the following versions with security updates:
+| Version | Supported with security updates |
+| ------- | ------------------------------- |
+| 5.3.x | Yes |
+| 5.2.x | Yes |
+| 5.1.x | Yes |
+| <5.1 | No |
-| Version | Supported |
-| ------- | ------------------ |
-| 5.3.x | :white_check_mark: |
-| 5.2.x | :white_check_mark: |
-| 5.1.x | :white_check_mark: |
-| < 5.1 | :x: |
-
-Versions before 5.0 shipped stub AI providers and are not a supported
-configuration.
+Versions before 5.0 shipped stub AI providers and are not supported.
## Reporting a Vulnerability
### How to Report
-If you discover a security vulnerability in GenAI Browser Tool, please report it responsibly:
-
-1. **DO NOT** create a public GitHub issue
-2. **DO NOT** disclose the vulnerability publicly until it has been addressed
-3. **DO** email security concerns directly to: aaronsequeira12@gmail.com
-4. **DO** provide detailed information about the vulnerability
+Email **aaronsequeira12@gmail.com**, subject
+`[SECURITY] GenAI Browser Tool - [Brief Description]`.
+Do not file a public issue or disclose the vulnerability before it is addressed.
### What to Include
-When reporting a security vulnerability, please include:
-
-- **Description**: Clear description of the vulnerability
-- **Impact**: Potential impact and severity assessment
-- **Reproduction**: Step-by-step instructions to reproduce
-- **Evidence**: Screenshots, logs, or proof of concept (if applicable)
-- **Environment**: Browser version, extension version, operating system
-- **Contact**: Your preferred method of communication for follow-up
+Send the impact, reproduction steps, redacted evidence, browser/extension/OS
+versions, and a way to follow up. Do not send keys or unrelated private page data.
### Response Timeline
-- **Acknowledgment**: Within 48 hours of report
-- **Initial Assessment**: Within 5 business days
-- **Status Updates**: Weekly until resolution
-- **Resolution**: Target 30 days for critical issues, 90 days for others
+- Acknowledgment: within 48 hours.
+- Initial assessment: within five business days.
+- Status updates: weekly until resolution.
+- Resolution targets: 30 days for critical issues, 90 days for others.
## Security Measures
### Content Security Policy
-```json
-{
- "content_security_policy": {
- "extension_pages": "script-src 'self'; object-src 'self'"
- }
-}
-```
-
-Extension pages load no remote scripts and no remote stylesheets. Network access
-is limited to the three provider hosts declared in `host_permissions`.
+The [manifest](../manifest.json) sets
+`script-src 'self'; object-src 'self'`. Extension pages load no remote scripts
+or stylesheets. Provider hosts are explicitly declared in `host_permissions`.
### Input validation
-- **Message validation** (`utils/validation-service.js`): every
- `chrome.runtime` message must be an object carrying a non-empty string
- `actionType` and a sender whose id is this extension's own, or it is rejected
- before any work is done. An `actionType` that is well formed but unrecognised
- is rejected by the router's `default` case, still before any work — the list
- of valid actions lives in exactly one place rather than being mirrored here.
-- **Input limits**: page/text documents are limited to 2,000,000 characters.
- Chat selects at most six passages of 2,800 characters. Translation and other
- single-request analyses clip context to 24,000 characters. Summaries can send
- up to eight sections of 24,000 characters, followed by a merge request.
-- **Question length cap**: 1,000 characters. This bounds token spend; it is not
- a security boundary.
-
- There is deliberately **no content filter on questions**. An earlier version
- rejected questions matching patterns like `/