Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions packages/domscribe-relay/src/mcp/tools/dormant-status.tool.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ describe('DormantStatusTool', () => {
expect(structured['cwd']).toBe(cwd);
});

it('should return guidance with setup instructions', async () => {
it('should return guidance explaining dormant state', async () => {
// Arrange
const tool = new DormantStatusTool('/tmp/test');

Expand All @@ -28,10 +28,23 @@ describe('DormantStatusTool', () => {
// Assert
const structured = result.structuredContent as Record<string, unknown>;
expect(structured['guidance']).toEqual(expect.any(String));
expect(structured['guidance']).toContain('npx domscribe init');
expect(structured['guidance']).toContain('.domscribe');
});

it('should return actionable next steps for the agent', async () => {
// Arrange
const tool = new DormantStatusTool('/tmp/test');

// Act
const result: CallToolResult = await tool.toolCallback({});

// Assert
const structured = result.structuredContent as Record<string, unknown>;
expect(structured['nextSteps']).toEqual(expect.any(String));
expect(structured['nextSteps']).toContain('package.json');
expect(structured['nextSteps']).toContain('@domscribe');
});

it('should return text content matching structured content', async () => {
// Arrange
const tool = new DormantStatusTool('/tmp/test');
Expand Down
16 changes: 14 additions & 2 deletions packages/domscribe-relay/src/mcp/tools/dormant-status.tool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ const DormantStatusToolOutputSchema = McpToolOutputSchema.extend({
cwd: z
.string()
.describe('The working directory where the MCP server was started'),
guidance: z.string().describe('Instructions for setting up Domscribe'),
guidance: z.string().describe('Explanation of why Domscribe is not active'),
nextSteps: z
.string()
.describe('Actionable steps for the agent to set up Domscribe'),
});

type DormantStatusToolOutput = z.infer<typeof DormantStatusToolOutputSchema>;
Expand All @@ -48,7 +51,16 @@ export class DormantStatusTool implements McpToolDefinition<
guidance:
'Domscribe is not active in this workspace. ' +
'No .domscribe/ directory was found at or above the current working directory. ' +
'To set up Domscribe, run `npx domscribe init` in your project root.',
'The .domscribe/ directory is created automatically when the dev server starts ' +
'with the Domscribe bundler plugin configured.',
nextSteps:
'To set up Domscribe: ' +
'(1) Read package.json to detect the framework (next/nuxt/react/vue + vite/webpack), ' +
'(2) detect the package manager from lockfiles (pnpm-lock.yaml/yarn.lock/bun.lock), ' +
'(3) install the appropriate @domscribe/* package as a dev dependency, ' +
'(4) edit the bundler config file to add the Domscribe plugin, ' +
'(5) add .domscribe to .gitignore, ' +
'(6) ask the user to start their dev server.',
};

return {
Expand Down
69 changes: 67 additions & 2 deletions packages/domscribe-relay/src/skills/domscribe/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
name: domscribe
description: Work with Domscribe — the pixel-to-code bridge. Use when editing or modifying UI components (React, Vue, Next.js, Nuxt), implementing features from captured UI annotations, querying runtime context for source locations, exploring component structure, or when user mentions annotations, queued tasks, UI changes, props, state, DOM, or asks about how elements render at runtime.
allowed-tools: Read, Edit, Write, mcp__domscribe__*, domscribe.*, mcp.domscribe.*, process_next, check_status, explore_component, find_annotations
description: Work with Domscribe — the pixel-to-code bridge. Use when setting up, initializing, or configuring Domscribe for a project, OR when editing or modifying UI components (React, Vue, Next.js, Nuxt), implementing features from captured UI annotations, querying runtime context for source locations, exploring component structure, or when user mentions annotations, queued tasks, UI changes, props, state, DOM, or asks about how elements render at runtime.
allowed-tools: Read, Edit, Write, Bash, Glob, mcp__domscribe__*, domscribe.*, mcp.domscribe.*, process_next, check_status, explore_component, find_annotations
---

# Domscribe
Expand All @@ -11,6 +11,71 @@ Domscribe bridges running UI and source code. It maps every rendered element to
- **UI → Code**: User clicks an element in the browser, Domscribe captures it as an annotation with source location, runtime context, and user intent. You claim and implement it.
- **Code → UI**: You're editing a source file and want to know what an element looks like at runtime. Query by file and line to get live props, state, and DOM snapshot.

## Setup / Initialization

If `domscribe.status` returns `active: false`, Domscribe is not yet configured in this workspace. The `.domscribe/` directory is created automatically when the dev server starts with the Domscribe bundler plugin configured. Follow this procedure to set it up:

1. **Confirm dormant state** — call `domscribe.status`. If `active: false`, proceed. Note the `cwd` in the response.
2. **Detect framework** — read `package.json` (at `cwd`, or at the `appRoot` from `domscribe.config.json` if it exists) and match dependencies against the table below.
3. **Detect package manager** — check which lockfile exists at the project root.
4. **Install the package** — run the appropriate install command via Bash (e.g., `pnpm add -D @domscribe/next`).
5. **Edit the bundler config** — read the config file and apply the integration pattern. Load `references/config-patterns.md` for the exact import, transformation, and example for each framework.
6. **Update `.gitignore`** — if `.domscribe` is not already listed, append a `# Domscribe artifacts` comment and `.domscribe` entry.
7. **Inform the user** — tell them to start (or restart) their dev server. Domscribe activates automatically on first run.

### Framework Detection

Check `dependencies` and `devDependencies` in `package.json`. Match top-down (first match wins):

| Dependency | Framework |
| ------------------- | ------------- |
| `next` | next |
| `nuxt` | nuxt |
| `react` + `vite` | react-vite |
| `react` (no `vite`) | react-webpack |
| `vue` + `vite` | vue-vite |
| `vue` (no `vite`) | vue-webpack |
| `vite` only | other-vite |
| `webpack` only | other-webpack |

### Package Mapping

| Framework | Package | Config file |
| ------------- | ---------------------- | ------------------- |
| next | `@domscribe/next` | `next.config.ts` |
| nuxt | `@domscribe/nuxt` | `nuxt.config.ts` |
| react-vite | `@domscribe/react` | `vite.config.ts` |
| react-webpack | `@domscribe/react` | `webpack.config.js` |
| vue-vite | `@domscribe/vue` | `vite.config.ts` |
| vue-webpack | `@domscribe/vue` | `webpack.config.js` |
| other-vite | `@domscribe/transform` | `vite.config.ts` |
| other-webpack | `@domscribe/transform` | `webpack.config.js` |

### Package Manager Detection

| Lockfile | Package manager |
| ------------------------ | --------------- |
| `pnpm-lock.yaml` | pnpm |
| `yarn.lock` | yarn |
| `bun.lock` / `bun.lockb` | bun |
| (none) | npm |

Install command pattern: `<pm> add -D <package>` (pnpm/yarn/bun) or `npm install -D <package>`.

### Monorepo Projects

If `domscribe.config.json` exists at the project root with an `appRoot` field, the frontend app lives in that subdirectory. Install packages and find the bundler config relative to `appRoot`.

If the project appears to be a monorepo (e.g., `apps/`, `packages/` directories, workspace config in `package.json`) but no `domscribe.config.json` exists, ask the user which directory contains the frontend app. Then write `domscribe.config.json` at the project root:

```json
{ "appRoot": "apps/web" }
```

### After Setup

The MCP server starts in dormant mode when no `.domscribe/` directory exists. After the user starts their dev server, the bundler plugin creates `.domscribe/` automatically. The MCP server will need to restart to detect the new workspace and transition to active mode with the full tool suite.

## Editing Components (Code → UI)

**Why query runtime state?** Source code alone doesn't tell you what props a component actually received, whether a conditional branch rendered, what CSS classes were applied, or what text the user sees. `domscribe.query.bySource` gives you the live truth from the browser.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
# Domscribe Config Patterns

How to edit each framework's bundler config to integrate Domscribe. The 8 framework combinations reduce to 4 distinct integration patterns.

---

## Next.js — HOF Wrapper

**Import to add:**

```typescript
import { withDomscribe } from '@domscribe/next';
```

**How to edit:**

Wrap the existing default export with `withDomscribe()()`. This is a higher-order function that returns a config transformer.

- If the file has `export default <expression>` — change to `export default withDomscribe()(<expression>)`
- If the file has `const config = ...; export default config` — change to `export default withDomscribe()(config)`
- If the file already uses another wrapper (e.g., `withMDX`), compose them: `export default withDomscribe()(withMDX(config))`

**Full example (`next.config.ts`):**

```typescript
import type { NextConfig } from 'next';
import { withDomscribe } from '@domscribe/next';

const nextConfig: NextConfig = {};

export default withDomscribe()(nextConfig);
```

---

## Nuxt — Module Registration

**How to edit:**

Add `'@domscribe/nuxt'` as a string to the `modules` array inside `defineNuxtConfig()`. No import needed — Nuxt resolves the module by package name.

- If no `modules` key exists in the config object, add it: `modules: ['@domscribe/nuxt']`
- If `modules` exists, append `'@domscribe/nuxt'` to the array

**Full example (`nuxt.config.ts`):**

```typescript
export default defineNuxtConfig({
modules: ['@domscribe/nuxt'],
});
```

---

## Vite Plugin (React / Vue / Other)

Three variants — same pattern, different import path:

| Framework | Import path |
| ---------- | ----------------------------------- |
| react-vite | `@domscribe/react/vite` |
| vue-vite | `@domscribe/vue/vite` |
| other-vite | `@domscribe/transform/plugins/vite` |

**Import to add** (use the path from the table above):

```typescript
import { domscribe } from '@domscribe/react/vite';
```

**How to edit:**

Add `domscribe()` to the `plugins` array inside `defineConfig()`. Place it after the framework plugin (e.g., after `react()` or `vue()`).

- If `plugins` exists, append `domscribe()` to the array
- If `plugins` doesn't exist, add `plugins: [domscribe()]`

**Full example — React + Vite (`vite.config.ts`):**

```typescript
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { domscribe } from '@domscribe/react/vite';

export default defineConfig({
plugins: [react(), domscribe()],
});
```

**Full example — Vue + Vite (`vite.config.ts`):**

```typescript
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import { domscribe } from '@domscribe/vue/vite';

export default defineConfig({
plugins: [vue(), domscribe()],
});
```

**Full example — Other + Vite (`vite.config.ts`):**

```typescript
import { defineConfig } from 'vite';
import { domscribe } from '@domscribe/transform/plugins/vite';

export default defineConfig({
plugins: [domscribe()],
});
```

---

## Webpack Plugin + Loader (React / Vue / Other)

Three variants — same pattern, different import path:

| Framework | Import path |
| ------------- | -------------------------------------- |
| react-webpack | `@domscribe/react/webpack` |
| vue-webpack | `@domscribe/vue/webpack` |
| other-webpack | `@domscribe/transform/plugins/webpack` |

**Two edits required:**

### Edit 1 — Add the transform loader rule

Add a pre-enforce loader rule to `module.rules`. This must run before other loaders:

```javascript
{
test: /\.[jt]sx?$/,
exclude: /node_modules/,
enforce: 'pre',
use: [
{
loader: '@domscribe/transform/webpack-loader',
options: { enabled: process.env.NODE_ENV !== 'production' },
},
],
}
```

### Edit 2 — Add the webpack plugin

Add the plugin instance to the `plugins` array. Use the import path from the table above:

```javascript
const { DomscribeWebpackPlugin } = require('@domscribe/react/webpack');

// In the plugins array:
new DomscribeWebpackPlugin({
enabled: process.env.NODE_ENV !== 'production',
overlay: true,
});
```

**Full example — React + Webpack (`webpack.config.js`):**

```javascript
const { DomscribeWebpackPlugin } = require('@domscribe/react/webpack');

const isDevelopment = process.env.NODE_ENV !== 'production';

module.exports = {
module: {
rules: [
{
test: /\.[jt]sx?$/,
exclude: /node_modules/,
enforce: 'pre',
use: [
{
loader: '@domscribe/transform/webpack-loader',
options: { enabled: isDevelopment },
},
],
},
// ... existing loaders
],
},
plugins: [
new DomscribeWebpackPlugin({
enabled: isDevelopment,
overlay: true,
}),
],
};
```

---

## Package Manager Install Commands

| Lockfile | Package manager | Install command |
| ------------------------ | --------------- | ---------------- |
| `pnpm-lock.yaml` | pnpm | `pnpm add -D` |
| `yarn.lock` | yarn | `yarn add -D` |
| `bun.lock` / `bun.lockb` | bun | `bun add -D` |
| (none) | npm | `npm install -D` |

## Gitignore

If `.domscribe` is not already listed in `.gitignore`, append:

```
# Domscribe artifacts
.domscribe
```
Loading