AI coding agents edit your source files blind — they can't see your running frontend, and your frontend can't tell them where to look.
Domscribe bridges both directions: click a DOM element to tell your agent what to change, or let your agent query any source location to see exactly what it looks like live in the browser. Build-time stable IDs, deep runtime context (props, state, DOM), framework-agnostic, any MCP-compatible agent. Zero production impact.
Your agent is editing Button.tsx line 12. It calls domscribe.query.bySource and instantly gets back the live DOM snapshot, current props, component state, and rendered attributes — without any human interaction. The agent can verify what an element looks like before changing it and confirm the result after.
Here's an example response that the agent might get back:
{
"sourceLocation": {
"file": "src/components/Button.tsx",
"line": 12,
"column": 4,
"componentName": "Button",
"tagName": "button"
},
"runtime": {
"componentProps": { "variant": "secondary", "onClick": "[Function]" },
"componentState": { "hook_0": false, "hook_1": "idle" },
"domSnapshot": {
"tagName": "button",
"attributes": { "class": "btn-secondary", "type": "submit" },
"innerText": "Save changes"
}
},
"browserConnected": true
}A developer clicks an element in the browser overlay, types "make this button use the primary color," and submits. Domscribe captures the element's source location, runtime context, and user intent as an annotation. The agent claims it, navigates to the exact file and line, and implements the change.
The annotation is stored as a JSON file in your repository in the .domscribe/annotations directory. Here's an example:
{
"found": true,
"annotationId": "ann_A1B2C3D4_1710500000",
"userIntent": "Make this button use the primary color from the design system",
"element": {
"tagName": "button",
"dataDs": "A1B2C3D4",
"selector": "main > div > button",
"attributes": { "class": "btn-secondary", "type": "submit" },
"innerText": "Save changes"
},
"sourceLocation": {
"file": "src/components/Button.tsx",
"line": 12,
"column": 4,
"componentName": "Button",
"tagName": "button"
},
"runtimeContext": {
"componentProps": { "variant": "secondary", "onClick": "[Function]" },
"componentState": { "hook_0": false, "hook_1": "idle" }
}
}Once the agent is done with the edits, it calls domscribe.annotation.respond with a description of what it did. The overlay shows the result in real time via WebSocket.
Domscribe has two sides: app-side (bundler + framework plugins) and agent-side (MCP for your coding agent). Both are needed for the full workflow.
Next.js (15 + 16) — npm install -D @domscribe/next
// next.config.ts
import type { NextConfig } from 'next';
import { withDomscribe } from '@domscribe/next';
const nextConfig: NextConfig = {};
export default withDomscribe()(nextConfig);Nuxt 3+ — npm install -D @domscribe/nuxt
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@domscribe/nuxt'],
});React 18–19 — npm install -D @domscribe/react
Vite plugin:
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { domscribe } from '@domscribe/react/vite';
export default defineConfig({
plugins: [react(), domscribe()],
});Webpack plugin:
// webpack.config.js
const { DomscribeWebpackPlugin } = require('@domscribe/react/webpack');
module.exports = {
plugins: [new DomscribeWebpackPlugin()],
};Vue 3+ — npm install -D @domscribe/vue
Vite plugin:
// vite.config.ts
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import { domscribe } from '@domscribe/vue/vite';
export default defineConfig({
plugins: [vue(), domscribe()],
});Webpack plugin:
// webpack.config.js
const { DomscribeWebpackPlugin } = require('@domscribe/vue/webpack');
module.exports = {
plugins: [new DomscribeWebpackPlugin()],
};Any framework — npm install -D @domscribe/transform (DOM→source mapping only, no runtime capture)
Vite plugin:
// vite.config.ts
import { defineConfig } from 'vite';
import { domscribe } from '@domscribe/transform/plugins/vite';
export default defineConfig({
plugins: [domscribe()],
});Webpack plugin:
// webpack.config.js
const {
DomscribeWebpackPlugin,
} = require('@domscribe/transform/plugins/webpack');
module.exports = {
plugins: [new DomscribeWebpackPlugin()],
};Working examples: See
packages/domscribe-test-fixtures/fixtures/for complete app setups across every supported framework and bundler combination.
Domscribe exposes its full tool surface (12 tools + 4 prompts) via MCP. Agent plugins bundle the MCP config and a skill file that teaches the agent how to use the tools effectively.
For agents with first-class plugin support, install the plugin and you're done:
/plugin marketplace add patchorbit/domscribe
/plugin install domscribe@domscribecopilot plugin install patchorbit/domscribegemini extensions install https://github.com/patchorbit/domscribeOpen the Powers panel → Add power from GitHub → enter https://github.com/patchorbit/domscribe.
(Coming soon — pending marketplace approval)
Install the Domscribe skills:
npx skills add patchorbit/domscribeThen add this MCP config to your agent:
{
"mcpServers": {
"domscribe": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@domscribe/mcp"]
}
}
}1. Inject. At build time, DomscribeInjector hooks into your bundler's transform phase (Vite, Webpack, or Turbopack). It parses each source file, finds every JSX/Vue template element, generates an HMR-stable ID via xxhash64 content hashing, and injects data-ds="{id}". Each ID is recorded in .domscribe/manifest.jsonl with its file path, line, column, tag name, and component name.
2. Capture. At runtime, framework adapters (React fiber walking, Vue VNode inspection) extract live props, state, and component metadata from every instrumented element. The overlay UI (Lit web components in shadow DOM) lets you click any element and see its full context.
3. Relay. The relay daemon (Fastify, localhost-only) connects the browser and your agent. It exposes REST endpoints, WebSocket real-time events, and an MCP stdio adapter. A file lock at .domscribe/relay.lock prevents duplicate instances across dev server restarts.
4. Agent. Your coding agent connects via MCP and gets two capabilities. It can query by source to see what any line of code looks like live in the browser. And it can process annotations — user-submitted instructions attached to specific elements — to claim, implement, and respond to UI change requests.
| Feature | Domscribe | Stagewise | DevInspector MCP | React Grab | Frontman |
|---|---|---|---|---|---|
| Build-time stable IDs | ✅ data-ds via AST |
❌ Proxy-based | ❌ No stable IDs | ❌ _debugSource |
❌ Source maps |
| DOM→source manifest | ✅ JSONL, append-only | ❌ | ❌ | ❌ | ❌ |
| Code→live DOM query | ✅ Agent queries source, gets live runtime | ❌ | ❌ | ❌ | ❌ |
| Runtime props/state | ✅ Fiber + VNode walking | ||||
| Multi-framework | ✅ React · Vue · Next.js · Nuxt · extensible | ✅ React + Vue + Angular | ✅ React + Vue + Svelte + Solid + Preact | ❌ React only | |
| Multi-bundler | ✅ Vite + Webpack + Turbopack | ❌ N/A (proxy) | ✅ Vite + Webpack + Turbopack | ❌ N/A | ❌ Dev server middleware |
| MCP tools | ✅ 12 tools + 4 prompts | ❌ Proprietary protocol | ✅ 9 tools | ❌ Built-in agent | |
| Agent-agnostic | ✅ Any MCP client | ✅ | ✅ | ❌ Bundled Elixir agent | |
| In-app element picker | ✅ Lit shadow DOM | ✅ Toolbar | ✅ Inspector bar | ✅ Click-to-capture | ✅ Chat interface |
| Source mapping | ✅ Deterministic (AST IDs) | _debugSource (workaround needed) |
|||
| License | ✅ MIT | ✅ Open source | ✅ Open source |
No single competitor combines build-time stable IDs, deep runtime capture, bidirectional source↔DOM querying, and an MCP tool surface in a framework-agnostic way.
The transform plugins work with any framework. Install one and every JSX/Vue template element gets a data-ds ID mapped to its exact source location.
| Bundler | Plugin | Parser |
|---|---|---|
| Vite 5 | @domscribe/transform/plugins/vite |
Acorn (JS/JSX) or VueSFC |
| Webpack 5 | @domscribe/transform/plugins/webpack |
Babel (TS/JSX) or VueSFC |
| Turbopack | Self-initializing loader | Babel (TS/JSX) |
All plugins accept the same core options. Bundler-specific differences are noted below.
Relay — controls the local relay daemon that connects the browser and your agent:
| Option | Type | Default | Description |
|---|---|---|---|
autoStart |
boolean |
true |
Auto-start the relay daemon if not running |
port |
number |
0 (dynamic) |
Relay server port (only used when starting) |
host |
string |
'127.0.0.1' |
Relay server host (only used when starting) |
Overlay — configures the in-app element picker and annotation UI:
| Option | Type | Default | Description |
|---|---|---|---|
overlay |
boolean | OverlayPluginOptions |
true |
Enable/disable or configure the overlay |
initialMode |
'collapsed' | 'expanded' |
'collapsed' |
Initial display mode for the overlay tab |
debug |
boolean |
false |
Enable debug logging in the overlay |
import { domscribe } from '@domscribe/transform/plugins/vite';
domscribe({
include: /\.(jsx|tsx|vue)$/i, // File pattern to transform (default)
exclude: /node_modules|\.test\.|\.spec\./i, // Files to skip (default)
debug: false,
relay: { autoStart: true, port: 0, host: '127.0.0.1' },
overlay: true, // or { initialMode: 'collapsed', debug: false }
});import { DomscribeWebpackPlugin } from '@domscribe/transform/plugins/webpack';
new DomscribeWebpackPlugin({
enabled: true, // Defaults to true in dev, false in production
debug: false,
relay: { autoStart: true, port: 0, host: '127.0.0.1' },
overlay: true,
});Turbopack has no plugin system, so the loader is self-initializing — it manages relay lifecycle and overlay injection directly.
// next.config.ts (turbopack)
{
turbopack: {
rules: {
'*.{tsx,jsx}': {
loaders: [{
loader: '@domscribe/transform/turbopack-loader',
options: {
enabled: true,
debug: false,
relay: { autoStart: true, port: 0, host: '127.0.0.1' },
overlay: true,
},
}],
},
},
},
}Framework adapters capture live props, state, and component metadata from the running app. We ship adapters for:
| Framework | Adapter | Capture Strategy |
|---|---|---|
| React 18–19 | @domscribe/react |
Fiber walking, DevTools hook, BestEffort |
| Vue 3 | @domscribe/vue |
VNode inspection, Composition + Options API |
| Next.js 15–16 | @domscribe/next |
React adapter + withDomscribe() config wrapper |
| Nuxt 3+ | @domscribe/nuxt |
Vue adapter + auto-configured Nuxt module |
Using a different framework? The FrameworkAdapter interface in @domscribe/runtime lets you build your own adapter — implement getComponentInstance, captureProps, and captureState, and the rest of the pipeline (element tracking, PII redaction, relay transmission) works automatically. See the Custom Adapters Guide for the full interface, a worked example, and integration instructions.
RuntimeManager is the browser-side singleton that captures live props, state, and DOM context from instrumented elements. It is initialized automatically by the framework adapters (@domscribe/react, @domscribe/vue, etc.), but you can configure it directly:
import { RuntimeManager } from '@domscribe/runtime';
const runtime = RuntimeManager.getInstance();
await runtime.initialize({
adapter: myAdapter,
debug: false,
redactPII: true,
blockSelectors: [],
});| Option | Type | Default | Description |
|---|---|---|---|
adapter |
FrameworkAdapter |
NoopAdapter |
Framework adapter for runtime context capture |
debug |
boolean |
false |
Enable debug logging |
redactPII |
boolean |
true |
Redact potentially sensitive values (emails, tokens, etc.) in captured data |
blockSelectors |
string[] |
[] |
CSS selectors for elements to skip during capture |
The FrameworkAdapter interface expected by adapter:
interface FrameworkAdapter {
readonly name: string;
readonly version?: string;
getComponentInstance(element: HTMLElement): Nullable<unknown>;
captureProps(component: unknown): Nullable<Record<string, unknown>>;
captureState(component: unknown): Nullable<Record<string, unknown>>;
getComponentName?(component: unknown): Nullable<string>;
getComponentTree?(component: unknown): Nullable<ComponentTreeNode>;
}| Tool | Description |
|---|---|
domscribe.query.bySource |
Query a source file + line and get live runtime context from the browser (props, state, DOM snapshot) |
domscribe.manifest.query |
Find all manifest entries by file path, component name, or element ID |
domscribe.manifest.stats |
Manifest coverage statistics (entry count, file count, component count, cache hit rate) |
| Tool | Description |
|---|---|
domscribe.resolve |
Resolve a single data-ds element ID to its ManifestEntry (file, line, col, component) |
domscribe.resolve.batch |
Resolve multiple element IDs in one call |
| Tool | Description |
|---|---|
domscribe.annotation.process |
Atomically claim the next queued annotation (claimNext — prevents concurrent agent conflicts) |
domscribe.annotation.respond |
Attach agent response and transition to PROCESSED |
domscribe.annotation.updateStatus |
Manually transition annotation status |
domscribe.annotation.get |
Retrieve annotation by ID |
domscribe.annotation.list |
List annotations with status/filter options |
domscribe.annotation.search |
Full-text search across annotation content |
| Tool | Description |
|---|---|
domscribe.status |
Relay daemon health, manifest stats, queue counts |
| Prompt | Purpose |
|---|---|
process_next |
Guide through annotation processing |
check_status |
Check relay status |
explore_component |
Explore component metadata |
find_annotations |
Search for annotations |
| From | To | Trigger |
|---|---|---|
QUEUED |
PROCESSING |
Agent calls domscribe.annotation.process |
PROCESSING |
PROCESSED |
Agent calls domscribe.annotation.respond |
PROCESSING |
FAILED |
Agent error or timeout |
PROCESSED |
ARCHIVED |
Developer archives via overlay |
A developer clicks an element in picker mode, types an instruction in the sidebar, and submits. The annotation is stored as QUEUED. A coding agent picks it up via domscribe.annotation.process (atomic — no two agents process the same annotation), resolves the source location, edits the code, and calls domscribe.annotation.respond. The overlay receives a WebSocket broadcast and shows the agent's response in real time.
domscribe serve # Start relay server (foreground or --daemon)
domscribe status # Check relay daemon status
domscribe stop # Stop relay daemon
domscribe init # Initialize workspace (.domscribe directory)
domscribe mcp # Run as MCP server via stdioFor agent MCP configuration, use the standalone binary:
domscribe-mcp # Standalone MCP server binary (use this in your agent's MCP config)withDomscribe() and the Nuxt module only activate in dev mode. In production, @domscribe/overlay is aliased to a no-op stub — zero data-ds attributes, zero relay connections, zero overlay scripts in the production bundle. This is enforced by production-strip.test.ts in CI, which runs against every real fixture.
CI runs four pipeline stages, with integration and e2e parallelized across fixtures:
checks (lint, test, build, typecheck)
↓
build-registry (build → publish to Verdaccio → upload storage artifact)
↓
integration [8 fixtures in parallel] e2e [14 fixtures in parallel]
All tests run against real published packages via a Verdaccio local registry — no mocked package resolution. The build-registry job publishes once and uploads the registry storage as an artifact; downstream matrix jobs download it and start Verdaccio from the pre-populated storage.
Fixtures: React 18/19 (Vite 5, Webpack 5), Next.js 15/16, Vue 3 (Vite 5, Webpack 5), and Nuxt 3. Integration tests validate builds programmatically (Vite/Webpack only). E2E tests use Playwright with real browser interaction including shadow DOM piercing.
| Package | Description |
|---|---|
@domscribe/core |
Zod schemas, RFC 7807 error system, ID generation, PII redaction, constants |
@domscribe/manifest |
Append-only JSONL manifest, IDStabilizer (xxhash64), BatchWriter, ManifestCompactor |
@domscribe/relay |
Fastify HTTP/WS server, MCP stdio adapter, CLI, annotation lifecycle |
@domscribe/transform |
Parser-agnostic AST injection (Acorn, Babel, VueSFC), bundler plugins |
@domscribe/runtime |
Browser-side ElementTracker, ContextCapturer, BridgeDispatch |
@domscribe/overlay |
Lit web components (shadow DOM), element picker, annotation UI |
@domscribe/react |
React fiber walking, props/state extraction, Vite + Webpack plugins |
@domscribe/vue |
Vue 3 VNode resolution, Composition + Options API support, Vite + Webpack plugins |
@domscribe/next |
withDomscribe() config wrapper for Next.js 15 + 16 |
@domscribe/nuxt |
Nuxt 3+ module with auto-relay and runtime plugin |
@domscribe/test-fixtures |
Black-box integration + e2e suite (not published) |
pnpm install
nx run-many -t build test lint typecheckConventions are in .claude/rules/. PRs welcome.




