Client feedback, right where it belongs.
An independent, open-source React widget for agencies and their clients. Highlight text, point at an element, leave a note, and resolve it together. The feedback lives in your own Cloudflare account. Clients only need a review link.
Built with React · TypeScript · Effect · Alchemy · Vite+. Inspired by Agentation’s ease of use; no Agentation code or dependency.
Requires Node.js 22.12+ and Vite+.
git clone https://github.com/SemStassen/annoteer.git
cd annoteer
pnpm install
pnpm devOpen http://127.0.0.1:5173. Choose “Review as client” or “Review as agency”. Both use the real Worker API and a local SQLite-backed D1 database through Miniflare. No Cloudflare account is needed for the demo. Local demo data persists in .annoteer/demo/; its disposable credentials are only used on loopback interfaces.
This repo is an MVP; the npm package has not been published yet. Build a tarball first:
pnpm build
pnpm run pack
# In your React project (use the absolute path to the generated file):
npm install /path/to/annoteer/artifacts/annoteer-0.1.0.tgz
npx annoteer init --site https://preview.your-client.com --name client-feedbackSetup installs an isolated Alchemy deployment project, opens Cloudflare authentication, creates D1, applies migrations, deploys a Worker, checks its health, and generates agency/client review links. A Cloudflare account with Workers and D1 permissions and an enabled workers.dev subdomain is required. Resources run on your account and are subject to Cloudflare’s usage limits and billing.
Add the generated component to your app’s root layout:
import AnnoteerFeedback from "./annoteer";
export default function App() {
return (
<>
<YourApp />
<AnnoteerFeedback />
</>
);
}Or use the component directly:
import { Annoteer } from "annoteer"; // 'annoteer/react' also works
<Annoteer endpoint="https://client-feedback.YOUR-SUBDOMAIN.workers.dev" />;For Next.js, place the generated 'use client' component in the root layout. The package is SSR-safe. Only the public endpoint belongs in frontend configuration. The widget is invisible unless a review invitation or existing session is present.
After a future npm release, installation starts with npx annoteer init. The MVP generates the integration component; adding it to your root layout is currently one manual step.
- Share the client link. Keep the agency link private.
- The reviewer enters their name and, if configured, the shared review password.
- Choose Add feedback, then click an element or drag to select text.
- Discuss feedback in threads. Agency reviewers can resolve or reopen notes.
- New feedback syncs every five seconds while the page is visible.
npx annoteer invite --label "Alex at Studio" --days 14
npx annoteer invite --role agency --label "Project team"
npx annoteer invites
npx annoteer revoke INVITATION_ID
npx annoteer deployInvitations expire after 30 days by default (1–90 configurable). A reviewer session lasts at most seven days and never outlives its invitation. Revoking a link immediately invalidates its sessions. Anyone holding a project’s client link can read and comment on that project’s feedback. Names are self-reported, not verified identities. Use a separate deployment per client project.
annoteer.jsonc: optional private review password. Generated at the project root and gitignored; read during deployment only.annoteer.config.json: public Worker endpoint; safe to commit.annoteer.tsx: generated integration component; safe to commit..annoteer/: private deployment project, credentials, Alchemy state, links. Gitignored before secrets are written. Back it up securely; retain its state and encryption password for redeployments. Do not commit it or put it under a static/public directory..annoteer/config.json: project name, site URL, exact allowed origins. Add other preview origins here, then runannoteer deploy.deploymentprop: isolates feedback by version, defaultmain. Keep the same value for reviewers of the same version.data-annoteer-id="hero-title": optional stable anchor for important elements.data-annoteer-ignore: prevents selecting private areas. Form controls and editable fields are excluded by default.nonceprop: supplies the widget’s style nonce for CSP-enabled sites; allow your Worker endpoint inconnect-src.
To require a password for client review links, edit annoteer.jsonc:
Run npx annoteer deploy to apply changes. Comments and trailing commas are supported. Passwords must contain at least 8 non-padding characters and at most 72 UTF-8 bytes. Remove the password field to disable the requirement; {} is valid. The local demo reads the same file when pnpm dev starts.
Clients still need an invitation link; agency links bypass this client password. Changing, enabling, or removing the password invalidates existing client sessions after deployment, so clients must reopen their links. Unchanged passwords preserve sessions. Email allowlists are deferred.
Keep this file private: it contains the plain-text password and must never be imported into your React app or placed in a public directory. Deployment sends only a salted bcrypt hash to the Worker as a secret; browsers submit the password for verification without persisting it. Password verification is limited to 10 attempts per invitation and IP per 15 minutes, including successful attempts. Bcrypt verification adds Worker CPU usage; the password flow has been tested locally, but still needs verification on your Cloudflare plan.
The API validates requests with Effect Schema, stores only hashes of invite/session tokens, enforces role checks, bounds request sizes, and applies per-session write quotas. CORS is an additional restriction, not authentication. The invitation is placed in the URL fragment, removed on entry, and exchanged for a session kept in sessionStorage. Page queries are intentionally excluded from stored page paths.
Annoteer captures the selected element’s label/selector and, for text, the quote and surrounding context. It does not capture screenshots, input values, or full-page HTML. Only enable it on sites where this content may be shared with reviewers. Infrastructure retains D1 on teardown; database deletion is deliberate and manual.
apps/playground/ Runnable Vite+ React example
packages/annoteer/ Single publishable npm package
src/react/ Widget, anchors, browser API client
src/cli/ Setup, deployment, invitation commands
src/server/ Worker entrypoint, HTTP handlers, credentials
src/domain/ Shared Effect schemas and types
infrastructure/ Alchemy template and SQL migrations
docs/ Architecture and ownership rules
scripts/ Build, local development, boundary checks
tests/ API, setup, and browser integration tests
The layout takes inspiration from Overseer’s workspace boundaries: runnable apps live under apps/, reusable code under packages/, and runtime entrypoints stay thin. Annoteer keeps one public npm package with explicit React, CLI, server, and domain modules. See the architecture notes.
Vite+ packs the React library, Node CLI, and standalone Worker separately. Consumers install one package; Alchemy is installed only into the generated deployment project, not the browser bundle. Alchemy stays pinned to 0.94.0 and Effect to the 3.x line; the structural changes do not require a prerelease migration.
pnpm build # vp pack library/CLI/Worker, vp build demo
pnpm typecheck
pnpm test # real local D1 API and setup tests
pnpm exec playwright install chromium # first browser test run
pnpm test:e2e # browser review flow (starts local demo)
pnpm check # Vite+ format and lint
pnpm run pack # artifacts/annoteer-0.1.0.tgz
pnpm test:package # install tarball in a fresh project; verify SSR + CLIReact 18+, modern desktop browsers, one client project per Worker. Annotations re-anchor after scrolling/resizing and DOM updates; uncertain or missing targets are marked as changed rather than silently moved. No cross-origin iframe or nested Shadow DOM selection, screenshots, notifications, verified accounts, automatic framework editing, hash-router support, or live cursors yet. Lists show up to 500 annotations per deployment. Rate quotas are basic abuse controls, not a substitute for edge rate limiting on a public high-traffic service.
Cloudflare deployment requires an interactive agency login or a configured CLOUDFLARE_API_TOKEN / CLOUDFLARE_ACCOUNT_ID. Use --skip-login for an existing Alchemy profile. Use init --skip-install --skip-deploy for offline scaffolding. Deployment should be smoke-tested against your Cloudflare account before relying on it for a client engagement.
MIT licensed. Contributions welcome.
{ // Share this password with your client separately from their review link. "password": "your-shared-review-password", }