Ramonda

listener-added-by-hand

This fails the run. Where it is wrong about your code, // ramonda-check-ignore <reason> on the line says so, and the reason is printed on every run.

Reported when a component adds a window or document listener by hand, where @onWindow or @onDocument would do it — or, inside if (__DEV__) where a decorator cannot be used, adds one that nothing ever removes.

@onWindow and @onDocument attach on mount and detach on unmount, and there is nothing to remember:

@onWindow("resize") onResize() { … }

A hand-rolled listener has to be removed by hand, and one that is not outlives the component that added it: the handler keeps running, reading state nobody is showing and holding the component and everything it closed over alive. Open and close the same view ten times and there are ten of them.

In a render() it is worse than it looks — a render runs whenever the framework likes, so the listener is registered again on every pass.

A listener the app ARMS — on a click, after a fetch — is the Listener hook. @onWindow attaches for the owner's whole life and cannot be turned on and off; Listener can, and the framework still removes it when the owner goes:

private escape = this.use(Listener, () => ({
  on: "document",
  type: "keydown",
  run: this.onKey,
}));

@mounted open() { this.escape.listen(); }
close() { this.escape.stop(); }

One hook instance is one listener, and teardown removes it — there is nothing to remember and no handler reference to keep in step. It is the same shape Interval and Timeout already have for timers.

Inside if (__DEV__) the hand-rolled call is right, and this asks only that it be cleaned up. A decorator is code on the class, so no guard can remove it and a dev-only listener written with @onWindow would attach in production too. Keep the raw call there and pair it with a removeEventListener for the same event in @destroyed, with the same handler reference. { once: true } and { signal } close it as well, and neither is reported.

A listener on anything else is not this rule's business: an AbortSignal dies with its request and an element with the element, and no decorator covers either.

Next