class _Playbook:
     put_keypress: Optional[Callable] = None
     assert_screen_line: Optional[Callable] = None
+    redraw_affected: Optional[Callable] = None
 
     def __init__(self, path: Path, get_client: Callable, verbose: bool
                  ) -> None:
                 client.conn.put_server_msg(msg)
             elif context == _TOK_TUI:
                 assert self.assert_screen_line is not None
+                assert self.redraw_affected is not None
+                self.redraw_affected()
                 position, attrs_str, msg = msg.split('.', maxsplit=2)
                 y, x = ((int(item) for item in position.split(','))
                         if ',' in position
         super().__init__(**kwargs)
         assert isinstance(self._term, TestTerminal)
         self._playbook.assert_screen_line = self._term.assert_screen_line
+        self._playbook.redraw_affected = self.redraw_affected
         self._playbook.put_keypress = self._term._q_keypresses.put
         self._playbook.ensure_has_started()  # if .__init__ didn't yet by log()
 
 
     def log(self, msg: str, **kwargs) -> tuple[tuple[int, ...], str]:
         win_ids, logged_msg = super().log(msg, **kwargs)
-        # usually whatever called log would call .redraw_affected directly
-        # after, but for TUI display tests on the effects of .log that might
-        # follow directly in the playbook (i.e. will run before .log actually
-        # finishes, signaling to caller they should call .redraw_affected) we
-        # want this available immediately
-        self.redraw_affected()
         fmt, time_str, msg_sans_time = logged_msg.split(' ', maxsplit=2)
         msg_sans_time = fmt + ' ' + msg_sans_time
         assert len(time_str) == 8