{"_id":"@activistchecklist/react-review-comments","_rev":"5-d2179b0bd3319bcf300686a97645c1fd","name":"@activistchecklist/react-review-comments","dist-tags":{"latest":"0.3.1"},"versions":{"0.1.2":{"name":"@activistchecklist/react-review-comments","version":"0.1.2","license":"GPL-3.0","_id":"@activistchecklist/react-review-comments@0.1.2","maintainers":[{"name":"nimble-turtle","email":"contact@activistchecklist.org"}],"dist":{"shasum":"f1acd2fc2b4d8da94cadfc655d85f8e3477ad98d","tarball":"https://registry.npmjs.org/@activistchecklist/react-review-comments/-/react-review-comments-0.1.2.tgz","fileCount":45,"integrity":"sha512-aghwdoIzb8mfEaiRIeTvTbKAdp9MONqhZCu76nifunAO5akijlVXpMOggClnSqOJMh2a97LkQzbVrxeCqD7/zg==","signatures":[{"sig":"MEQCIQCXWiNnnWnyxFj0vh0TycAc2eSLpEEtQtfvfM1juo+O3gIfXQL7EFknP+Wd3PUznC6twq9Lt315G539e0/spB8YcQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":290453},"main":"./src/index.ts","type":"module","types":"./src/index.ts","exports":{".":"./src/index.ts","./server":"./server/handler.ts","./server/db":"./server/db.ts","./styles.css":"./src/rrc.css","./highlightDom":"./src/highlightDom.ts","./server/collections":"./server/collections.ts"},"gitHead":"4865321fbecbd3dad70a2ba34a92f11f338015da","scripts":{"test":"vitest run","build":"yarn clean && tsup src/index.ts src/highlightDom.ts server/handler.ts server/collections.ts server/db.ts --format esm --dts --out-dir dist && cp src/rrc.css dist/rrc.css","clean":"rm -rf dist","prepack":"yarn build","release":"changeset publish","changeset":"changeset","typecheck":"tsc --noEmit","version-packages":"changeset version"},"_npmUser":{"name":"nimble-turtle","email":"contact@activistchecklist.org"},"_npmVersion":"10.8.2","description":"Google-Docs-style anchored review comments for React and Next.js (client shell + optional API handler).","directories":{},"sideEffects":["**/*.css"],"_nodeVersion":"20.20.2","dependencies":{"next":"^15.5.15","mongodb":"^7.1.1","lucide-react":"^1.8.0","openseadragon":"^6.0.2","@annotorious/react":"^3.8.0","@recogito/react-text-annotator":"^3.4.9"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.5.1","jsdom":"^29.0.2","vitest":"^4.1.2","typescript":"~5.7.0","@types/node":"^22.10.0","@types/react":"^19.0.0","@changesets/cli":"^2.31.0","@testing-library/dom":"^10.0.0","@testing-library/react":"^16.0.0"},"peerDependencies":{"react":"^18.2.0 || ^19.0.0","react-dom":"^18.2.0 || ^19.0.0"},"_npmOperationalInternal":{"tmp":"tmp/react-review-comments_0.1.2_1776883451881_0.641685219013411","host":"s3://npm-registry-packages-npm-production"}},"0.1.5":{"name":"@activistchecklist/react-review-comments","version":"0.1.5","license":"GPL-3.0","_id":"@activistchecklist/react-review-comments@0.1.5","maintainers":[{"name":"nimble-turtle","email":"contact@activistchecklist.org"}],"homepage":"https://github.com/ActivistChecklist/react-review-comments#readme","bugs":{"url":"https://github.com/ActivistChecklist/react-review-comments/issues"},"dist":{"shasum":"7062f40caadeb42cea78572ac00a0951008f14e5","tarball":"https://registry.npmjs.org/@activistchecklist/react-review-comments/-/react-review-comments-0.1.5.tgz","fileCount":45,"integrity":"sha512-ifurMc+ZNpGJDtVfwFK0eRCaH/chrrA/MVeYnSYquNL9AXuKguZ3ivUowlAzyp9SkZrSwC87tjBTdOYVTpDgsQ==","signatures":[{"sig":"MEUCICY/x6pFCHJMc/qehLSHj83qroAe7tYjuXtEoYYjr/3rAiEArNQ/WZIKi6gfLCghmccnNvGnnaDi/+VHb62sEBGlWlc=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@activistchecklist%2freact-review-comments@0.1.5","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":291378},"main":"./src/index.ts","type":"module","types":"./src/index.ts","exports":{".":"./src/index.ts","./server":"./server/handler.ts","./server/db":"./server/db.ts","./styles.css":"./src/rrc.css","./highlightDom":"./src/highlightDom.ts","./server/collections":"./server/collections.ts"},"gitHead":"4c65f6f36d49435f9c82bdad56a00b67ae9461f6","scripts":{"test":"vitest run","build":"yarn clean && tsup src/index.ts src/highlightDom.ts server/handler.ts server/collections.ts server/db.ts --format esm --dts --out-dir dist && cp src/rrc.css dist/rrc.css","clean":"rm -rf dist","prepack":"yarn build","release":"changeset publish","changeset":"changeset","typecheck":"tsc --noEmit","version-packages":"changeset version"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:2fdd0801-98b7-4452-a736-5e43cd9e795f"}},"repository":{"url":"git+https://github.com/ActivistChecklist/react-review-comments.git","type":"git"},"_npmVersion":"11.13.0","description":"Google-Docs-style anchored review comments for React and Next.js (client shell + optional API handler).","directories":{},"sideEffects":["**/*.css"],"_nodeVersion":"24.14.1","dependencies":{"next":"^15.5.15","mongodb":"^7.1.1","lucide-react":"^1.8.0","openseadragon":"^6.0.2","@annotorious/react":"^3.8.0","@recogito/react-text-annotator":"^3.4.9"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.5.1","jsdom":"^29.0.2","react":"^19.2.5","vitest":"^4.1.2","react-dom":"^19.2.5","typescript":"~5.7.0","@types/node":"^22.10.0","@types/react":"^19.0.0","@changesets/cli":"^2.31.0","@testing-library/dom":"^10.0.0","@testing-library/react":"^16.0.0"},"peerDependencies":{"react":"^18.2.0 || ^19.0.0","react-dom":"^18.2.0 || ^19.0.0"},"_npmOperationalInternal":{"tmp":"tmp/react-review-comments_0.1.5_1776915625013_0.9734969123833079","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@activistchecklist/react-review-comments","version":"0.2.0","license":"GPL-3.0","_id":"@activistchecklist/react-review-comments@0.2.0","maintainers":[{"name":"nimble-turtle","email":"contact@activistchecklist.org"}],"homepage":"https://github.com/ActivistChecklist/react-review-comments#readme","bugs":{"url":"https://github.com/ActivistChecklist/react-review-comments/issues"},"dist":{"shasum":"719212718f17d19935fbb3d2d6f099dadd8a19b0","tarball":"https://registry.npmjs.org/@activistchecklist/react-review-comments/-/react-review-comments-0.2.0.tgz","fileCount":47,"integrity":"sha512-rtgrlNzq9dk64XfqzoyJ+bPoytMY5jYAG/0YE6m8ZCr/LiQqyyLPc/2NoZtNL0qUI3SL0b/dObioiwo71luNyg==","signatures":[{"sig":"MEUCIQChv8NWpMxbjYHTxHWn2CqoMItA+F7mr+8tRvtSrqvubgIgWnOeGmvgjLUc2AIIUpcyQIRBJN7QIuh+2o0dcntHf/c=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@activistchecklist%2freact-review-comments@0.2.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":302238},"main":"./src/index.ts","type":"module","types":"./src/index.ts","exports":{".":"./src/index.ts","./server":"./server/handler.ts","./server/db":"./server/db.ts","./styles.css":"./src/rrc.css","./highlightDom":"./src/highlightDom.ts","./server/collections":"./server/collections.ts"},"gitHead":"52da21f41b733ea9a85100161724d568f500c3c6","scripts":{"test":"vitest run","build":"yarn clean && tsup src/index.ts src/highlightDom.ts server/handler.ts server/collections.ts server/db.ts --format esm --dts --out-dir dist && cp src/rrc.css dist/rrc.css","clean":"rm -rf dist","prepack":"yarn build","release":"changeset publish","changeset":"changeset","typecheck":"tsc --noEmit","version-packages":"changeset version"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:2fdd0801-98b7-4452-a736-5e43cd9e795f"}},"repository":{"url":"git+https://github.com/ActivistChecklist/react-review-comments.git","type":"git"},"_npmVersion":"11.15.0","description":"Google-Docs-style anchored review comments for React and Next.js (client shell + optional API handler).","directories":{},"sideEffects":["**/*.css"],"_nodeVersion":"24.15.0","dependencies":{"next":"^15.5.15","mongodb":"^7.1.1","lucide-react":"^1.8.0","openseadragon":"^6.0.2","@annotorious/react":"^3.8.0","@recogito/react-text-annotator":"^3.4.9"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.5.1","jsdom":"^29.0.2","react":"^19.2.5","vitest":"^4.1.2","react-dom":"^19.2.5","typescript":"~5.7.0","@types/node":"^22.10.0","@types/react":"^19.0.0","@changesets/cli":"^2.31.0","@testing-library/dom":"^10.0.0","@testing-library/react":"^16.0.0"},"peerDependencies":{"react":"^18.2.0 || ^19.0.0","react-dom":"^18.2.0 || ^19.0.0"},"_npmOperationalInternal":{"tmp":"tmp/react-review-comments_0.2.0_1779493132252_0.029425915569183836","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@activistchecklist/react-review-comments","version":"0.3.0","license":"GPL-3.0","_id":"@activistchecklist/react-review-comments@0.3.0","maintainers":[{"name":"nimble-turtle","email":"contact@activistchecklist.org"}],"homepage":"https://github.com/ActivistChecklist/react-review-comments#readme","bugs":{"url":"https://github.com/ActivistChecklist/react-review-comments/issues"},"dist":{"shasum":"4969f101f9cd43a3ca907885becbbd62a35f1ec8","tarball":"https://registry.npmjs.org/@activistchecklist/react-review-comments/-/react-review-comments-0.3.0.tgz","fileCount":47,"integrity":"sha512-2dpx0vaD2TG7dPGvrMjf26Tf1xS4TZloTCcMq7/bvRS0x0WFTtNjbSCvTetDb9rOD0AKgUlvHBwSnv/w2DTlTQ==","signatures":[{"sig":"MEQCIBrX4I1g46ZGphnS+WrD16wyhyE4IBf3aGpxZf2kgIpvAiAbTGVVtwWDLOpaxHTMPjk/JVfe/+jCvZtPpF7qdJOy1A==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@activistchecklist%2freact-review-comments@0.3.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":309270},"main":"./src/index.ts","type":"module","types":"./src/index.ts","exports":{".":"./src/index.ts","./server":"./server/handler.ts","./server/db":"./server/db.ts","./styles.css":"./src/rrc.css","./highlightDom":"./src/highlightDom.ts","./server/collections":"./server/collections.ts"},"gitHead":"22085f631726d9103e9c3fdde4c32fba2b5ebabf","scripts":{"test":"vitest run","build":"yarn clean && tsup src/index.ts src/highlightDom.ts server/handler.ts server/collections.ts server/db.ts --format esm --dts --out-dir dist && cp src/rrc.css dist/rrc.css","clean":"rm -rf dist","prepack":"yarn build","release":"changeset publish","changeset":"changeset","typecheck":"tsc --noEmit","version-packages":"changeset version"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:2fdd0801-98b7-4452-a736-5e43cd9e795f"}},"repository":{"url":"git+https://github.com/ActivistChecklist/react-review-comments.git","type":"git"},"_npmVersion":"11.16.0","description":"Google-Docs-style anchored review comments for React and Next.js (client shell + optional API handler).","directories":{},"sideEffects":["**/*.css"],"_nodeVersion":"24.16.0","dependencies":{"next":"^15.5.15","mongodb":"^7.1.1","lucide-react":"^1.8.0","openseadragon":"^6.0.2","@annotorious/react":"^3.8.0","@recogito/react-text-annotator":"^3.4.9"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.5.1","jsdom":"^29.0.2","react":"^19.2.5","vitest":"^4.1.2","react-dom":"^19.2.5","typescript":"~5.7.0","@types/node":"^22.10.0","@types/react":"^19.0.0","@changesets/cli":"^2.31.0","@testing-library/dom":"^10.0.0","@testing-library/react":"^16.0.0"},"peerDependencies":{"react":"^18.2.0 || ^19.0.0","react-dom":"^18.2.0 || ^19.0.0"},"_npmOperationalInternal":{"tmp":"tmp/react-review-comments_0.3.0_1780091330584_0.2728075147127351","host":"s3://npm-registry-packages-npm-production"}},"0.3.1":{"_id":"@activistchecklist/react-review-comments@0.3.1","bugs":{"url":"https://github.com/ActivistChecklist/react-review-comments/issues"},"dist":{"shasum":"1ea2233046a2a38a2bd3b7ae12414a4295dccdfe","tarball":"https://registry.npmjs.org/@activistchecklist/react-review-comments/-/react-review-comments-0.3.1.tgz","fileCount":47,"integrity":"sha512-Pv/ZhlC/66krXCmKLcpUAnu6HA8Xnd4DazCkrgVFSS1zO4FfceFHg+rh4HzUHhJnUVxFT32scvQ60ke66p02Tw==","signatures":[{"sig":"MEUCIQCwjqqTYfTNimFvxf2AZUfwc2QS/SEpjWmexRZ+xV3VrgIgeYOGObItidsPigHrzDwWaNhJz9U1Y75onbrpry9bB4s=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDIrrUSs/g14UOpKUG0D9DgF5zsN1cArW9L+CADrTbBnAIhAMjS1yod0SkHtOlqeffROxgmhMYQErtf/8d1izn/3mrd"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@activistchecklist%2freact-review-comments@0.3.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":309425},"main":"./src/index.ts","name":"@activistchecklist/react-review-comments","type":"module","types":"./src/index.ts","exports":{".":"./src/index.ts","./server":"./server/handler.ts","./server/db":"./server/db.ts","./styles.css":"./src/rrc.css","./highlightDom":"./src/highlightDom.ts","./server/collections":"./server/collections.ts"},"gitHead":"492b995b5ed2e0b256712f36e762d6d582a53fbd","license":"GPL-3.0","scripts":{"test":"vitest run","build":"yarn clean && tsup src/index.ts src/highlightDom.ts server/handler.ts server/collections.ts server/db.ts --format esm --dts --out-dir dist && cp src/rrc.css dist/rrc.css","clean":"rm -rf dist","prepack":"yarn build","release":"changeset publish","changeset":"changeset","typecheck":"tsc --noEmit","version-packages":"changeset version"},"version":"0.3.1","_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:2fdd0801-98b7-4452-a736-5e43cd9e795f"}},"homepage":"https://github.com/ActivistChecklist/react-review-comments#readme","repository":{"url":"git+https://github.com/ActivistChecklist/react-review-comments.git","type":"git"},"_npmVersion":"12.0.2","description":"Google-Docs-style anchored review comments for React and Next.js (client shell + optional API handler).","directories":{},"maintainers":[{"name":"nimble-turtle","email":"contact@activistchecklist.org"}],"sideEffects":["**/*.css"],"_nodeVersion":"24.20.0","dependencies":{"mongodb":"^7.1.1","lucide-react":"^1.8.0","openseadragon":"^6.0.2","@annotorious/react":"^3.8.0","@recogito/react-text-annotator":"^3.4.9"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org"},"_hasShrinkwrap":false,"devDependencies":{"next":"^15.5.24","tsup":"^8.5.1","jsdom":"^29.0.2","react":"^19.2.5","vitest":"^4.1.2","react-dom":"^19.2.5","typescript":"~5.7.0","@types/node":"^22.10.0","@types/react":"^19.0.0","@changesets/cli":"^2.31.0","@testing-library/dom":"^10.0.0","@testing-library/react":"^16.0.0"},"peerDependencies":{"next":"^14.0.0 || ^15.0.0 || ^16.0.0","react":"^18.2.0 || ^19.0.0","react-dom":"^18.2.0 || ^19.0.0"},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/react-review-comments_0.3.1_1789835032052_0.9072193704902967"}}},"time":{"created":"2026-04-22T18:44:11.813Z","modified":"2026-09-19T16:23:52.517Z","0.1.2":"2026-04-22T18:44:12.025Z","0.1.5":"2026-04-23T03:40:25.230Z","0.2.0":"2026-05-22T23:38:52.408Z","0.3.0":"2026-05-29T21:48:50.723Z","0.3.1":"2026-09-19T16:23:52.176Z"},"bugs":{"url":"https://github.com/ActivistChecklist/react-review-comments/issues"},"license":"GPL-3.0","homepage":"https://github.com/ActivistChecklist/react-review-comments#readme","repository":{"url":"git+https://github.com/ActivistChecklist/react-review-comments.git","type":"git"},"description":"Google-Docs-style anchored review comments for React and Next.js (client shell + optional API handler).","maintainers":[{"name":"nimble-turtle","email":"contact@activistchecklist.org"}],"readme":"# react-review-comments\n\n> [!WARNING]\n> Warning: This library is **alpha**. It is developed for and tested alongside [ActivistChecklist.org](https://activistchecklist.org). **It has not been validated on other deployments yet**. APIs, environment contracts, and Mongo layout may change. It may work just fine for you. We'd love help testing it.\n\nThink of this as Google Docs comments for your react site. It is meant to be easy to drop in so reviewers can leave comments on the text of a page.\n\nMany tools put a \"dot\" on the page for markup-style comments. Those fit poorly when the layout moves (for example accordion sections). This package uses text-based highlighting and comments instead.\n\nIt was designed for deploy preview branches (Vercel, Railway, and similar) and may not suit a production site.\n\n## Install\n\n```bash\n# Pick your package manager\nnpm install @activistchecklist/react-review-comments\nyarn add @activistchecklist/react-review-comments\npnpm add @activistchecklist/react-review-comments\n```\n\n**Peers:** `react`, `react-dom`, `next` (App Router recommended; `^14 || ^15 || ^16` — the package uses whatever Next your app already has, and never installs its own copy), `@annotorious/react`, `@recogito/react-text-annotator`, `lucide-react`. **`mongodb`** is bundled as a regular dependency — you do not need to install it separately.\n\nThe package ships prebuilt ESM + type declarations.\n\n## Quick setup\n\n**1. Client: provider**\n\nIn a Next.js layout, add **`ReviewCommentsProvider`**. It includes the shell and styles; **`path`**, **`locale`**, and **`scope`** are optional and inferred from the URL and `window.location.host` when omitted. **`enabled`** defaults to `true` when omitted.\n\n```tsx\nimport { ReviewCommentsProvider } from '@activistchecklist/react-review-comments';\n\nexport default async function Layout({ children }) {\n  const enabled = true;\n  return (\n    <ReviewCommentsProvider enabled={enabled}>\n      {children}\n    </ReviewCommentsProvider>\n  );\n}\n```\n\n**2. Next.js App Router: one API route.** Add a **catch-all** and forward to the package handler.\n\nAdd this to `app/api/review-comments/[[...path]]/route.ts` (or `route.js` if you're not using TypeScript):\n\n```typescript\nimport {\n  handleReviewCommentsRequest,\n  type ReviewCommentsRouteContext,\n} from '@activistchecklist/react-review-comments/server';\nimport { getReviewCommentsConfig } from '@/lib/review-comments-env';\n\nexport const dynamic = 'force-dynamic';\n\nconst handlerOptions = {\n  getReviewCommentsRuntimeConfig: getReviewCommentsConfig,\n};\n\nfunction handler(request: Request, context: ReviewCommentsRouteContext) {\n  return handleReviewCommentsRequest(request, context, handlerOptions);\n}\n\nexport const GET = handler;\nexport const POST = handler;\nexport const PATCH = handler;\nexport const DELETE = handler;\n```\n\n**2a. Create `getReviewCommentsConfig`**\n\nCreate a small helper in your app, for example `lib/review-comments-env.ts`:\n\n```typescript\nimport { type ReviewCommentsRuntimeConfig } from '@activistchecklist/react-review-comments/server';\n\nfunction isTrue(value: string | undefined): boolean {\n  return ['1', 'true', 'yes', 'on'].includes((value || '').toLowerCase());\n}\n\nexport function getReviewCommentsConfig(env: NodeJS.ProcessEnv = process.env): ReviewCommentsRuntimeConfig {\n  return {\n    enabled: isTrue(env.REVIEW_COMMENTS_ENABLED),\n    publicReadWrite: isTrue(env.REVIEW_COMMENTS_PUBLIC_WRITE || 'true'),\n  };\n}\n```\n\nIf your route is JavaScript, use `lib/review-comments-env.js`:\n\n```javascript\nfunction isTrue(value) {\n  return ['1', 'true', 'yes', 'on'].includes(String(value || '').toLowerCase());\n}\n\nexport function getReviewCommentsConfig(env = process.env) {\n  return {\n    enabled: isTrue(env.REVIEW_COMMENTS_ENABLED),\n    publicReadWrite: isTrue(env.REVIEW_COMMENTS_PUBLIC_WRITE || 'true'),\n  };\n}\n```\n\nThen import it in your route:\n\n```typescript\nimport { getReviewCommentsConfig } from '@/lib/review-comments-env';\n```\n\n**3. Environment variables**\n\n**Required for the API:**\n\n- **`REVIEW_COMMENTS_ENABLED`**: `true`\n- **`REVIEW_COMMENTS_MONGODB_URL`**: connection string. If the URL has no database path segment, the database name defaults to **`review_comments`**.\n\n**Optional:** **`REVIEW_COMMENTS_PUBLIC_WRITE`** (see [Environment and security](#environment-and-security)).\n\nPreview vs production is only the hostname in the browser; no extra env vars for that.\n\n---\n\n## Restricting access\n\nThe stock handler does not authenticate users. **Who may call the API** is up to your route or middleware: call `handleReviewCommentsRequest` only after your checks pass. `getReviewCommentsRuntimeConfig` is for feature flags (and similar) from **`process.env`**, not per-request auth.\n\n### Example: signed-in users only\n\n**UI:** enable the shell when your auth says the user is logged in.\n\n```tsx\nimport { auth } from '@/auth'; // e.g. Auth.js / your session helper\n\nexport default async function GuideLayout({ children }: { children: React.ReactNode }) {\n  const session = await auth();\n  return (\n    <ReviewCommentsProvider\n      enabled={Boolean(session)}\n      path={/* … */}\n      locale={/* … */}\n      scope={/* … */}\n    >\n      {children}\n    </ReviewCommentsProvider>\n  );\n}\n```\n\n**API:** reject anonymous requests before the stock handler. The client uses **`credentials: 'same-origin'`**, so a **session cookie** is sent automatically when your session is cookie-based.\n\n```typescript\nimport { auth } from '@/auth';\nimport { handleReviewCommentsRequest, type ReviewCommentsRouteContext } from '@activistchecklist/react-review-comments/server';\nimport { getReviewCommentsConfig } from '@/lib/review-comments-env';\n\nconst handlerOptions = { getReviewCommentsRuntimeConfig: getReviewCommentsConfig };\n\nasync function gated(request: Request, context: ReviewCommentsRouteContext) {\n  const session = await auth();\n  if (!session) {\n    return Response.json({ error: 'Unauthorized' }, { status: 401 });\n  }\n  return handleReviewCommentsRequest(request, context, handlerOptions);\n}\n\nexport const GET = gated;\nexport const POST = gated;\nexport const PATCH = gated;\nexport const DELETE = gated;\n```\n\nKeep **`REVIEW_COMMENTS_ENABLED`** and Mongo env as a global kill switch; `getReviewCommentsConfig` can still return `enabled: false` when the feature is off entirely.\n\n### Example: URL query secret (e.g. preview reviewers)\n\n**UI:** validate the query on the server and pass **`enabled`** from that (do not expose the secret to the client as a prop).\n\n```tsx\nexport default async function Page({\n  children,\n  searchParams,\n}: {\n  children: React.ReactNode;\n  searchParams: Promise<{ rrc?: string }>;\n}) {\n  const sp = await searchParams;\n  const enabled = sp.rrc === process.env.RRC_REVIEW_SECRET;\n  return (\n    <ReviewCommentsProvider enabled={enabled} path={/* … */} locale={/* … */} scope={/* … */}>\n      {children}\n    </ReviewCommentsProvider>\n  );\n}\n```\n\n**API:** the default client **does not** append arbitrary query params to every request. Either:\n\n- Set a **short-lived cookie** (e.g. in middleware) when `?rrc=` is valid, and in the route handler require that cookie before calling `handleReviewCommentsRequest`, or\n- Use a thin wrapper around **`createReviewCommentsApi`** / custom **`fetch`** that adds a **header** (e.g. `X-Rrc-Preview: …`) that your route compares to `process.env.RRC_REVIEW_SECRET`.\n\n```typescript\nasync function gatedBySecret(request: Request, context: ReviewCommentsRouteContext) {\n  const secret = request.headers.get('x-rrc-preview');\n  if (secret !== process.env.RRC_REVIEW_SECRET) {\n    return Response.json({ error: 'Forbidden' }, { status: 403 });\n  }\n  return handleReviewCommentsRequest(request, context, {\n    getReviewCommentsRuntimeConfig: getReviewCommentsConfig,\n  });\n}\n```\n\nTreat shared secrets like passwords: **rotate them**, prefer **HTTPS**, and do not log them.\n\n---\n\n## Reference\n\n### Provider props\n\nThese apply to **`ReviewCommentsProvider`** (and to **`ReviewCommentsContextProvider`** if you wire the shell yourself).\n\n- **`apiBase`**: base URL for fetches (no trailing slash), e.g. `/api/review-comments`. Defaults to `/api/review-comments`.\n- **`enabled`**: when `false`, the shell renders `children` only (no panel, no listeners). Defaults to `true` when omitted.\n- **`panelMode`**: `'docked'` (default) pins the panel to the side of the page; `'floating'` renders it as a floating overlay.\n- **`path`**: stable document id for this page (e.g. `/guide/foo/` with a trailing slash if your site uses one).\n- **`locale`**: short locale string, e.g. `en`, `es`.\n- **`scope`**: `{ scopeKey: string }` from **`reviewCommentsScopeFromHostHeader`** (layouts / client) or **`reviewCommentsScopeFromRequest`** (route handlers). This ties client-side state (for example seen threads in `localStorage`) to the **HTTP host**. The API reads the same host from each request and does **not** accept a separate scope in query or body.\n- **`labels`**: a partial object merged onto English defaults (button labels, errors, panel chrome). See `src/defaultLabels.ts` or the exported `defaultReviewCommentsLabels` for the full list of keys.\n\n### Anonymous author names\n\nThe system auto-generates a random two-word handle (e.g. `CalmPine`) and stores it in **`sessionStorage`**.\n\n### Optional: override copy\n\n**`labels`**: a partial object merged onto English defaults (button labels, errors, panel chrome). Import **`defaultReviewCommentsLabels`** from the package to reference the full set of defaults before overriding.\n\n```tsx\nimport { defaultReviewCommentsLabels } from '@activistchecklist/react-review-comments';\n\n<ReviewCommentsProvider\n  labels={{\n    ...defaultReviewCommentsLabels,\n    threadPanelTitle: 'Feedback',\n    addComment: 'Leave feedback',\n  }}\n>\n```\n\n### When to use `transpilePackages`\n\nUse this **only with Next.js** when you import this package from **`node_modules`** and Next must compile its **TypeScript** (and TSX) for the app build. If your bundler already consumes a precompiled JS build of the package, you do not need it.\n\n```javascript\n// next.config.js\nmodule.exports = {\n  transpilePackages: ['@activistchecklist/react-review-comments'],\n};\n```\n\nTypes are exported from the package entry for the client API, provider props, threads, and labels.\n\n### Manual context provider + shell\n\nUse **`ReviewCommentsContextProvider`** when you need a custom shell or split imports. Wrap the **main article body** with the context provider and **`ReviewCommentsShell`**. Pass **`scope`** from `reviewCommentsScopeFromHostHeader` (or `window.location.host` in a client-only tree) so it matches what the API will use.\n\n```jsx\n'use client';\n\nimport {\n  ReviewCommentsContextProvider,\n  ReviewCommentsShell,\n} from '@activistchecklist/react-review-comments';\nimport '@activistchecklist/react-review-comments/styles.css';\n\nexport function CommentsWrapper({ enabled, path, locale, scope, children }) {\n  return (\n    <ReviewCommentsContextProvider\n      apiBase=\"/api/review-comments\"\n      enabled={enabled}\n      path={path}\n      locale={locale}\n      scope={scope}\n    >\n      <ReviewCommentsShell>{children}</ReviewCommentsShell>\n    </ReviewCommentsContextProvider>\n  );\n}\n```\n\n### `useReviewComments` hook\n\n`useReviewComments()` returns the full context value (`api`, `apiBase`, `enabled`, `panelMode`, `path`, `locale`, `scope`, `labels`). Must be called inside a `ReviewCommentsContextProvider` (or `ReviewCommentsProvider`). Useful when building custom shells or reading context state in child components.\n\n### Handler options\n\nPass **`getReviewCommentsRuntimeConfig`** only for feature flags (`enabled`, `publicReadWrite`). Document scope in Mongo always comes from the request **Host** (see `src/scopeFromHost.ts`). If you omit it, the handler uses **`getReviewCommentsRuntimeConfigFromEnv`** in `server/env.ts`.\n\n### Environment and security\n\n**Optional:**\n\n- **`REVIEW_COMMENTS_PUBLIC_WRITE`**: defaults to **`true`** if unset (anonymous write for PR-style review). When set to **`false`**, the stock handler returns **403** for POST, PATCH, and DELETE; GET routes (list threads, overview) still work for read-only embeds.\n- **`REVIEW_COMMENTS_CLEANUP_DAYS`**: used by maintenance scripts only (for example `yarn annotations:cleanup`), not required for normal operation.\n- **`BUILD_MODE=static`**: when set to `static`, the stock handler's `isReviewCommentsEnabled` returns `false` regardless of `REVIEW_COMMENTS_ENABLED`. Useful for static export builds where the API route should never activate.\n\nCollections use the **`rrc_*`** prefix (see `server/collections.ts`).\n\n### Built-in rate limiting\n\nThe handler includes **in-memory, per-IP rate limiting** on all routes. Limits reset on server restart (not suitable as a hard security boundary — pair with infrastructure-level rate limiting for that). Current limits per 60-second window:\n\n| Action | Limit |\n|---|---|\n| List threads (GET) | 120 |\n| Overview (GET) | 60 |\n| Create thread (POST) | 20 |\n| Update thread status (PATCH) | 60 |\n| Create comment (POST) | 40 |\n| Update comment (PATCH) | 60 |\n| Delete comment (DELETE) | 40 |\n\nExceeded requests receive **HTTP 429** with a `Retry-After` header.\n\n**Security notes**\n\n- **No stored HTML**: comment bodies and quotes are plain text; the UI renders them as React text nodes (no `dangerouslySetInnerHTML` in the stock shell).\n- **MongoDB**: filters use fixed field names and string parameters. Client-supplied **`anchorSelector`** is sanitized (no `$` keys, no `__proto__` / `constructor` paths, bounded depth and size) before insert.\n- **IDs**: thread and comment ids in URL segments and JSON bodies must match a normal **UUID** shape before updates or deletes.\n- **Trust model**: there is **no authentication** in the stock handler; scope is derived from **Host** / **X-Forwarded-Host**. Treat this as suitable for low-risk, same-site review comments, not for sensitive workflows without your own auth layer.\n\n### Static export\n\nIf you use `output: 'export'`, do not ship the API route or live comments UI: tree-shake or replace the shell and stub the API with your build, as you would for any dynamic backend.\n\n### API surface (client)\n\n`createReviewCommentsApi(apiBase)` returns methods used by the shell: `fetchThreads`, `fetchOverview`, `createThread`, `createComment`, `patchThreadStatus`, `patchComment`, `deleteComment`. You can reuse these if you build a custom layout.\n\n### Package layout\n\n- **`src/`** – React UI, highlight helpers, client API (`ReviewCommentsProvider`, `ReviewCommentsContextProvider`, shell, `scopeFromHost.ts`).\n- **`src/index.ts`** – main client exports: provider, shell, `ReviewCommentsPanel`, `useReviewComments`, `createReviewCommentsApi`, `defaultReviewCommentsLabels`, scope helpers, and all public types.\n- **`server/handler.ts`** – `handleReviewCommentsRequest` for Next.js.\n- **`server/collections.ts`** – Mongo collection names (`rrc_*`).\n- **`server/db.ts`** – exported as `@activistchecklist/react-review-comments/server/db`; low-level Mongo connection helper if you need direct access.\n- **`shared/sanitize.ts`** – shared normalization for quotes, anchor metadata, and UUID validation.\n- **`src/highlightDom.ts`** – exported as `@activistchecklist/react-review-comments/highlightDom`; DOM highlight utilities if you need to drive highlighting outside the shell.\n- **`src/rrc.css`** – scoped panel and thread styles (`rrc-*`).\n\n## License\n\nGPL-3.0. See `LICENSE` in this package.\n","readmeFilename":"README.md"}