docs: document event bus and wire app consumers
- Update CHANGELOG.md with event bus, test restructuring, and lint hardening - Document EditorEvents table and conventions in AGENTS.md - Wire collab graph sync to editor event bus instead of SceneGraph.onNodeEvents - Wire collab awareness zoom to viewport:changed instead of Vue watcher - Wire collab selection broadcasting to selection:changed instead of Vue watcher
This commit is contained in:
parent
1835903e56
commit
7dce333200
27
AGENTS.md
27
AGENTS.md
|
|
@ -49,8 +49,8 @@ Runtime `canvaskit-wasm` import exists only in `canvaskit.ts` — all other file
|
|||
|
||||
| Module | What |
|
||||
|---|---|
|
||||
| `types.ts` | EditorState, EditorOptions, Tool, EditorToolDef, EditorContext |
|
||||
| `create.ts` | `createEditor()` assembler — wires context + all modules |
|
||||
| `types.ts` | EditorState, EditorOptions, EditorEvents, Tool, EditorToolDef, EditorContext |
|
||||
| `create.ts` | `createEditor()` assembler — wires context, event bus + all modules |
|
||||
| `viewport.ts` | screenToCanvas, applyZoom, pan, zoomToFit/100/Selection |
|
||||
| `selection.ts` | select, clearSelection, marquee, snap, hover, entered container |
|
||||
| `pages.ts` | switchPage, addPage, deletePage, renamePage |
|
||||
|
|
@ -66,6 +66,29 @@ Each module exports a factory: `createXxxActions(ctx: EditorContext) => { ... }`
|
|||
`create.ts` assembles context + all modules, spreads into a flat return object.
|
||||
`Editor` type = `ReturnType<typeof createEditor>`.
|
||||
|
||||
#### Editor event bus
|
||||
|
||||
The editor exposes a typed nanoevents emitter for lifecycle events. Defined in `EditorEvents` (`types.ts`), emitted via `emitEditorEvent()` on the context, subscribed via `editor.onEditorEvent(event, handler)` which returns an unbind function.
|
||||
|
||||
| Event | Payload | Emitted by |
|
||||
|---|---|---|
|
||||
| `render:requested` | `{ renderVersion, sceneVersion }` | `requestRender()` |
|
||||
| `repaint:requested` | `{ renderVersion, sceneVersion }` | `requestRepaint()` |
|
||||
| `graph:replaced` | `SceneGraph` | `replaceGraph()` |
|
||||
| `node:created` | `SceneNode` | SceneGraph emitter → `graph-events.ts` |
|
||||
| `node:updated` | `id, changes` | SceneGraph emitter → `graph-events.ts` |
|
||||
| `node:deleted` | `id` | SceneGraph emitter → `graph-events.ts` |
|
||||
| `node:reparented` | `nodeId, oldParentId, newParentId` | SceneGraph emitter → `graph-events.ts` |
|
||||
| `node:reordered` | `nodeId, parentId, index` | SceneGraph emitter → `graph-events.ts` |
|
||||
| `selection:changed` | `selectedIds[], previousIds[]` | `setSelectedIds()` |
|
||||
| `tool:changed` | `tool, previousTool` | `setActiveTool()` |
|
||||
| `page:changed` | `pageId, previousPageId` | `switchPage()`, `replaceGraph()` |
|
||||
| `viewport:changed` | `{ panX, panY, zoom }, previous` | viewport actions |
|
||||
|
||||
All selection mutations in core use `ctx.setSelectedIds()` and all tool changes use `ctx.setActiveTool()` so the event bus fires consistently. App-layer code uses `editor.clearSelection()`, `editor.select()`, or `editor.setTool()` — never direct `state.selectedIds =` or `state.activeTool =` assignments.
|
||||
|
||||
Vue SDK provides `useEditorEvent(event, handler)` composable (`packages/vue/src/editor/events/use.ts`) that auto-disposes on scope cleanup.
|
||||
|
||||
The app editor session (`src/app/editor/session/create.ts`) is a thin Vue wrapper: creates `shallowReactive` state, calls `createEditor()`, and assembles app-specific modules for document I/O, autosave, export, vector edit, pen resume, flashes, profiler, and mobile clipboard. Tabs live in `src/app/tabs/`; active editor access lives in `src/app/editor/active-store/`.
|
||||
|
||||
## Commands
|
||||
|
|
|
|||
|
|
@ -2,8 +2,16 @@
|
|||
|
||||
## Unreleased
|
||||
|
||||
### Added
|
||||
|
||||
- Add editor event bus with typed lifecycle events — `selection:changed`, `tool:changed`, `page:changed`, `viewport:changed`, `graph:replaced`, `render:requested`, `repaint:requested`, and all scene graph node events. Subscribe via `editor.onEditorEvent()` in core or `useEditorEvent()` composable in the Vue SDK.
|
||||
|
||||
### Changed
|
||||
|
||||
- Route all selection and tool mutations through `setSelectedIds()` / `setActiveTool()` in the editor core, replacing direct `state.selectedIds` / `state.activeTool` assignments. This ensures event bus subscribers receive consistent change notifications.
|
||||
- Split large test files into focused domain folders and remove all `max-lines`, `complexity`, and `no-empty-function` test lint exceptions.
|
||||
- Enforce `no-sibling-domain-prefixed-files` lint rule project-wide — files with prefixes matching an existing sibling folder must be moved into that folder.
|
||||
|
||||
- Refactor the editor architecture across core, app, Vue SDK, CLI, MCP, docs, and desktop into smaller domain modules with structural lint rules to keep package boundaries explicit.
|
||||
- Add targeted core subpath exports and package-local import aliases for cleaner app, Vue SDK, CLI, and MCP imports.
|
||||
- Split the canvas into separate scene and overlay render layers so rulers, labels, selections, and input overlays are isolated from scene rendering.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import type { Room } from 'trystero'
|
||||
import { watch } from 'vue'
|
||||
import type { Ref } from 'vue'
|
||||
import { IndexeddbPersistence } from 'y-indexeddb'
|
||||
import * as awarenessProtocol from 'y-protocols/awareness'
|
||||
|
|
@ -136,19 +135,16 @@ export function createCollabConnectionActions({
|
|||
}
|
||||
|
||||
export function watchAwarenessZoom(store: EditorStore, getAwareness: () => Awareness | null) {
|
||||
return watch(
|
||||
() => store.state.zoom,
|
||||
(zoom) => {
|
||||
const awareness = getAwareness()
|
||||
if (!awareness) return
|
||||
const prev = awareness.getLocalState()?.cursor as
|
||||
| { x: number; y: number; pageId: string; zoom: number }
|
||||
| undefined
|
||||
if (prev) {
|
||||
awareness.setLocalStateField('cursor', { ...prev, zoom })
|
||||
}
|
||||
return store.onEditorEvent('viewport:changed', (viewport) => {
|
||||
const awareness = getAwareness()
|
||||
if (!awareness) return
|
||||
const prev = awareness.getLocalState()?.cursor as
|
||||
| { x: number; y: number; pageId: string; zoom: number }
|
||||
| undefined
|
||||
if (prev) {
|
||||
awareness.setLocalStateField('cursor', { ...prev, zoom: viewport.zoom })
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export function connectCollabSession({
|
||||
|
|
|
|||
|
|
@ -76,12 +76,12 @@ export function bindCollabGraphEvents({
|
|||
}
|
||||
}
|
||||
|
||||
return store.graph.onNodeEvents({
|
||||
updated: (id) => onGraphMutation(id),
|
||||
created: (node) => onGraphMutation(node.id),
|
||||
reparented: (nodeId) => onGraphMutation(nodeId),
|
||||
reordered: (nodeId) => onGraphMutation(nodeId),
|
||||
deleted: (id) => {
|
||||
const unbinds = [
|
||||
store.onEditorEvent('node:updated', (id) => onGraphMutation(id)),
|
||||
store.onEditorEvent('node:created', (node) => onGraphMutation(node.id)),
|
||||
store.onEditorEvent('node:reparented', (nodeId) => onGraphMutation(nodeId)),
|
||||
store.onEditorEvent('node:reordered', (nodeId) => onGraphMutation(nodeId)),
|
||||
store.onEditorEvent('node:deleted', (id) => {
|
||||
const ydoc = getYdoc()
|
||||
const ynodes = getYnodes()
|
||||
if (!getSuppressGraphSync() && ydoc && ynodes) {
|
||||
|
|
@ -91,8 +91,11 @@ export function bindCollabGraphEvents({
|
|||
})
|
||||
setSuppressYjsEvents(false)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
]
|
||||
return () => {
|
||||
for (const unbind of unbinds) unbind()
|
||||
}
|
||||
}
|
||||
|
||||
export function registerYjsObservers({
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import { watch } from 'vue'
|
||||
|
||||
import type { useCollabInjected } from '@/app/collab/use'
|
||||
import type { EditorStore } from '@/app/editor/active-store'
|
||||
|
||||
|
|
@ -12,10 +10,7 @@ export function useCanvasCollaborationAwareness(store: EditorStore, collab: Coll
|
|||
collab?.updateCursor(cx, cy, store.state.currentPageId)
|
||||
}
|
||||
|
||||
watch(
|
||||
() => [...store.state.selectedIds],
|
||||
(ids) => collab?.updateSelection(ids)
|
||||
)
|
||||
store.onEditorEvent('selection:changed', (ids) => collab?.updateSelection(ids))
|
||||
|
||||
return { updateCursor }
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue