def affect(self, target: 'Client') -> None:
         target.log(msg='# connected to server', chat=CHAT_GLOB)
         target.send(IrcMessage(verb='CAP', params=('LS', '302')))
-        target.send(IrcMessage(verb='CAP', params=('LIST',)))
-        target.send(IrcMessage(verb='CAP', params=('END',)))
         target.send(IrcMessage(verb='USER', params=(getuser(), '0', '*',
                                                     target.realname)))
         target.send(IrcMessage(verb='NICK', params=(target.nickname,)))
         self._hostname = hostname
         self._socket: Optional[socket] = None
         self._recv_loop: Optional[Loop] = None
-        self._cap_ls_done = False
-        self._caps: dict[str, Optional[bool]] = {}
+        self.cap_neg_state: set[str] = set()
+        self.caps: dict[str, tuple[bool, str]] = {}
         self.id_ = uuid4()
         self.assumed_open = False
         self.realname = realname
         Thread(target=connect, daemon=True, args=(self,)).start()
 
     def collect_caps(self, params: tuple[str, ...]) -> None:
-        'Record and list available or enabled server capabilities.'
-        collect_enabled = params[0] != 'LS'
+        'Record available and enabled server capabilities.'
+        verb = params[0]
+        items = params[-1].strip().split()
         is_final_line = params[1] != '*'
-        listed_names = params[-1].strip().split()
-        if collect_enabled:
-            for k in [k for k, v in self._caps.items() if v is None]:
-                self._caps[k] = False
-            for name in [name for name in self._caps
-                         if name.split('=')[0] in listed_names]:
-                self._caps[name] = True
-        else:
-            if self._cap_ls_done:
-                self._caps.clear()
-            for cap_name in listed_names:
-                self._caps[cap_name] = None
-            self._cap_ls_done = is_final_line
-        if is_final_line and collect_enabled:
-            self.log('# server capabilities available (* if enabled): '
-                     + ', '.join([(f'(*){k}' if v else k)
-                                  for k, v in self._caps.items()]))
+        doneness = f'{verb} done'
+        waiting = f'{verb} wait'
+        if doneness in self.cap_neg_state:
+            if verb == 'LS':
+                self.caps.clear()
+            else:
+                for name in self.caps:
+                    self.caps[name] = (False, self.caps[name][1])
+            self.cap_neg_state.remove(doneness)
+        if waiting not in self.cap_neg_state:
+            self.cap_neg_state.add(waiting)
+        for item in items:
+            if verb == 'LS':
+                splitted = item.split('=', maxsplit=1)
+                self.caps[splitted[0]] = (False, ''.join(splitted[1:]))
+            else:
+                self.caps[item] = (True, self.caps[item][1])
+        if is_final_line:
+            self.cap_neg_state.remove(waiting)
+            self.cap_neg_state.add(doneness)
 
     @abstractmethod
     def log(self, msg: str, chat: str = '') -> None:
         elif msg.verb == 'CAP':
             if msg.params[1] in {'LS', 'LIST'}:
                 target.collect_caps(msg.params[1:])
+            if ('LIST done' in target.cap_neg_state
+                    and 'listed' not in target.cap_neg_state):
+                target.send(IrcMessage(verb='CAP', params=('END',)))
+                target.log('# available server capabilities (enabled: "+"):')
+                for cap_name, config in target.caps.items():
+                    target.log(f'# {"+" if config[0] else "-"} {cap_name}'
+                               + (f' ({config[1]})' if config[1] else ''))
+                target.cap_neg_state.add('listed')
+            elif ('LS done' in target.cap_neg_state
+                    and 'LIST wait' not in target.cap_neg_state):
+                target.send(IrcMessage(verb='CAP', params=('LIST',)))