agent-panel
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseAgent Panel
Agent面板
The captures 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.
EFAgentPanelef-editEFAgentPanelef-editEvent 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 buttonEFWorkbenchaddEventListener('ef-edit', ...)ef-editGUI交互(画布拖拽、检查器变更、修剪拖拽、循环切换)
└─ component.dispatchEvent(createEditCustomEvent(editEvent))
└─ [bubbles: true, composed: true]
└─ EFWorkbench 监听器 → agentPanel.addEdit(event)
└─ Map<editChangeKey, EditEvent> 注册表(去重)
└─ groupEditsBySelector() → buildAgentPrompt() → 复制按钮EFWorkbenchaddEventListener('ef-edit', ...)ef-editKey Types (editEvents.ts
)
editEvents.ts关键类型(editEvents.ts
)
editEvents.tsts
// 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::resizeselector::rotate
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 }注册表键格式 —— 用于判断去重规则:
- 属性变更:
selector::prop:propName - 移动/缩放/旋转:,
selector::move,selector::resizeselector::rotate
Pattern: Adding a New ef-edit Dispatch Point
模式:添加新的ef-edit触发点
ts
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)EFInspector.tsts
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(),
}));EFInspector.tsbuildPropertyEditEvent(element, property, oldValue, newValue)Finding Current Dispatch Points
查找当前触发点
bash
undefinedbash
undefinedEvery call site that produces an ef-edit event
Every call site that produces an ef-edit event
rg 'createEditCustomEvent' elements/packages/elements/src/
rg 'createEditCustomEvent' elements/packages/elements/src/
Track subclasses that override render() — each must bind @trim-change-end
Track subclasses that override render() — each must bind @trim-change-end
rg 'override render' elements/packages/elements/src/gui/timeline/tracks/
rg 'override render' elements/packages/elements/src/gui/timeline/tracks/
Registered element schemas
Registered element schemas
rg 'SCHEMA_REGISTRY' elements/packages/elements/src/gui/elementPropertySchema.ts
undefinedrg 'SCHEMA_REGISTRY' elements/packages/elements/src/gui/elementPropertySchema.ts
undefinedWhat is NOT Wired (intentional)
未接入的功能(有意设计)
Playback controls (play/pause/seek), pan-zoom/zoom level, and toolbar settings are display-only state — they should not produce events. Hierarchy reorder ( from ) would require a new type; not yet implemented.
ef-edithierarchy-reorderEFHierarchyElementReorderedOperation播放控制(播放/暂停/跳转)、平移缩放/缩放级别、工具栏设置均为仅显示状态 —— 它们不应触发 事件。层级重排(来自的)需要新增类型,目前尚未实现。
ef-editEFHierarchyhierarchy-reorderElementReorderedOperationShadow DOM Event Routing Gotcha
Shadow DOM事件路由陷阱
trim-change-endcomposed: trueEFTrimHandlesTrackItem.render()@trim-change-endef-trim-handlesEFVideoTrackrender()ef-trim-handlesrender()ef-trim-handles@trim-change@trim-change-endTrackItem.render()render()trim-change-endEFTrimHandlescomposed: trueTrackItem.render()ef-trim-handles@trim-change-endEFVideoTrackrender()ef-trim-handlesrender()ef-trim-handles@trim-change@trim-change-endrender()TrackItem.render()Element Property Schema (elementPropertySchema.ts
)
elementPropertySchema.ts元素属性Schema(elementPropertySchema.ts
)
elementPropertySchema.tsStatic descriptors drive the inspector UI. Register new element types at the bottom via . Use the factory functions:
SCHEMA_REGISTRY- — time values (stored as
timeDescriptor(attr, label, opts)attributes)${ms}ms 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 for the test pattern.
elementPropertySchema.test.ts静态描述器驱动检查器UI。在文件底部通过注册新元素类型。使用以下工厂函数:
SCHEMA_REGISTRY- —— 时间值(以
timeDescriptor(attr, label, opts)属性存储)${ms}ms enumDescriptor(attr, label, options, condition?)boolDescriptor(attr, label, condition?)numberDescriptor(attr, label, opts)stringDescriptor(attr, label)
没有注册表条目的元素对检查器不可见。可查看了解测试模式。
elementPropertySchema.test.tsTesting ef-edit Events
测试ef-edit事件
Unit tests (Node/vitest): use for helpers — , , , .
editEvents.tseditChangeKeyrollUpEditsgroupEditsBySelectorbuildAgentPromptBrowser tests (Chromium/vitest): required for any test involving DOM events or shadow DOM.
For trim ef-edit, dispatch directly on the host element — do not try to simulate full pointer events through shadow DOM layers, as pointer capture mechanics are unreliable in test environments:
trim-change-endef-trim-handlests
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 level — is , so it propagates from any component to .
documentef-editbubbles: true, composed: truedocument单元测试(Node/vitest):用于测试中的辅助函数 —— , , , 。
editEvents.tseditChangeKeyrollUpEditsgroupEditsBySelectorbuildAgentPrompt浏览器测试(Chromium/vitest):涉及DOM事件或Shadow DOM的测试必须使用浏览器测试。
对于修剪操作的ef-edit测试,直接在宿主元素上触发 —— 不要尝试通过Shadow DOM层模拟完整的指针事件,因为测试环境中的指针捕获机制不可靠:
ef-trim-handlestrim-change-endts
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,
}));对于循环操作的ef-edit测试,通过点击循环按钮。
shadowRoot.querySelector("button[title='Loop']").click()在级别监听事件 —— 的属性使其可以从任何组件传播到。
documentef-editbubbles: true, composed: truedocument