attach_render tells the output that a "new" buffer has been prepared (even if we haven't changed it). We need to call that and then commit it to keep the render loop going.
Software cursors will freeze momentarily during layout updates, but I suspect that this is not as easily fixed as it sounds. You can force software cursors by running:
WLR_NO_HARDWARE_CURSORS=1 ./dwl
XHVBTXWGXGOVHLFX57RQI54NP2OY3BYI42K4ZODZDHXVZ2VF5YTQC
LRGGX34PR2PQEBW6U465VYOGIYOPD365PB444JGR6R3H7E524CCQC
3EKVUJXI7KI4ZCILWWFJ6UNBYAHBS42TZ3PIPXOEXQ24KEFFA72QC
7L3TU7JVWPBPHN7WF4TJ263BZ6BC3AYRRW6PULFUP5JZUGWWNUSAC
7B74AT3BXYB7PVW4F6SGQNPMHOU5TEV5TZ54CG6VSQI46XSEKWXQC
J6OSBEBQXZR5JZ5TOCCUPELBPUVEQULGCXURXLPY7WFYTDEQOU2AC
2QL2H4REDZT46FI3LQ4RYEMQYZBNBK3IC3KH3XERAJU3NCZWMNYQC
PLJJLNS7E2UXW2YARGBSV6IHZEBGL5EW354IMMHPDATY5DQD77DAC
LQPHYO7IIMLXHUD5IK657BO4BE3SGT5HYDRJDU5OFDF5YUXKIRTAC
/* Begin the renderer (calls glViewport and some other GL sanity checks) */
wlr_renderer_begin(drw, m->wlr_output->width, m->wlr_output->height);
/* Hardware cursors are rendered by the GPU on a separate plane, and can be
* moved around without re-rendering what's beneath them - which is more
* efficient. However, not all hardware supports hardware cursors. For this
* reason, wlroots provides a software fallback, which we ask it to render
* here. wlr_cursor handles configuring hardware vs software cursors for you,
* and this function is a no-op when hardware cursors are in use. */
wlr_output_render_software_cursors(m->wlr_output, NULL);
/* Hardware cursors are rendered by the GPU on a separate plane, and can be
* moved around without re-rendering what's beneath them - which is more
* efficient. However, not all hardware supports hardware cursors. For this
* reason, wlroots provides a software fallback, which we ask it to render
* here. wlr_cursor handles configuring hardware vs software cursors for you,
* and this function is a no-op when hardware cursors are in use. */
wlr_output_render_software_cursors(m->wlr_output, NULL);