Frontmatter
| id | 9314 |
| title | SharedCanvas: Refactor ResizeObserver Architecture & Event Routing |
| state | Closed |
| labels | aiarchitecture |
| assignees | tobiu |
| createdAt | Feb 26, 2026, 8:31 PM |
| updatedAt | Feb 26, 2026, 9:34 PM |
| githubUrl | https://github.com/neomjs/neo/issues/9314 |
| author | tobiu |
| commentsCount | 1 |
| parentIssue | null |
| subIssues | [] |
| subIssuesCompleted | 0 |
| subIssuesTotal | 0 |
| blockedBy | [] |
| blocking | [] |
| closedAt | Feb 26, 2026, 9:34 PM |
SharedCanvas: Refactor ResizeObserver Architecture & Event Routing
tobiu assigned to @tobiu on Feb 26, 2026, 8:31 PM

tobiu
Feb 26, 2026, 9:34 PM
Input from Gemini 3.1 Pro:
✦ ✦ I have pushed the architectural refactoring to
dev.This implementation achieves the ticket's goals without relying on monkey patches:
- Removed the
listeners: { resize: 'onResize' }anti-pattern fromSharedCanvas.- Replaced the hardcoded
getCanvasId()observation incomponent/Canvaswith a newgetMonitorTargetId()template method, defaulting tothis.vdom.id(honoring wrapper nodes natively).- Updated
manager/DomEvent.mjsto validateresizeevents against the listener's explicitdelegateID instead ofcomponent.id, securely separating the component receiver from the observed target node.- Added comprehensive JSDoc to
manager/DomEvent.mjsexplaining the intentional architectural limitation ofresizedelegation (IDs only, no CSS selectors) to prevent performance thrashing on permutable DOM structures.- Cleaned up
app/header/Canvas.mjsby removing redundantorientationchangeand manual parentresizelisteners, as the base class now handles parent observation natively via thegetMonitorTargetIdoverride.The foundation for cross-worker resize routing is now clean and extensible. I will mark this ticket as completed.
tobiu closed this issue on Feb 26, 2026, 9:34 PM
Context
During work on the Header Canvas (#9313), we discovered a fundamental architectural flaw in how
Neo.app.SharedCanvashandlesResizeObserverevents. The symptom was thatonResize()never fired when the browser window resized, breaking the synchronization between the DOM and the Shared Worker canvas renderer.The Flaw
src/component/Canvas.mjsusesmonitorSize_: trueto automatically add aresizeDOM listener tome.getCanvasId(). When it resizes, it callsonDomResize(data), which simply fires a Neo component event:this.fire('resize', data).src/app/SharedCanvas.mjs(which extendscomponent/Canvas) maps this Neo component event vialisteners: { resize: 'onResize' }.This is structurally flawed: Listening to a component event that your own base class fires is an anti-pattern. Events are intended for communication between unrelated instances (e.g., a Viewport listening to a Button's click). A subclass should simply override the method (
onDomResize) and callsuper.onDomResize(), rather than registering an event listener against itself.The Second Breaking Point: If the canvas is absolutely positioned (like the
.app-header-canvaswithwidth: 100%), the browser's nativeResizeObserveroften does not trigger on the canvas itself when the parent container (the Toolbar) resizes. Because the element being observed (me.id) doesn't trigger,manager.DomEventnever routes aresizeevent back to the component, and the whole chain is dead.The Patch (Current State)
We temporarily patched this in
src/app/header/Canvas.mjsby adding agetObserverId()method that returnsthis.parentIdinstead ofthis.id. However, this still requires manually addingparent.addDomListeners({ resize: me.onToolbarResize }), bypassing theSharedCanvasdefault setup entirely.The Goal
This ticket is to refactor the base
SharedCanvasandcomponent/Canvasto have a unified, clean architecture for resize tracking:listeners: { resize: 'onResize' }anti-pattern fromSharedCanvas. It should instead overrideonDomResize.monitorSizelogic works reliably without manualaddDomListenersworkarounds in subclasses.