feat(editor): Add dev-panel for DOM-annotated feedback prompts (no-changelog)#28761
feat(editor): Add dev-panel for DOM-annotated feedback prompts (no-changelog)#28761
Conversation
…no-changelog) Adds a dev-only overlay that lets developers pick any DOM element in the editor, describe a change in a popover, and either send it to a running Claude Code session via Claude Channels or copy the prompt + element context to the clipboard for use with other tools. - packages/@n8n/dev-panel-channel: MCP stdio server that exposes a local HTTP endpoint and pushes notifications/claude/channel events into the running Claude session. - packages/frontend/editor-ui/src/app/dev/dev-panel: Vue overlay with element picker, prompt popover, clipboard formatter, and channel health polling. Mounted only when import.meta.env.DEV is true. - Root scripts: dev:claude-panel launches Claude with the channel loaded. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ed markers and inline editing (no-changelog) Reworks the dev panel into a collapsible black FAB that expands into an icon toolbar, and adds multi-annotation capture with clipboard export. - Collect annotations across picks instead of sending one-off prompts. Each capture drops a numbered marker on its anchor element; clicking a marker reopens the popover pre-filled for editing. - Copy all annotations at once as Agentation-style markdown, with page path, viewport, DOM breadcrumb, source file:line:col, Vue component, test id, and feedback text per item. Source paths are trimmed to start at packages/ or src/. CSS-module hashes of the form `_name_hash_line` are stripped from selectors and class lists so the pasted context remains greppable. - Single black circular FAB with channel-health dot and annotation count badge; clicking expands into a toolbar (pick / copy / clear / close) and enters picking mode. ESC on the popover cancels without dropping out of annotation mode. - Markers only render while the panel is expanded, and swap the number for a pen icon on hover to signal editability. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
… TTL (no-changelog) Annotations are stored in localStorage keyed by page pathname and render from captured bounding-box coordinates instead of re-resolving DOM element references — so markers stay put across reloads and SPA navigation even when selectors can't match (CSS-module class hashing, duplicate selectors, async-rendered content). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ngelog) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…d Ctrl+Shift+D (no-changelog) Drop the MCP channel server and Claude send-now flow; the first version only needs to copy annotations as markdown. Hide the panel by default so it does not interfere with regular app testing, and toggle it with Ctrl+Shift+D (uses event.key so it works across keyboard layouts like Colemak). Visibility persists in localStorage. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Performance ComparisonComparing current → latest master → 14-day baseline Memory consumption baseline with starter plan resources
docker-stats
Idle baseline with Instance AI module loaded
How to read this table
|
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Bundle ReportChanges will decrease total bundle size by 533.22kB (-1.17%) ⬇️. This is within the configured threshold ✅ Detailed changes
Affected Assets, Files, and Routes:view changes for bundle: editor-ui-esmAssets Changed:
Files in
|
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
|
/size-limit-override |
There was a problem hiding this comment.
5 issues found across 8 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/frontend/editor-ui/src/app/dev/dev-panel/DevPanel.vue">
<violation number="1" location="packages/frontend/editor-ui/src/app/dev/dev-panel/DevPanel.vue:131">
P2: Editing a multi-element annotation collapses it to a single element because the existing multi-selection is cleared before save.</violation>
<violation number="2" location="packages/frontend/editor-ui/src/app/dev/dev-panel/DevPanel.vue:634">
P2: Custom agent: **Design System Tokens**
Extensive hard-coded color values (`#2563eb`, `#1a1a1a`, `#fff`, `#10b981`, `#1d4ed8`, and multiple `rgb()` values) throughout the component CSS. Per the design system style rules §2, hard-coded colors are a strong warning — use semantic or primitive color tokens instead. The file already uses tokens for spacing, typography, and some semantic colors (`--color--success`, `--color--danger`), so the color hardcoding is inconsistent. Even for a dev-only overlay, keeping token usage consistent avoids drift if this pattern is copied elsewhere.</violation>
<violation number="3" location="packages/frontend/editor-ui/src/app/dev/dev-panel/DevPanel.vue:806">
P2: Custom agent: **Design System Tokens**
Hard-coded `font-size: 10px` — use `var(--font-size--2xs)` for consistency with the same file's `.dev-panel-marker` and `.dev-panel-fab-badge` classes.</violation>
</file>
<file name="packages/frontend/editor-ui/src/app/dev/dev-panel/PromptPopover.vue">
<violation number="1" location="packages/frontend/editor-ui/src/app/dev/dev-panel/PromptPopover.vue:78">
P2: Remove the global resize/scroll listeners on unmount to avoid accumulating duplicate handlers when the popover is opened/closed multiple times.</violation>
</file>
<file name="packages/frontend/editor-ui/src/app/dev/dev-panel/formatPrompt.ts">
<violation number="1" location="packages/frontend/editor-ui/src/app/dev/dev-panel/formatPrompt.ts:45">
P2: Multi-select prompts lose the metadata for all but the first selected element.</violation>
</file>
Architecture diagram
sequenceDiagram
participant User
participant Main as main.ts
participant Panel as DevPanel.vue
participant Picker as useElementPicker.ts
participant Context as collectElementContext.ts
participant Storage as annotationStorage.ts
participant Clipboard as Clipboard API
Note over Main,Panel: NEW: Dev-only overlay injection
Main->>Panel: NEW: import & mountDevPanel() (if import.meta.env.DEV)
Panel->>Storage: NEW: loadAnnotations(path)
Storage-->>Panel: Returns stored annotations (7-day TTL)
Note over User,Picker: Activation & Element Selection
User->>Panel: Press Ctrl+Shift+D
Panel->>Picker: NEW: start() element picking
Picker->>Picker: Add DOM event listeners (mousedown, mousemove, click)
alt Single Click
User->>Picker: Click DOM Element
else Shift-Click / Drag
User->>Picker: Multi-select elements
end
Picker-->>Panel: NEW: emit selectedElement(s)
Panel->>Panel: Show PromptPopover.vue at element position
Note over User,Storage: Annotation & Context Extraction
User->>Panel: Input feedback text + Save (Cmd+Enter)
Panel->>Context: NEW: collectElementContext(Element)
Context->>Context: Read [data-v-inspector] (Source File/Line)
Context->>Context: Read [data-testid] & Vue Component Name
Context->>Context: Generate CSS Selector & DOM Path
Context-->>Panel: Returns Metadata Object
Panel->>Storage: NEW: saveAnnotations(path, data)
Storage->>Storage: JSON.stringify to localStorage
Note over User,Clipboard: Export Flow
User->>Panel: Click "Copy to Clipboard"
Panel->>Panel: formatAnnotationsForClipboard()
Panel->>Clipboard: NEW: Write formatted Markdown block
Clipboard-->>User: Feedback ready for Claude Code/LLM
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review, or fix all with cubic.
- Preserve multi-element selection when editing an annotation - Emit source/vue/testid metadata for every element in multi-select clipboard output - Remove resize/scroll listeners in PromptPopover onUnmounted - Use --font-size--2xs token for toolbar badge Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…no-changelog) Swap hex and rgb() colors in DevPanel.vue for design-system tokens, using color-mix() for the translucent overlays and shadows. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…changelog) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ay (no-changelog) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
… (no-changelog) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…over (no-changelog) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…nd theme-stable styling (no-changelog) - Use neutral primitives for FAB/toolbar so they stay dark in both themes - Swap custom popover textarea and buttons for N8nInput/N8nButton - Swap theme-aware shadows for black-alpha primitives - Animate copy-button icon swap to a checkmark on success and drop the success toast - Enlarge copy-button annotation-count badge Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
There was a problem hiding this comment.
1 issue found across 2 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/frontend/editor-ui/src/app/dev/dev-panel/PromptPopover.vue">
<violation number="1" location="packages/frontend/editor-ui/src/app/dev/dev-panel/PromptPopover.vue:106">
P2: Attach the keyboard shortcut handler to the dialog container, not just the textarea, so Esc/Cmd+Enter still work after tabbing to the buttons.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review, or fix all with cubic.
…n dev-panel popover (no-changelog) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…es textarea (no-changelog) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
| patchHistory(); | ||
| loadForPath(currentPath.value); | ||
| domObserver = new MutationObserver(scheduleReresolve); | ||
| domObserver.observe(document.body, { childList: true, subtree: true }); | ||
| }); | ||
|
|
||
| onUnmounted(() => { | ||
| window.removeEventListener('scroll', scheduleBump, true); | ||
| window.removeEventListener('resize', scheduleBump); | ||
| window.removeEventListener('keyup', handleShiftKeyUp, true); | ||
| window.removeEventListener('keydown', handleToggleShortcut, true); |
There was a problem hiding this comment.
Panel is hidden by default, but the MutationObserver on document.body, scroll/resize listeners, and history patch are installed unconditionally on mount.
For devs who never open the panel, this is constant overhead on every app-wide DOM mutation. Can we gate these on visible (attach on togglin on the panel, detach on toggle-off)?
There was a problem hiding this comment.
Done in fc47feb — observers (scroll, resize, keyup, MutationObserver, router subscription) are now attached lazily inside an activateObservers helper that runs when visible flips true and tears down on toggle-off. Only the Ctrl+Shift+D keydown listener stays mounted unconditionally so the toggle still works.
| expanded.value = false; | ||
| stop(); | ||
| clearSelection(); | ||
| pendingMulti.value = []; | ||
| editingId.value = null; | ||
| } | ||
| } | ||
|
|
||
| function updatePath() { | ||
| currentPath.value = window.location.pathname; | ||
| } | ||
|
|
||
| let restoreHistory: (() => void) | null = null; |
There was a problem hiding this comment.
Ctrl+Shift+D iirc is "Bookmark all tabs" in Chrome/Edge on Windows/Linux, and the handler doesn't check the event target, so pressing it while typing in an input silently eats the keystroke. Could we either pick a less-claimed chord, or early-return when event.target is editable?
There was a problem hiding this comment.
Fixed in fc47feb — added an isEditableTarget check that early-returns when the event target is an <input>, <textarea>, <select>, or anything contenteditable, so the chord no longer eats keystrokes during typing. Keeping Ctrl+Shift+D for now since the panel is dev-only and the editable check covers the main hazard.
| return result; | ||
| }; | ||
| history.replaceState = function (...args) { | ||
| const result = originalReplace(...args); | ||
| updatePath(); | ||
| return result; | ||
| }; | ||
| restoreHistory = () => { | ||
| history.pushState = originalPush; | ||
| history.replaceState = originalReplace; | ||
| }; | ||
| } | ||
|
|
||
| function loadForPath(path: string) { | ||
| const stored = loadAnnotations(path); | ||
| annotations.value = stored.map((a) => ({ | ||
| ...a, | ||
| elements: a.contexts |
There was a problem hiding this comment.
I wouldn't monkey-patch history.pushState/replaceState globally which is fragile. Since good ole' vue-router is already in the app, using useRouter().afterEach(...) would give us the same without touching the global.
There was a problem hiding this comment.
Fixed in fc47feb — dropped the pushState/replaceState monkey-patch and now subscribe via router.afterEach (importing the router singleton from @/app/router). popstate is also no longer needed since the router already handles browser back/forward.
- Gate global observers (scroll/resize/MutationObserver/router) on `visible` so they're only active while the dev panel is open - Replace history.pushState/replaceState monkey-patch with router.afterEach - Skip Ctrl+Shift+D toggle when target is editable (input/textarea/contenteditable)
Adds a flag toolbar button that opens a panel listing PostHog flags and lets you set N8N_EXPERIMENT_OVERRIDES per flag. Detects boolean vs variant flags from the SDK + persisted PostHog blob, supports custom variant strings, and reloads to apply.
There was a problem hiding this comment.
1 issue found across 3 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/frontend/editor-ui/src/app/dev/dev-panel/FlagPanel.vue">
<violation number="1" location="packages/frontend/editor-ui/src/app/dev/dev-panel/FlagPanel.vue:281">
P2: Custom agent: **Design System Tokens**
Global overlay/dropdown layers use hard-coded max z-index values instead of design-system layering tokens.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review, or fix all with cubic.
…ff (no-changelog) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
You're iterating quickly on this pull request. To help protect your rate limits, cubic has paused automatic reviews on new pushes for now—when you're ready for another review, comment |
|
The flag panel should seed from I’d put this in const evaluated = readEvaluatedFlags();
const persisted = readPersistedFlags();
const sdk = readSdkFlags();
const ph: Record<string, FlagValue> = { ...persisted, ...sdk, ...evaluated }; |
…ags (no-changelog) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
Good catch — fixed in b2ff9aa. |
…nel (no-changelog) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ling (no-changelog) - Drag-end now installs a one-shot capture click suppressor so the post-drag click can never leak to the underlying app, and the next picker session starts with a clean slate. - Annotation markers compute position from the live element's bounding rect when it's connected, falling back to the captured bbox only when the element resolution failed. Keeps markers anchored after layout shifts. - PromptPopover now resyncs its prompt ref when the parent reuses the instance for a different annotation. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
Got released with |
Summary
Adds a dev-only overlay to the editor-ui that lets developers pick DOM
elements (single click, shift-click multi-select, or drag-to-select), attach
a free-form prompt to each, and copy the whole batch as formatted markdown
to paste into Claude Code (or any other tool).
Highlights
annotated element; the copy button emits a markdown block with page path,
viewport, DOM path, source file/line (via
vite-plugin-vue-inspector),Vue component name, test id, and the user's prompt.
localStoragewith a 7-day TTL sothey survive refresh and HMR; re-resolved against the current DOM when
navigation or mutations shift things around.
lists all PostHog flags discovered in the SDK + persisted blobs, lets you
set/clear overrides (boolean or variant, with custom-value input), and
writes them to the same
N8N_EXPERIMENT_OVERRIDESlocalStorage key thePlaywright suite already uses. Reload-and-apply button included.
(uses
event.ctrlKey+event.keyso it works on macOS and onnon-QWERTY layouts like Colemak). Visibility is remembered across reloads.
import.meta.env.DEV, so Vite tree-shakes itout of production bundles. No backend, no extra process.
Preview
Why
Speeds up the "I noticed something on this screen, let me describe it
precisely to an AI" loop. Copying N annotations at once beats round-tripping
a single screenshot, and every annotation carries enough source metadata
that an AI assistant can usually jump straight to the right file.
The flag override panel folds in a workflow that previously lived in a
bookmarklet, so toggling experiments no longer requires editing
localStorage by hand or keeping a separate snippet around.
Reviewer notes
`nodes-base`, etc.
Related Linear tickets, Github issues, and Community forum posts
N/A — internal dev tooling.
Review / Merge checklist