@01.works/diff-layer
v0.0.1-dev.0
Published
Event-driven browser overlay for visualizing AI-authored UI changes.
Downloads
97
Maintainers
Readme
@01.works/diff-layer
Event-driven browser overlay for visualizing UI changes authored by AI agents.
This package does not track React renders. It renders explicit change events provided by an app, AI tool, browser extension, or test harness.
Resolved changes render as persistent target outlines with compact AI markers.
Click a marker or timeline item to inspect one selected change in the timeline
panel. This keeps multiple nearby changes visible without stacking detail
cards over the app.
Quick Start In Your App
Install the dogfood channel:
pnpm add @01.works/diff-layer@dev1. Install the overlay in development
import { installAIChangeOverlay } from '@01.works/diff-layer/client';
installAIChangeOverlay({
mutationRetryMs: 1000,
});Without an explicit enabled option, the overlay installs only when runtime
NODE_ENV is exactly development. Browser runtimes with no detectable
NODE_ENV default to disabled. For those bundlers, pass the bundler's own
development flag, such as enabled: import.meta.env.DEV in Vite.
2. Mark the UI you want to review
export function BillingSummary() {
return (
<section data-ai-id="billing-summary">
<h2 data-ai-id="billing-title">Current plan</h2>
<button data-ai-id="upgrade-cta">Upgrade</button>
</section>
);
}Stable data-ai-id hooks make explicit AI change events easy to map back to
visible UI without depending on brittle generated class names.
3. Push a change from an AI tool, extension, or test harness
window.__AI_CHANGE_OVERLAY__?.push({
id: crypto.randomUUID(),
source: 'ai',
label: 'Upgrade CTA copy changed',
selector: "[data-ai-id='upgrade-cta']",
before: 'Upgrade',
after: 'Compare plans',
reason: 'Clarified that the next step shows plan options before purchase.',
filePath: 'app/billing/page.tsx',
line: 38,
timestamp: Date.now(),
});The event is explicit: the overlay shows what changed, where it changed, why it changed, and which source location is associated with the update.
window.dispatchEvent(
new CustomEvent('ai-change-overlay:push', {
detail: {
id: crypto.randomUUID(),
source: 'ai',
label: 'Billing summary risk note added',
selector: "[data-ai-id='billing-summary']",
reason: 'Called out a plan limit that reviewers should verify.',
filePath: 'app/billing/page.tsx',
line: 52,
timestamp: Date.now(),
},
}),
);Use this path when the producer cannot import the package directly.
Public API
Root export:
import {
CHANGE_OVERLAY_EVENT_NAME,
CHANGE_OVERLAY_GLOBAL_NAME,
getAIChangeOverlay,
installAIChangeOverlay,
registerChangeTarget,
unregisterChangeTarget,
type AIChangeOverlayGlobal,
type ChangeEvent,
type ChangeOverlayOptions,
type ChangeSource,
type CreateChangeEventInput,
type PushChangeInput,
type SourceLocation,
} from '@01.works/diff-layer';Client export:
import {
getAIChangeOverlay,
installAIChangeOverlay,
} from '@01.works/diff-layer/client';Options
| Option | Default | Description |
| --- | --- | --- |
| enabled | NODE_ENV === 'development' | Enables the overlay client. |
| maxHistory | 20 | Maximum changes kept in the timeline. |
| mutationRetryMs | 0 | How long to watch for targets that render after the event. |
| root | document.body | DOM node that receives overlay and timeline elements. |
| onOpenSource | undefined | Callback for the timeline source action. |
Change outlines and markers stay visible until the reviewer clears active overlays from the timeline or destroys the client. The selected change remains available in the timeline inspector while it is still in history. This package is a review aid for explicit AI-authored changes, not a transient render flash.
Change Event
type ChangeEvent = {
id: string;
source: 'ai' | 'manual' | 'system';
label: string;
selector?: string;
componentName?: string;
filePath?: string;
line?: number;
before?: string;
after?: string;
reason?: string;
timestamp: number;
};Malformed global or event payloads are ignored at runtime.
CustomEvent API
Non-importing producers can dispatch the event name directly:
window.dispatchEvent(
new CustomEvent('ai-change-overlay:push', {
detail: {
id: crypto.randomUUID(),
source: 'ai',
label: 'Pricing card layout changed',
selector: "[data-ai-id='pricing-card']",
reason: 'Improved scanability of the primary plan',
timestamp: Date.now(),
},
}),
);Component Targets
Use component names when a component boundary is easier to identify than a selector:
import { registerChangeTarget } from '@01.works/diff-layer';
registerChangeTarget('BillingSummary', "[data-ai-id='billing-summary']");Remove registrations when the target is no longer valid:
import { unregisterChangeTarget } from '@01.works/diff-layer';
unregisterChangeTarget('BillingSummary');Queue Before Install
Events can be queued before installation by assigning the global to an array:
window.__AI_CHANGE_OVERLAY__ = window.__AI_CHANGE_OVERLAY__ ?? [];
if (Array.isArray(window.__AI_CHANGE_OVERLAY__)) {
window.__AI_CHANGE_OVERLAY__.push({
id: crypto.randomUUID(),
source: 'ai',
label: 'Billing title changed',
selector: "[data-ai-id='billing-title']",
before: 'Plan',
after: 'Current plan',
timestamp: Date.now(),
});
}Late Targets
Enable mutationRetryMs when events can arrive before the target is mounted:
installAIChangeOverlay({
enabled: true,
mutationRetryMs: 1000,
});Source Hook
Clicking a rendered source action copies filePath or filePath:line to the
clipboard by default. Provide onOpenSource when you also want the source
action to open a trusted editor link.
Keep editor opening enabled only in development and only for trusted workspace-relative paths. Only a trusted local bridge that validates workspace containment should handle absolute paths.
installAIChangeOverlay({
onOpenSource: (change) => {
if (change.filePath === undefined) return;
const workspaceRoot = '/absolute/path/to/your/project';
const relativePath = change.filePath.replaceAll('\\', '/');
if (
relativePath.startsWith('/') ||
relativePath.split('/').includes('..') ||
/^[a-z][a-z0-9+.-]*:/i.test(relativePath)
) {
return;
}
const absolutePath = `${workspaceRoot}/${relativePath}`;
const suffix = change.line === undefined ? '' : `:${change.line}`;
window.location.href = `vscode://file/${encodeURI(absolutePath)}${suffix}`;
},
});The callback runs after the copy attempt. Clipboard failures are ignored so a trusted editor-opening callback can still run.
Verification
pnpm --filter @01.works/diff-layer test
pnpm --filter @01.works/diff-layer typecheck
pnpm --filter @01.works/diff-layer build
pnpm --filter @01.works/diff-layer test:packagedDev-Tag Publishing
This package is configured for dogfood releases on npm's dev dist-tag:
pnpm version:dev
pnpm publish:devEvery npm publish still needs a unique version. Consumers install the dogfood channel explicitly:
pnpm add @01.works/diff-layer@devDo not publish dogfood builds with the latest dist-tag.
