isupport: Dict[str]
     sasl_account: str = ''
     sasl_auth_state: str = ''
-    user_modes: str = ''
 
     def is_chan_name(self, name: str) -> bool:
         'Tests name to match CHANTYPES prefixes.'
     topic: _CompletableTopic
 
     def __init__(self,
-                 id_for_nickuserhost: Callable,
+                 userid_for_nickuserhost: Callable,
                  get_membership_prefixes: Callable,
                  purge_users: Callable,
                  **kwargs
                  ) -> None:
-        self._id_for_nickuserhost = id_for_nickuserhost
+        self._userid_for_nickuserhost = userid_for_nickuserhost
         self._get_membership_prefixes = get_membership_prefixes
         self.purge_users = purge_users
         super().__init__(**kwargs)
         'Add to .user_ids items assumed as nicknames with membership prefixes.'
         for item in items:
             nickname = item.lstrip(self._get_membership_prefixes())
-            self.user_ids.append(
-                    self._id_for_nickuserhost(_NickUserHost(nickname),
-                                              create_if_none=True))
-
-    def append_nick(self, nickuserhost: '_NickUserHost') -> None:
-        'To .user_ids append .nickname and declare .user_ids complete.'
-        user_id = self._id_for_nickuserhost(nickuserhost, create_if_none=True,
-                                            updating=True)
+            self.user_ids.append(self._userid_for_nickuserhost(
+                NickUserHost(nickname), create_if_none=True))
+
+    def append_user(self, user: '_User') -> None:
+        'To .user_ids append user.nickname and declare .user_ids complete.'
+        user_id = self._userid_for_nickuserhost(user, create_if_none=True,
+                                                updating=True)
         self.user_ids.append(user_id, complete=True)
 
-    def remove_nick(self, nickuserhost: '_NickUserHost') -> None:
+    def remove_user(self, user: '_User') -> None:
         'From .user_ids remove .nickname and declare .user_ids complete.'
-        user_id = self._id_for_nickuserhost(nickuserhost)
+        user_id = self._userid_for_nickuserhost(user)
         self.user_ids.remove(user_id, complete=True)
         self.purge_users()
 
         return name + str(0 if not digits else (int(digits) + 1))
 
 
+class _User(NickUserHost):
+    modes: str = '?'
+
+    @classmethod
+    def from_nickuserhost(cls, nuh: NickUserHost) -> Self:
+        'Create with nuh fields set.'
+        return cls(nuh.nick, nuh.user, nuh.host)
+
+
 class _UpdatingServerCapability(_UpdatingMixin, ServerCapability):
     pass
 
     topic: _UpdatingCompletableTopic
 
 
-class _UpdatingNickUserHost(_UpdatingMixin, _NickUserHost):
+class _UpdatingUser(_UpdatingMixin, _User):
     pass
 
 
     channels: _UpdatingDict[_UpdatingChannel]
     isupport: _UpdatingDict[str]
     motd: _UpdatingCompletableStringsList
-    users: _UpdatingDict[_UpdatingNickUserHost]
+    users: _UpdatingDict[_UpdatingUser]
 
     def __getattribute__(self, key: str):
         attr = super().__getattribute__(key)
             attr._defaults = ISUPPORT_DEFAULTS
         elif key == 'channels' and not attr._create_if_none:
             attr._create_if_none = {
-                    'id_for_nickuserhost': self.userid_for_nickuserhost,
+                    'userid_for_nickuserhost': self.userid_for_nickuserhost,
                     'get_membership_prefixes': self._get_membership_prefixes,
                     'purge_users': self.purge_users}
         return attr
         assert toks[0][0] == '('
         return toks[1]
 
-    def chans_of_user(self,
-                      nickuserhost: _NickUserHost
-                      ) -> dict[str, _UpdatingChannel]:
+    def chans_of_user(self, user: _User) -> dict[str, _UpdatingChannel]:
         'Return dictionary of channels user is in.'
-        id_ = self.userid_for_nickuserhost(nickuserhost)
+        id_ = self.userid_for_nickuserhost(user)
         return {k: self.channels[k] for k in self.channels.keys()
                 if id_ in self.channels[k].user_ids.completed}
 
     def userid_for_nickuserhost(self,
-                                nickuserhost: _NickUserHost,
+                                nickuserhost: NickUserHost,
                                 create_if_none=False,
                                 allow_none=False,
                                 updating=False
                 assert stored.user != '?'
         elif create_if_none:
             id_ = str(uuid4())
-            self.users.set_updating(id_, nickuserhost)
+            self.users.set_updating(id_, _User.from_nickuserhost(nickuserhost))
         else:
             return None
         return id_
 
     def _on_connect(self) -> None:
         assert self.conn is not None
-        self.db.users.set_updating('me', _NickUserHost('?', getuser(), '?'))
+        self.db.users.set_updating('me', _User('?', getuser(), '?'))
         self.db.connection_state = 'connected'
         self.caps.start_negotation()
         self.send(IrcMessage(verb='USER', params=(
         if '_verb' not in ret:
             self._log(f'PLEASE IMPLEMENT HANDLER FOR: {msg.raw}')
             return
-        for n_u_h in ret['_nickuserhosts']:  # update, ensure identities
+        for n_u_h in ret['_nickuserhosts']:  # update, turn into proper users
             if (id_ := self.db.userid_for_nickuserhost(n_u_h, allow_none=True,
                                                        updating=True)):
                 for ret_name in [k for k in ret if ret[k] is n_u_h]:
                 else:
                     self.caps.end_negotiation()
         elif ret['_verb'] == 'JOIN' and ret['joiner'] != self.db.users['me']:
-            self.db.channels[ret['channel']].append_nick(ret['joiner'])
+            self.db.channels[ret['channel']].append_user(ret['joiner'])
         elif ret['_verb'] == 'NICK':
             user_id = self.db.userid_for_nickuserhost(ret['named'],
                                                       updating=True)
                                   else ret['channel'])}
             self._log(ret['message'], out=False, **kw)
         elif ret['_verb'] == 'PART':
-            self.db.channels[ret['channel']].remove_nick(ret['parter'])
+            self.db.channels[ret['channel']].remove_user(ret['parter'])
             if 'message' in ret:
                 self._log(f'{ret["parter"]} parts: {ret["message"]}',
                           LogScope.CHAT, target=ret['channel'])
             self.send(IrcMessage(verb='PONG', params=(ret['reply'],)))
         elif ret['_verb'] == 'QUIT':
             for ch_name, ch in self.db.chans_of_user(ret['quitter']).items():
-                ch.remove_nick(ret['quitter'])
+                ch.remove_user(ret['quitter'])
                 self._log(f'{ret["quitter"]} quits: {ret["message"]}',
                           LogScope.CHAT, target=ch_name)