agent-panel
Agent Panel
The EFAgentPanel captures ef-edit CustomEvents dispatched whenever a user makes a meaningful composition edit in the GUI, deduplicates them by element+property via a keyed registry, groups them by selector, and renders a copy-able coding-agent prompt.
Event Pipeline
GUI interaction (canvas drag, inspector change, trim drag, loop toggle)
└─ component.dispatchEvent(createEditCustomEvent(editEvent))
└─ [bubbles: true, composed: true]
└─ EFWorkbench listener → agentPanel.addEdit(event)
└─ Map<editChangeKey, EditEvent> registry (deduplicates)
└─ groupEditsBySelector() → buildAgentPrompt() → copy button
EFWorkbench registers addEventListener('ef-edit', ...) on itself — because ef-edit bubbles and is composed, any component in any slot (timeline, hierarchy, canvas) can dispatch it without special routing.
Key Types (editEvents.ts)
// EditEvent — what the panel accumulates
interface EditEvent {
operation: EditOperation;
description: string; // human sentence
selector: string; // CSS path from composition root
elementHtml: string; // cleaned outerHTML snippet
timestamp: number;
}
// Operation union — extend here when adding new interaction types
type EditOperation =
| { type: "element-property-changed"; elementId; tagName; label; property; oldValue; newValue }
| { type: "element-moved"; elementId; tagName; label; fromX; fromY; toX; toY }
| { type: "element-resized"; elementId; tagName; label; x; y; width; height }
| { type: "element-rotated"; elementId; tagName; label; rotation }
Registry key format — determines deduplication:
- Property change:
selector::prop:propName - Move/resize/rotate:
selector::move,selector::resize,selector::rotate
Pattern: Adding a New ef-edit Dispatch Point
import {
createEditCustomEvent, buildSelectorPath, getElementHtml,
buildEditDescription, type ElementPropertyChangedOperation,
} from "../../editEvents.js";
const oldValue = element.someProperty;
element.someProperty = newValue;
const op: ElementPropertyChangedOperation = {
type: "element-property-changed",
elementId: el.id,
tagName: el.tagName.toLowerCase(),
label: el.textContent?.trim().slice(0, 30) || el.id || el.tagName.toLowerCase(),
property: "attr-name", // attribute name as used in HTML source
oldValue,
newValue,
};
this.dispatchEvent(createEditCustomEvent({
operation: op,
description: buildEditDescription(op),
selector: buildSelectorPath(el),
elementHtml: getElementHtml(el),
timestamp: Date.now(),
}));
buildPropertyEditEvent(element, property, oldValue, newValue) in EFInspector.ts is a shorter form for simple property edits.
Finding Current Dispatch Points
# Every call site that produces an ef-edit event
rg 'createEditCustomEvent' elements/packages/elements/src/
# Track subclasses that override render() — each must bind @trim-change-end
rg 'override render' elements/packages/elements/src/gui/timeline/tracks/
# Registered element schemas
rg 'SCHEMA_REGISTRY' elements/packages/elements/src/gui/elementPropertySchema.ts
What is NOT Wired (intentional)
Playback controls (play/pause/seek), pan-zoom/zoom level, and toolbar settings are display-only state — they should not produce ef-edit events. Hierarchy reorder (hierarchy-reorder from EFHierarchy) would require a new ElementReorderedOperation type; not yet implemented.
Shadow DOM Event Routing Gotcha
trim-change-end bubbles with composed: true from EFTrimHandles. TrackItem.render() binds @trim-change-end on the ef-trim-handles element. EFVideoTrack overrides render() entirely and provides its own ef-trim-handles binding. Any future track subclass that overrides render() and includes ef-trim-handles must bind both @trim-change and @trim-change-end — the base TrackItem.render() handlers are not inherited when render() is overridden.
Element Property Schema (elementPropertySchema.ts)
Static descriptors drive the inspector UI. Register new element types at the bottom via SCHEMA_REGISTRY. Use the factory functions:
timeDescriptor(attr, label, opts)— time values (stored as${ms}msattributes)enumDescriptor(attr, label, options, condition?)boolDescriptor(attr, label, condition?)numberDescriptor(attr, label, opts)stringDescriptor(attr, label)
Elements with no registry entry are invisible to the inspector. Check elementPropertySchema.test.ts for the test pattern.
Testing ef-edit Events
Unit tests (Node/vitest): use for editEvents.ts helpers — editChangeKey, rollUpEdits, groupEditsBySelector, buildAgentPrompt.
Browser tests (Chromium/vitest): required for any test involving DOM events or shadow DOM.
For trim ef-edit, dispatch trim-change-end directly on the ef-trim-handles host element — do not try to simulate full pointer events through shadow DOM layers, as pointer capture mechanics are unreliable in test environments:
const trimHandles = track.shadowRoot?.querySelector("ef-trim-handles");
trimHandles?.dispatchEvent(new CustomEvent("trim-change-end", {
detail: { elementId: video.id, type: "start" },
bubbles: true, composed: true,
}));
For loop ef-edit, click the loop button via shadowRoot.querySelector("button[title='Loop']").click().
Listen at document level — ef-edit is bubbles: true, composed: true, so it propagates from any component to document.
More from editframe/skills
video-analysis
Analyze video files using ffprobe, mp4dump, and jq. Use when investigating video samples, keyframes, MP4 box structure, codec info, packet timing, or debugging video playback issues.
75visual-thinking
Create visual analogies by mapping relational structure from familiar domains onto unfamiliar concepts using spatial relationships to make abstract patterns concrete. Covers static diagrams AND animated video storytelling (camera choreography, race comparisons, pacing). Use when explaining complex concepts, creating analogies, designing diagrams, creating explainer animations, or revealing system structure.
71css-animations
CSS animation fill-mode requirements for Editframe timeline system. Use when creating CSS animations, debugging flashing/flickering issues, or when user mentions animation problems, fade effects, slide effects, or sequential animations.
69threejs-compositions
Integrate Three.js 3D scenes into Editframe compositions via addFrameTask. Scenes are pure functions of time, fully scrubable, and renderable to MP4. Use when creating 3D animations, WebGL content in compositions, or integrating Three.js with Editframe's timeline system.
66editor-gui
Build video editing interfaces using Editframe's GUI web components. Assemble timeline, scrubber, filmstrip, preview, and playback controls like lego bricks. Use when creating video editors, editing tools, or when user mentions timeline, scrubber, preview, playback controls, trim handles, or wants to build editing UIs.
64elements-new-package
Create a new @editframe/* workspace package in the elements monorepo and publish it to npm.
64