Visual annotation tool for design review workflows. Framework-agnostic, zero dependencies.
Click any element on your running app and leave a comment - web-remarq fingerprints the element and (with a build plugin) resolves it to file:line:column in your source. From there, three ways to close the loop: hand the task to an AI coding agent over MCP, export a report for a developer, or sync it to your team via Supabase. Agents fix; humans verify - the verification gate is built into the annotation lifecycle.
| Package | Description | |
|---|---|---|
web-remarq |
Core library — browser annotation tool | |
@web-remarq/mcp |
MCP server — gives AI agents (Claude Code, Cursor, ...) access to annotations; zero-config local mode | |
@web-remarq/unplugin |
Universal plugin for Vite/webpack/Rollup/esbuild/Rspack (JSX + Vue SFC) | |
@web-remarq/babel-plugin |
Babel plugin for JSX source injection (React, Preact, Solid) | |
@web-remarq/swc-plugin |
SWC/WASM plugin for source injection (Turbopack) | |
@web-remarq/next |
Next.js config wrapper — withRemarq() for webpack and Turbopack |
|
@web-remarq/cloud |
Cloud storage adapter — sync annotations across team via Supabase | |
@web-remarq/cli |
Installer and doctor - detects your stack, installs packages, prints the remaining edits, verifies the setup |
Let your coding agent do it:
npx skills add DPostnik/web-remarqThen tell it: "set up web-remarq". It runs the installer, wires up your build
config and entry point, and verifies the result with doctor.
Prefer to drive yourself:
npx @web-remarq/cli init # installs packages, writes .mcp.json, prints the remaining edits
npx @web-remarq/cli doctor # checks the setup and explains what is wrongOn a plain HTML page with no bundler, init completes the whole setup on its own.
See each package's README for detailed docs.
The flagship flow: you point at what's wrong on the page, your agent fixes it, you verify with one click. No account, no cloud, no env vars.
One-time setup (a Vue + Vite project as the example; React/Next work the same via the matching plugin):
npm i -D web-remarq @web-remarq/unplugin// vite.config.ts — stamps data-remarq-source="src/components/Card.vue:24:6" (dev-only)
import remarq from '@web-remarq/unplugin/vite'
export default defineConfig({ plugins: [vue(), remarq({ include: ['src/**/*.vue'] })] })// main.ts
import { WebRemarq, HttpStorageAdapter } from 'web-remarq'
if (import.meta.env.DEV) {
WebRemarq.init({ submitFlow: true, storage: new HttpStorageAdapter() })
}// .mcp.json
{ "mcpServers": { "web-remarq": { "command": "npx", "args": ["-y", "@web-remarq/mcp"] } } }The MCP server starts in local mode automatically: annotations live in .remarq/annotations.json (self-gitignored), served to the widget over 127.0.0.1. Access is paired, not open: .remarq/config.json holds the project id and a random token; the Vite plugin hands the token to the widget in development, and the server accepts only listed browser origins. See the @web-remarq/mcp README for the model and its limits.
The daily loop:
- Annotate. Run the dev server, hit Inspect in the toolbar, click the broken element, type what's wrong. Drafts collect quietly; press Submit to release them.
- Put the agent on duty. In Claude Code, type
/mcp__web-remarq__watch. The agent long-polls for feedback, acknowledges each annotation (marker turns yellow), hands the fix to a background subagent, and goes straight back to watching - fixes run in parallel, new feedback never waits. - Or don't. With no agent running, every actionable annotation is mirrored as a ticket file in
.remarq/tasks/<id>.md- comment, source location, grep hints, and reporting instructions included. Later, tell any agent: "work through the tickets in.remarq/tasks/". - Verify. A blue marker means the agent claims a fix. Look at it: Verify (green, ticket disappears) or Reject with a reason (back to pending - the agent on duty picks it up again). Agents cannot verify their own work; that button is human-only.
The toolbar's small dot says where your last change landed - and the states are never blurred together:
| Dot | State | Meaning |
|---|---|---|
| green | synced |
the server confirmed the write |
| orange, pulsing | queued |
server unreachable; the change is in a per-project queue in localStorage and is replayed, in order, when the server is back - including after a reload |
| red | memory |
localStorage unavailable too (quota, disabled); the change lives in this tab only and is lost on reload - you are told so |
| red | unauthorized |
not paired: no token, a rotated token, or an origin the server does not allow; nothing is sent until fixed |
| red | rejected |
the server refused a change as invalid; it is parked, not retried forever |
| orange | conflict |
your change collided with a newer server copy (an agent moved it first); fields that could be kept were kept, the rest is journaled per project in localStorage and survives reloads until you export and clear it |
Rules that hold behind the dot: a queue only empties after the server confirms each operation; a stale local copy can never roll back a newer status or truncate history (writes carry revisions, collisions are merged three-way against the copy the edit was made from, which is stored with the queued change so a reload cannot turn a partial edit into an overwrite); a queue built for project A is never sent to a project B later served on the same port; a queue built before any server was ever seen is only sent after WebRemarq.adoptUnsent(); WebRemarq.exportUnsent() downloads everything that never reached the server, conflict records included (a conflict is written to the journal before the queued change is dropped, so a crash in between replays the change and replaces the record instead of losing it; when the journal itself cannot be written the change stays queued and the state says memory); WebRemarq.getSyncStatus() returns the state programmatically. Two tabs editing the same annotation do not lose changes silently: the second write is a detectable conflict. Imports are validated before anything is cleared and the previous store is backed up to localStorage["remarq:import-backup"].
Nothing to run beyond the widget - annotations live in localStorage.
- A designer annotates on staging, then exports JSON or copies the report as Markdown from the toolbar.
- A developer imports the JSON - markers appear on the exact elements, viewport-aware; anything that no longer matches lands in a side panel instead of getting lost.
- Or skip the human:
WebRemarq.copy('agent')produces an agent-optimized export - source locations, prioritized grep queries with confidence levels, DOM context - ready to paste into any AI coding agent.
Same widget and MCP server, shared storage:
npx @web-remarq/cloud gen-key --name "my-project" # prints pk_... and a SQL snippetimport { createCloudStorage } from '@web-remarq/cloud'
WebRemarq.init({
storage: createCloudStorage({ supabaseUrl, supabaseAnonKey, projectKey: 'pk_...' }),
})Everyone with the project key sees the same annotations (row-level security keyed by a hashed project key - the DB never stores the plaintext). The MCP server joins in cloud mode via REMARQ_PROJECT_KEY / REMARQ_SUPABASE_URL / REMARQ_SUPABASE_ANON_KEY. Details: @web-remarq/cloud, @web-remarq/mcp.
WebRemarq.init({ storage }) accepts any StorageAdapter implementation. Default is localStorage. See the core package README for the interface and custom adapter examples. A Supabase-backed adapter for team collaboration ships separately as @web-remarq/cloud.
MIT
