I've implemented exactly this at $WORK. We literally just set a global variable, "should render next frame", from input handlers and other important events. It's not pretty but it gets the job done. There are occasional bugs where someone forgets to set the render flag after some significant event. But the bug goes away as soon as the mouse is moved, so it's usually not a big deal. If we wanted we could also schedule a "render once per second" to bound the amount of time that visual issues would persist for.
I'm not sure how this translates to ECS architecture - probably depends on your library, but if you're in charge of the main loop it shouldn't be a problem.
At one company we had a deployment script written in English. That support from India would execute. Except they would occasionally inject their undocumented codes to fix their own issues.
That and the component oriented distributed monolith that ran on single machine. It was monolith architecture but it was made to run on multiple machines. However it was never distributed and starting the components was an arcane science.
One trick I found is to gate mutable access to state behind a "transaction object" which sets a flag for each object mutated, then once the transaction object's destructor is called, recomputes all derived state based on which flags were set. Using a destructor might not be the best idea since I ended up not marking it noexcept, it may be better to add an explicit "recompute" function called at the end, but this breaks the convenience of `state().cursor_mut() = new_cursor;` automatically recomputing derived state once the state() object is destroyed.
I'm not sure how this translates to ECS architecture - probably depends on your library, but if you're in charge of the main loop it shouldn't be a problem.