Subject: 30.0.50; ERC 5.6: Retrieve buffer targets sans subscription status
From: "J.P." <[email-masked]>
To: [email-masked]
Date: Mon, 16 Oct 2023 07:18:35 -0700
List-Id: <debbugs-submit.debbugs.gnu.org>

Tags: patch

The go-to public function for determining a buffer's target has always
been 'erc-default-target'. However, it doesn't always behave in the most
intuitive manner for those unfamiliar with ERC's inner workings. This by
itself is not a deal breaker, but things get more complicated when you
consider the function's handful of "consumers" in ERC's public API.
Take, for example, the function `erc-server-buffer-p'. When called in a
PARTed channel, it returns t, just like it has since at least ERC 5.3.
What about a more involved dependent function, like `erc-get-buffer'?
Its result is pretty straightforward to predict so long as you ignore
the same glaring "joinedness" blind spot.

I'm proposing we offer a new public function, `erc-target', that simply
returns non-nil in any query or channel buffer, regardless of whether
the client has left or been kicked, and regardless of network
connectivity (logical or otherwise). I'm also proposing that we slowly
replace uses of `erc-default-target' in the code base wherever we can
comfortably demonstrate that doing so is unlikely to break anything. (We
should probably leave a note behind with each replacement mentioning the
old behavior.) Attached is an initial sketch of this idea. Please try it
if you dare.

In GNU Emacs 30.0.50 (build 1, x86_64-pc-linux-gnu, GTK+ Version
 3.24.38, cairo version 1.17.6) of 2023-10-16 built on localhost
Repository revision: c3038bf5e1d79c3dfa83717a5a61ecc86116f04a
Repository branch: master
Windowing system distributor 'The X.Org Foundation', version 11.0.12014000
System Description: Fedora Linux 37 (Workstation Edition)

Configured using:
 'configure --enable-check-lisp-object-type --enable-checking=yes,glyphs
 'CFLAGS=-O0 -g3'
 PKG_CONFIG_PATH=:/usr/lib64/pkgconfig:/usr/share/pkgconfig'

Configured features:
ACL CAIRO DBUS FREETYPE GIF GLIB GMP GNUTLS GPM GSETTINGS HARFBUZZ JPEG
JSON LCMS2 LIBOTF LIBSELINUX LIBSYSTEMD LIBXML2 M17N_FLT MODULES NOTIFY
INOTIFY PDUMPER PNG RSVG SECCOMP SOUND SQLITE3 THREADS TIFF
TOOLKIT_SCROLL_BARS WEBP X11 XDBE XIM XINPUT2 XPM GTK3 ZLIB

Important settings:
  value of $LANG: en_US.UTF-8
  value of $XMODIFIERS: @im=ibus
  locale-coding-system: utf-8-unix

Major mode: Lisp Interaction

Minor modes in effect:
  tooltip-mode: t
  global-eldoc-mode: t
  eldoc-mode: t
  show-paren-mode: t
  electric-indent-mode: t
  mouse-wheel-mode: t
  tool-bar-mode: t
  menu-bar-mode: t
  file-name-shadow-mode: t
  global-font-lock-mode: t
  font-lock-mode: t
  blink-cursor-mode: t
  minibuffer-regexp-mode: t
  line-number-mode: t
  indent-tabs-mode: t
  transient-mark-mode: t
  auto-composition-mode: t
  auto-encryption-mode: t
  auto-compression-mode: t

Load-path shadows:
None found.

Features:
(shadow sort mail-extr emacsbug message mailcap yank-media puny dired
dired-loaddefs rfc822 mml mml-sec epa derived epg rfc6068 epg-config
gnus-util text-property-search time-date mm-decode mm-bodies mm-encode
mail-parse rfc2231 mailabbrev gmm-utils mailheader sendmail rfc2047
rfc2045 ietf-drums mm-util mail-prsvr mail-utils erc auth-source cl-seq
eieio eieio-core cl-macs password-cache json subr-x map format-spec
cl-loaddefs cl-lib erc-backend erc-networks byte-opt gv bytecomp
byte-compile erc-common erc-compat erc-loaddefs rmc iso-transl tooltip
cconv eldoc paren electric uniquify ediff-hook vc-hooks lisp-float-type
elisp-mode mwheel term/x-win x-win term/common-win x-dnd touch-screen
tool-bar dnd fontset image regexp-opt fringe tabulated-list replace
newcomment text-mode lisp-mode prog-mode register page tab-bar menu-bar
rfn-eshadow isearch easymenu timer select scroll-bar mouse jit-lock
font-lock syntax font-core term/tty-colors frame minibuffer nadvice seq
simple cl-generic indonesian philippine cham georgian utf-8-lang
misc-lang vietnamese tibetan thai tai-viet lao korean japanese eucjp-ms
cp51932 hebrew greek romanian slovak czech european ethiopic indian
cyrillic chinese composite emoji-zwj charscript charprop case-table
epa-hook jka-cmpr-hook help abbrev obarray oclosure cl-preloaded button
loaddefs theme-loaddefs faces cus-face macroexp files window
text-properties overlay sha1 md5 base64 format env code-pages mule
custom widget keymap hashtable-print-readable backquote threads dbusbind
inotify lcms2 dynamic-setting system-font-setting font-render-setting
cairo gtk x-toolkit xinput2 x multi-tty move-toolbar
make-network-process emacs)

Memory information:
((conses 16 66550 9194) (symbols 48 8704 0) (strings 32 23573 2312)
 (string-bytes 1 687630) (vectors 16 16105)
 (vector-slots 8 216057 14988) (floats 8 24 33) (intervals 56 239 0)
 (buffers 984 11))
From 7784c02425d9353294eccff75688326f251ef1dd Mon Sep 17 00:00:00 2001
From: "F. Jason Park" <[email-masked]>
Date: Sun, 15 Oct 2023 07:22:31 -0700
Subject: [PATCH 1/2] [5.6] Rename erc-server-buffer-p

* lisp/erc/erc-log.el (erc-log-all-but-server-buffers): Use
`erc--server-buffer-p' instead of `erc-server-buffer-p'.  This
replacement is presumed to be relatively "safe" because this function
is unused in the code base and only appears in the doc string for the
option `erc-enable-logging'.
* lisp/erc/erc-match.el (erc-match-message): Leave comment proposing
that `erc--server-buffer-p' should be preferred to
`erc-server-buffer-p'.  Use preferred alias for `erc-server-buffer-p'.
* lisp/erc/erc-notify.el (erc-cmd-NOTIFY): Use preferred alias for
`erc-server-buffer-p' and leave FIXME comment.
* lisp/erc/erc-speedbar.el (erc-speedbar-buttons): Use
`erc--server-buffer-p' instead of `erc-server-buffer-p'.  The logic
here seems simple enough to justify a change, however the absence of
related bug reports is perhaps an argument against this.
* lisp/erc/erc-track.el (erc-track-modified-channels): Use preferred
alias for `erc-server-buffer-p'.
* lisp/erc/erc.el (erc-once-with-server-event): Use
`erc--server-buffer-p' instead of `erc-server-buffer-p'.  This change
seems justified because the function sets local hooks that would
otherwise be ignored outside of a server buffer.
(erc-server-buffer-p, erc-server-or-unjoined-channel-buffer-p): Make
former obsolete alias for latter.
(erc--server-buffer-p): New function to replace `erc-server-buffer-p'
internally in new code.  It returns nil in parted and kicked channels.
(erc-open-server-buffer-p): Use `erc--server-buffer-p' instead of
`erc-server-buffer-p'.
(erc-get-buffer): Mention behavior in doc string regarding parted and
kicked-from channels.
(erc-cmd-GQUIT): Fix wrong-number-of-arguments bug in timer function.
(erc-default-target): Mention behavior regarding unjoined channels.
(erc-kill-query-buffers): Don't use `erc-server-buffer-p'.  This
replacement may break third-party code expecting to leave parted
channels behind, but it seems sane when considering only the lone
internal use in `erc-cmd-QUIT'.

; * test/lisp/erc/resources/join/network-id/foonet.eld: Timeouts.
---
 lisp/erc/erc-log.el                           |  4 +-
 lisp/erc/erc-match.el                         |  4 +-
 lisp/erc/erc-notify.el                        |  4 +-
 lisp/erc/erc-speedbar.el                      |  2 +-
 lisp/erc/erc-track.el                         |  4 +-
 lisp/erc/erc.el                               | 44 ++++++++++++-------
 .../erc/resources/join/network-id/foonet.eld  |  4 +-
 7 files changed, 42 insertions(+), 24 deletions(-)

diff --git a/lisp/erc/erc-log.el b/lisp/erc/erc-log.el
index 472cc1388a4..79fece5779e 100644
--- a/lisp/erc/erc-log.el
+++ b/lisp/erc/erc-log.el
@@ -276,11 +276,11 @@ erc-log-disable-logging
 
 (defun erc-log-all-but-server-buffers (buffer)
   "Return t if logging should be enabled in BUFFER.
-Returns nil if `erc-server-buffer-p' returns t."
+Return nil if BUFFER is a server buffer."
   (save-excursion
     (save-window-excursion
       (set-buffer buffer)
-      (not (erc-server-buffer-p)))))
+      (not (erc--server-buffer-p)))))
 
 (defun erc-save-query-buffers (process)
   "Save all buffers of the given PROCESS."
diff --git a/lisp/erc/erc-match.el b/lisp/erc/erc-match.el
index 186717579d7..8644e61106f 100644
--- a/lisp/erc/erc-match.el
+++ b/lisp/erc/erc-match.el
@@ -491,7 +491,9 @@ erc-match-message
          (message (buffer-substring message-beg (point-max))))
     (when (and vector
 	       (not (and erc-match-exclude-server-buffer
-			 (erc-server-buffer-p))))
+                         ;; FIXME replace with `erc--server-buffer-p'
+                         ;; or explain why that's unwise.
+                         (erc-server-or-unjoined-channel-buffer-p))))
       (mapc
        (lambda (match-type)
 	 (goto-char (point-min))
diff --git a/lisp/erc/erc-notify.el b/lisp/erc/erc-notify.el
index 55be8976ada..cf7ffbb40d7 100644
--- a/lisp/erc/erc-notify.el
+++ b/lisp/erc/erc-notify.el
@@ -218,7 +218,9 @@ erc-cmd-NOTIFY
 	    ;; from your notify list.
 	    (dolist (buf (erc-buffer-list))
 	      (with-current-buffer buf
-		(if (erc-server-buffer-p)
+                ;; FIXME replace with `erc--server-buffer-p' or
+                ;; explain why that's unwise.
+                (if (erc-server-or-unjoined-channel-buffer-p)
 		    (setq erc-last-ison (delete (car args) erc-last-ison))))))
 	(setq erc-notify-list (cons (erc-string-no-properties (car args))
 				    erc-notify-list)))
diff --git a/lisp/erc/erc-speedbar.el b/lisp/erc/erc-speedbar.el
index 625d59530b0..bb5fad6f52f 100644
--- a/lisp/erc/erc-speedbar.el
+++ b/lisp/erc/erc-speedbar.el
@@ -135,7 +135,7 @@ erc-speedbar-buttons
   (erase-buffer)
   (let (serverp chanp queryp)
     (with-current-buffer buffer
-      (setq serverp (erc-server-buffer-p))
+      (setq serverp (erc--server-buffer-p))
       (setq chanp (erc-channel-p (erc-default-target)))
       (setq queryp (erc-query-buffer-p)))
     (cond (serverp
diff --git a/lisp/erc/erc-track.el b/lisp/erc/erc-track.el
index 64e59a90047..c8f2e04c3eb 100644
--- a/lisp/erc/erc-track.el
+++ b/lisp/erc/erc-track.el
@@ -795,7 +795,9 @@ erc-track-modified-channels
     (if (and (not (erc-buffer-visible (current-buffer)))
 	     (not (member this-channel erc-track-exclude))
 	     (not (and erc-track-exclude-server-buffer
-		       (erc-server-buffer-p)))
+                       ;; FIXME either use `erc--server-buffer-p' or
+                       ;; explain why that's unwise.
+                       (erc-server-or-unjoined-channel-buffer-p)))
 	     (not (erc-message-type-member
 		   (or (erc-find-parsed-property)
 		       (point-min))
diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el
index 5bf6496e926..7144e81397a 100644
--- a/lisp/erc/erc.el
+++ b/lisp/erc/erc.el
@@ -1417,7 +1417,7 @@ erc-once-with-server-event
 channel-buffers it may not work at all, as it uses the LOCAL
 argument of `add-hook' and `remove-hook' to ensure multiserver
 capabilities."
-  (unless (erc-server-buffer-p)
+  (unless (erc--server-buffer-p)
     (error
      "You should only run `erc-once-with-server-event' in a server buffer"))
   (let ((fun (make-symbol "fun"))
@@ -1474,19 +1474,30 @@ erc-server-buffer-live-p
   (and (processp erc-server-process)
        (buffer-live-p (process-buffer erc-server-process))))
 
-(defun erc-server-buffer-p (&optional buffer)
+(define-obsolete-function-alias
+  'erc-server-buffer-p 'erc-server-or-unjoined-channel-buffer-p "30.1")
+(defun erc-server-or-unjoined-channel-buffer-p (&optional buffer)
   "Return non-nil if argument BUFFER is an ERC server buffer.
-
-If BUFFER is nil, the current buffer is used."
+If BUFFER is nil, use the current buffer.  For historical
+reasons, also return non-nil for channel buffers the client has
+parted or from which it's been kicked."
   (with-current-buffer (or buffer (current-buffer))
     (and (eq major-mode 'erc-mode)
          (null (erc-default-target)))))
 
+(defun erc--server-buffer-p (&optional buffer)
+  "Return non-nil if BUFFER is an ERC server buffer.
+Without BUFFER, use the current buffer."
+  (if buffer
+      (with-current-buffer buffer
+        (and (eq major-mode 'erc-mode) (null erc--target)))
+    (and (eq major-mode 'erc-mode) (null erc--target))))
+
 (defun erc-open-server-buffer-p (&optional buffer)
   "Return non-nil if BUFFER is an ERC server buffer with an open IRC process.
 
 If BUFFER is nil, the current buffer is used."
-  (and (erc-server-buffer-p buffer)
+  (and (erc--server-buffer-p buffer)
        (erc-server-process-alive buffer)))
 
 (defun erc-query-buffer-p (&optional buffer)
@@ -1858,7 +1869,10 @@ erc-member-ignore-case
 
 (defun erc-get-buffer (target &optional proc)
   "Return the buffer matching TARGET in the process PROC.
-If PROC is not supplied, all processes are searched."
+Without PROC, search all ERC buffers.  For historical reasons,
+skip buffers for channels the client has \"PART\"ed or from which
+it's been \"KICK\"ed.  Expect users to use a different function
+for finding targets independent of \"JOIN\"edness."
   (let ((downcased-target (erc-downcase target)))
     (catch 'buffer
       (erc-buffer-filter
@@ -4584,10 +4598,7 @@ erc-cmd-GQUIT
     ;; kill them
     (run-at-time
      4 nil
-     (lambda ()
-       (dolist (buffer (erc-buffer-list (lambda (buf)
-                                          (not (erc-server-buffer-p buf)))))
-         (kill-buffer buffer)))))
+     #'erc-buffer-do (lambda () (when erc--target (kill-buffer)))))
   t)
 
 (defalias 'erc-cmd-GQ #'erc-cmd-GQUIT)
@@ -7027,7 +7038,9 @@ erc--current-buffer-joined-p
 ;; continue to use `erc-default-target'.
 
 (defun erc-default-target ()
-  "Return the current default target (as a character string) or nil if none."
+  "Return the current channel or query target, if any.
+For historical reasons, return nil in channel buffers if not
+currently joined."
   (let ((tgt (car erc-default-recipients)))
     (cond
      ((not tgt) nil)
@@ -7589,15 +7602,14 @@ erc-directory-writable-p
   (unless (file-attributes dir) (make-directory dir))
   (or (file-accessible-directory-p dir) (error "Cannot access %s" dir)))
 
+;; FIXME make function obsolete or alias to something less confusing.
 (defun erc-kill-query-buffers (process)
-  "Kill all buffers of PROCESS.
-Does nothing if PROCESS is not a process object."
+  "Kill all target buffers of PROCESS, including channel buffers.
+Do nothing if PROCESS is not a process object."
   ;; here, we only want to match the channel buffers, to avoid
   ;; "selecting killed buffers" b0rkage.
   (when (processp process)
-    (erc-with-all-buffers-of-server process
-      (lambda ()
-	(not (erc-server-buffer-p)))
+    (erc-with-all-buffers-of-server process (lambda () erc--target)
       (kill-buffer (current-buffer)))))
 
 (defun erc-nick-at-point ()
diff --git a/test/lisp/erc/resources/join/network-id/foonet.eld b/test/lisp/erc/resources/join/network-id/foonet.eld
index 7d63f5f0c6c..74a107f8144 100644
--- a/test/lisp/erc/resources/join/network-id/foonet.eld
+++ b/test/lisp/erc/resources/join/network-id/foonet.eld
@@ -1,8 +1,8 @@
 ;; -*- mode: lisp-data; -*-
 ((pass 10 "PASS :foonet:changeme"))
-((nick 1 "NICK tester"))
+((nick 10 "NICK tester"))
 
-((user 1 "USER user 0 * :tester")
+((user 10 "USER user 0 * :tester")
  (0 ":irc.foonet.org 001 tester :Welcome to the foonet IRC Network tester")
  (0 ":irc.foonet.org 002 tester :Your host is irc.foonet.org, running version oragono-2.6.0-7481bf0385b95b16")
  (0 ":irc.foonet.org 003 tester :This server was created Mon, 10 May 2021 00:58:22 UTC")
-- 
2.41.0

From 8ec6095d2f433e505765874c043903d0975a70e7 Mon Sep 17 00:00:00 2001
From: "F. Jason Park" <[email-masked]>
Date: Sun, 15 Oct 2023 13:20:07 -0700
Subject: [PATCH 2/2] [5.6] Add function erc-target

* etc/ERC-NEWS: Mention `erc-target' and new `erc-server-buffer-p'
alias.
* lisp/erc/erc-backend.el (erc-process-sentinel): Set `joined-p' slot
of `erc--target-channel' object to nil if applicable.
(JOIN): Mark `erc--target-channel' object as being joined.
* lisp/erc/erc-common.el (erc--target-channel): Add `joined-p' slot.
Use hyphenated name so accessor function's name ends in "joined-p"
rather than "joinedp".
(erc--target): Relocate here from erc.el.
(erc-target): New public API function to return the current buffer's
target as a string even in channels that have been unjoined.
* lisp/erc/erc-networks.el (erc--default-target): Remove forward
declaration.
(erc-networks--id-reload): Use `erc-target' instead of
`erc--default-target' as predicate for visiting target buffers.
* lisp/erc/erc.el (erc-remove-channel-users): Set channel "joinedness"
to nil in `erc--target-channel' object, when applicable.
(erc--target): Move to erc-common.
(erc--default-target): Remove, replaced by new function `erc-target'.
(erc-query-buffer-p): Use `erc-target'.
(erc-after-connect): Revise doc string.
(erc-connection-established): Revise doc string and move
`erc-unhide-query-prompt' business before hook.
(erc--current-buffer-joined-p): Remove comment and use new `joined-p'
slot of `erc--target-channel' for determining "joinedness" of channel.
(erc-kill-buffer-function): Use `erc--target-channel-p' for detecting
whether the buffer is a channel buffer.
* test/lisp/erc/erc-networks-tests.el
(erc-networks--shrink-ids-and-buffer-names--hook-collapse-target):
Remove comment.
* test/lisp/erc/erc-scenarios-base-reuse-buffers.el
(erc-scenarios-common--base-reuse-buffers-channel-buffers):
Clarify invariant.
* test/lisp/erc/erc-tests.el (erc-with-all-buffers-of-server):
Replace `erc-default-recipients' with `erc--target'.
(erc--target-from-string): Fix expected shape of `erc--target-channel'
struct.
(erc-message): Set `erc--target' in buffer "#chan".
---
 etc/ERC-NEWS                                  | 18 +++++
 lisp/erc/erc-backend.el                       |  5 +-
 lisp/erc/erc-common.el                        | 22 ++++---
 lisp/erc/erc-networks.el                      | 12 ++--
 lisp/erc/erc.el                               | 65 ++++++-------------
 test/lisp/erc/erc-networks-tests.el           |  5 --
 .../erc/erc-scenarios-base-reuse-buffers.el   |  2 +
 test/lisp/erc/erc-tests.el                    | 19 +++---
 8 files changed, 72 insertions(+), 76 deletions(-)

diff --git a/etc/ERC-NEWS b/etc/ERC-NEWS
index 2e56539f210..4b261254bda 100644
--- a/etc/ERC-NEWS
+++ b/etc/ERC-NEWS
@@ -402,6 +402,24 @@ use of 'insert-before-markers' instead of 'insert'.  As always, users
 feeling unduly inconvenienced by these changes are encouraged to voice
 their concerns on the bug list.
 
+*** Introducing new ways to detect ERC buffer types.
+The old standby 'erc-default-target' has served ERC well for over two
+decades.  But a lesser known gotcha affecting its use has always
+haunted an unlucky few, that is, the function has always returned
+non-nil in "unjoined" channel buffers (those that the client has
+parted with or been kicked from).  While perhaps not itself a major
+footgun, recessive pitfalls rooted in this subtlety continue to affect
+dependent functions, like 'erc-get-buffer'.
+
+To discourage misuse of 'erc-default-target', ERC 5.6 offers an
+alternative in the function 'erc-target', which is identical to the
+former except for its disregard for "joinedness."  As a related bonus,
+the dependent function 'erc-server-buffer-p' is being rebranded as
+'erc-server-or-unjoined-channel-buffer-p'.  Unfortunately, this
+release lacks a similar solution for detecting "joinedness" directly,
+but users can turn to 'xor'-ing 'erc-default-target' and 'erc-target'
+as a makeshift kludge.
+
 *** Miscellaneous changes
 Two helper macros from GNU ELPA's Compat library are now available to
 third-party modules as 'erc-compat-call' and 'erc-compat-function'.
diff --git a/lisp/erc/erc-backend.el b/lisp/erc/erc-backend.el
index 3d34fc97d00..29b69fad0e6 100644
--- a/lisp/erc/erc-backend.el
+++ b/lisp/erc/erc-backend.el
@@ -1103,7 +1103,7 @@ erc-process-sentinel
             (erc--register-connection)
           ;; assume event is 'failed
           (erc-with-all-buffers-of-server cproc nil
-                                          (setq erc-server-connected nil))
+            (setq erc-server-connected nil))
           (when erc-server-ping-handler
             (progn (cancel-timer erc-server-ping-handler)
                    (setq erc-server-ping-handler nil)))
@@ -1111,6 +1111,8 @@ erc-process-sentinel
                               (erc-current-nick) (system-name) "")
           (dolist (buf (erc-buffer-filter (lambda () (boundp 'erc-channel-users)) cproc))
             (with-current-buffer buf
+              (when (erc--target-channel-p erc--target)
+                (setf (erc--target-channel-joined-p erc--target) nil))
               (setq erc-channel-users (make-hash-table :test 'equal))))
           ;; Hide the prompt
           (erc--hide-prompt cproc)
@@ -1729,6 +1731,7 @@ erc--server-determine-join-display-context
                         (with-suppressed-warnings
                             ((obsolete erc-add-default-channel))
                           (erc-add-default-channel chnl))
+                        (setf (erc--target-channel-joined-p erc--target) t)
                         (erc-server-send (format "MODE %s" chnl)))
                       (erc-with-buffer (chnl proc)
                         (erc-channel-begin-receiving-names))
diff --git a/lisp/erc/erc-common.el b/lisp/erc/erc-common.el
index 8d896e663b5..930e8032f6d 100644
--- a/lisp/erc/erc-common.el
+++ b/lisp/erc/erc-common.el
@@ -81,16 +81,13 @@ erc--target
   (string "" :type string :documentation "Received name of target.")
   (symbol nil :type symbol :documentation "Case-mapped name as symbol."))
 
-;; At some point, it may make sense to add a query type with an
-;; account field, which may help support reassociation across
-;; reconnects and nick changes (likely requires v3 extensions).
-;;
-;; These channel variants should probably take on a `joined' field to
-;; track "joinedness", which `erc-server-JOIN', `erc-server-PART',
-;; etc. should toggle.  Functions like `erc--current-buffer-joined-p'
-;; may find it useful.
+;; At some point, it may make sense to add a separate query type,
+;; possibly with an account field to help reassociation across
+;; reconnects and nick changes.
+
+(cl-defstruct (erc--target-channel (:include erc--target))
+  (joined-p nil :type boolean :documentation "Whether channel is joined."))
 
-(cl-defstruct (erc--target-channel (:include erc--target)))
 (cl-defstruct (erc--target-channel-local (:include erc--target-channel)))
 
 ;; Beginning in 5.5/29.1, the `tags' field may take on one of two
@@ -427,6 +424,13 @@ erc-with-all-buffers-of-server
                             ,@forms))
                         ,process)))
 
+(defvar-local erc--target nil
+  "A permanent `erc--target' struct instance in channel and query buffers.")
+
+(define-inline erc-target ()
+  "Return target of current buffer, if any, as a string."
+  (inline-quote (and erc--target (erc--target-string erc--target))))
+
 (defun erc-log-aux (string)
   "Do the debug logging of STRING."
   (let ((cb (current-buffer))
diff --git a/lisp/erc/erc-networks.el b/lisp/erc/erc-networks.el
index d73d715db2c..dd047243a3c 100644
--- a/lisp/erc/erc-networks.el
+++ b/lisp/erc/erc-networks.el
@@ -53,7 +53,6 @@ erc-server-parameters
 (defvar erc-server-process)
 (defvar erc-session-server)
 
-(declare-function erc--default-target "erc" nil)
 (declare-function erc--get-isupport-entry "erc-backend" (key &optional single))
 (declare-function erc-buffer-filter "erc" (predicate &optional proc))
 (declare-function erc-current-nick "erc" nil)
@@ -991,12 +990,11 @@ erc-networks--id-reload
                                       (erc-networks--id-qualifying-len nid))
   (erc-networks--rename-server-buffer (or proc erc-server-process) parsed)
   (erc-networks--shrink-ids-and-buffer-names-any)
-  (erc-with-all-buffers-of-server
-      erc-server-process #'erc--default-target
-      (when-let* ((new-name (erc-networks--reconcile-buffer-names erc--target
-                                                                  nid))
-                  ((not (equal (buffer-name) new-name))))
-        (rename-buffer new-name 'unique))))
+  (erc-with-all-buffers-of-server erc-server-process #'erc-target
+    (when-let
+        ((new-name (erc-networks--reconcile-buffer-names erc--target nid))
+         ((not (equal (buffer-name) new-name))))
+      (rename-buffer new-name 'unique))))
 
 (cl-defgeneric erc-networks--id-ensure-comparable (self other)
   "Take measures to ensure two net identities are in comparable states.")
diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el
index 7144e81397a..26f7f5bf188 100644
--- a/lisp/erc/erc.el
+++ b/lisp/erc/erc.el
@@ -534,6 +534,8 @@ erc-remove-channel-users
 
 Removes all users in the current channel.  This is called by
 `erc-server-PART' and `erc-server-QUIT'."
+  (when (erc--target-channel-p erc--target)
+    (setf (erc--target-channel-joined-p erc--target) nil))
   (when (and erc-server-connected
              (erc-server-process-alive)
              (hash-table-p erc-channel-users))
@@ -1391,16 +1393,6 @@ erc--target-from-string
              #'make-erc--target)
            :string string :symbol (intern (erc-downcase string))))
 
-(defvar-local erc--target nil
-  "Info about a buffer's target, if any.")
-
-;; Temporary internal getter to ease transition to `erc--target'
-;; everywhere.  Will be replaced by updated `erc-default-target'.
-(defun erc--default-target ()
-  "Return target string or nil."
-  (when erc--target
-    (erc--target-string erc--target)))
-
 (defun erc-once-with-server-event (event f)
   "Run function F the next time EVENT occurs in the `current-buffer'.
 
@@ -1504,7 +1496,7 @@ erc-query-buffer-p
   "Return non-nil if BUFFER is an ERC query buffer.
 If BUFFER is nil, the current buffer is used."
   (with-current-buffer (or buffer (current-buffer))
-    (let ((target (erc-default-target)))
+    (let ((target (erc-target)))
       (and (eq major-mode 'erc-mode)
            target
            (not (memq (aref target 0) '(?# ?& ?+ ?!)))))))
@@ -2480,10 +2472,13 @@ erc-before-connect
   :type '(repeat function))
 
 (defcustom erc-after-connect nil
-  "Functions called after connecting to a server.
-This functions in this variable gets executed when an end of MOTD
-has been received.  All functions in here get called with the
-parameters SERVER and NICK."
+  "Abnormal hook run upon establishing a logical IRC connection.
+Runs on MOTD's end when `erc-server-connected' becomes non-nil.
+ERC calls members with `erc-server-announced-name', falling back
+to the 376/422 message's \"sender\", as well as the current nick,
+as given by the 376/422 message's \"target\" parameter, which is
+typically the same as that reported by `erc-current-nick'."
+  :package-version '(ERC . "5.6") ; FIXME sync on release
   :group 'erc-hooks
   :type '(repeat function))
 
@@ -5701,9 +5696,7 @@ erc-handle-login
         (erc-load-script f)))))
 
 (defun erc-connection-established (proc parsed)
-  "Run just after connection.
-
-Set user modes and run `erc-after-connect' hook."
+  "Set user mode and run `erc-after-connect' hook in server buffer."
   (with-current-buffer (process-buffer proc)
     (unless erc-server-connected ; only once per session
       (let ((server (or erc-server-announced-name
@@ -5722,14 +5715,11 @@ erc-connection-established
         (erc-update-mode-line)
         (erc-set-initial-user-mode nick buffer)
         (erc-server-setup-periodical-ping buffer)
-        (run-hook-with-args 'erc-after-connect server nick))))
-
-  (when erc-unhide-query-prompt
-    (erc-with-all-buffers-of-server proc
-      nil ; FIXME use `erc--target' after bug#48598
-      (when (and (erc-default-target)
-                 (not (erc-channel-p (car erc-default-recipients))))
-        (erc--unhide-prompt)))))
+        (when erc-unhide-query-prompt
+          (erc-with-all-buffers-of-server erc-server-process nil
+            (when (and erc--target (not (erc--target-channel-p erc--target)))
+              (erc--unhide-prompt))))
+        (run-hook-with-args 'erc-after-connect server nick)))))
 
 (defun erc-set-initial-user-mode (nick buffer)
   "If `erc-user-mode' is non-nil for NICK, set the user modes.
@@ -7017,25 +7007,11 @@ erc-nick-equal-p
 ;; default target handling
 
 (defun erc--current-buffer-joined-p ()
-  "Return whether the current target buffer is joined."
-  ;; This may be a reliable means of detecting subscription status,
-  ;; but it's also roundabout and awkward.  Perhaps it's worth
-  ;; discussing adding a joined slot to `erc--target' for this.
+  "Return non-nil if the current buffer is a channel and is joined."
   (cl-assert erc--target)
   (and (erc--target-channel-p erc--target)
-       (erc-get-channel-user (erc-current-nick)) t))
-
-;; While `erc-default-target' happens to return nil in channel buffers
-;; you've parted or from which you've been kicked, using it to detect
-;; whether a channel is currently joined may become unreliable in the
-;; future.  For now, third-party code can use
-;;
-;;   (erc-get-channel-user (erc-current-nick))
-;;
-;; A predicate may be provided eventually.  For retrieving a target's
-;; name regardless of subscription or connection status, new library
-;; code should use `erc--default-target'.  Third-party code should
-;; continue to use `erc-default-target'.
+       (erc--target-channel-joined-p erc--target)
+       t))
 
 (defun erc-default-target ()
   "Return the current channel or query target, if any.
@@ -8267,6 +8243,7 @@ erc-kill-buffer-hook
   :group 'erc-hooks
   :type 'hook)
 
+;; FIXME alias and deprecate current *-function suffixed name.
 (defun erc-kill-buffer-function ()
   "Function to call when an ERC buffer is killed.
 This function should be on `kill-buffer-hook'.
@@ -8280,7 +8257,7 @@ erc-kill-buffer-function
     (cond
      ((eq (erc-server-buffer) (current-buffer))
       (run-hooks 'erc-kill-server-hook))
-     ((erc-channel-p (or (erc-default-target) (buffer-name)))
+     ((erc--target-channel-p erc--target)
       (run-hooks 'erc-kill-channel-hook))
      (t
       (run-hooks 'erc-kill-buffer-hook)))))
diff --git a/test/lisp/erc/erc-networks-tests.el b/test/lisp/erc/erc-networks-tests.el
index e95d99c128f..e5069880bc5 100644
--- a/test/lisp/erc/erc-networks-tests.el
+++ b/test/lisp/erc/erc-networks-tests.el
@@ -623,11 +623,6 @@ erc-networks--shrink-ids-and-buffer-names--hook-collapse-target
                                :symbol 'foonet/dummy
                                :parts [foonet "dummy"]
                                :len 2)
-             ;; `erc-kill-buffer-function' uses legacy target detection
-             ;; but falls back on buffer name, so no need for:
-             ;;
-             ;;   erc-default-recipients '("#a")
-             ;;
              erc--target (erc--target-from-string "#a")
              erc-server-process (with-temp-buffer
                                   (erc-networks-tests--create-dead-proc)))
diff --git a/test/lisp/erc/erc-scenarios-base-reuse-buffers.el b/test/lisp/erc/erc-scenarios-base-reuse-buffers.el
index 71027a0c138..af483bb1a52 100644
--- a/test/lisp/erc/erc-scenarios-base-reuse-buffers.el
+++ b/test/lisp/erc/erc-scenarios-base-reuse-buffers.el
@@ -124,6 +124,7 @@ erc-scenarios-common--base-reuse-buffers-channel-buffers
         (erc-d-t-search-for 1 "shake my sword")
         (erc-cmd-PART "#chan")
         (funcall expect 3 "You have left channel #chan")
+        (should-not (erc-get-channel-user (erc-current-nick)))
         (erc-cmd-JOIN "#chan")))
 
     (ert-info ("Part #chan@barnet")
@@ -139,6 +140,7 @@ erc-scenarios-common--base-reuse-buffers-channel-buffers
       (get-buffer "#chan/127.0.0.1<3>"))
 
     (ert-info ("Activity continues in new, <n>-suffixed #chan@foonet buffer")
+      ;; The first /JOIN did not cause the same buffer to be reused.
       (with-current-buffer "#chan/127.0.0.1"
         (should-not (erc-get-channel-user (erc-current-nick))))
       (with-current-buffer "#chan/127.0.0.1<3>"
diff --git a/test/lisp/erc/erc-tests.el b/test/lisp/erc/erc-tests.el
index 4f4662f5075..5155a8fc724 100644
--- a/test/lisp/erc/erc-tests.el
+++ b/test/lisp/erc/erc-tests.el
@@ -69,26 +69,25 @@ erc-with-all-buffers-of-server
     (with-current-buffer (get-buffer-create "#foo")
       (erc-mode)
       (setq erc-server-process proc-exnet)
-      (setq erc-default-recipients '("#foo")))
+      (setq erc--target (erc--target-from-string "#foo")))
 
     (with-current-buffer (get-buffer-create "#spam")
       (erc-mode)
       (setq erc-server-process proc-onet)
-      (setq erc-default-recipients '("#spam")))
+      (setq erc--target (erc--target-from-string "#spam")))
 
     (with-current-buffer (get-buffer-create "#bar")
       (erc-mode)
       (setq erc-server-process proc-onet)
-      (setq erc-default-recipients '("#bar")))
+      (setq erc--target (erc--target-from-string "#bar")))
 
     (with-current-buffer (get-buffer-create "#baz")
       (erc-mode)
       (setq erc-server-process proc-exnet)
-      (setq erc-default-recipients '("#baz")))
+      (setq erc--target (erc--target-from-string "#baz")))
 
     (should (eq (get-buffer-process "ExampleNet") proc-exnet))
-    (erc-with-all-buffers-of-server (get-buffer-process "ExampleNet")
-      nil
+    (erc-with-all-buffers-of-server (get-buffer-process "ExampleNet") nil
       (kill-buffer))
 
     (should-not (get-buffer "ExampleNet"))
@@ -102,8 +101,7 @@ erc-with-all-buffers-of-server
            (calls 0)
            (get-test (lambda () (cl-incf calls) test)))
 
-      (erc-with-all-buffers-of-server proc-onet
-        (funcall get-test)
+      (erc-with-all-buffers-of-server proc-onet (funcall get-test)
         (kill-buffer))
 
       (should (= calls 1)))
@@ -812,7 +810,7 @@ erc--restore-initialize-priors
 
 (ert-deftest erc--target-from-string ()
   (should (equal (erc--target-from-string "#chan")
-                 #s(erc--target-channel "#chan" \#chan)))
+                 #s(erc--target-channel "#chan" \#chan nil)))
 
   (should (equal (erc--target-from-string "Bob")
                  #s(erc--target "Bob" bob)))
@@ -820,7 +818,7 @@ erc--target-from-string
   (let ((erc--isupport-params (make-hash-table)))
     (puthash 'CHANTYPES  '("&#") erc--isupport-params)
     (should (equal (erc--target-from-string "&Bitlbee")
-                   #s(erc--target-channel-local "&Bitlbee" &bitlbee)))))
+                   #s(erc--target-channel-local "&Bitlbee" &bitlbee nil)))))
 
 (ert-deftest erc--modify-local-map ()
   (when (and (bound-and-true-p erc-irccontrols-mode)
@@ -1846,6 +1844,7 @@ erc-message
         (erc-mode)
         (setq erc-server-process (buffer-local-value 'erc-server-process
                                                      (get-buffer "ExampleNet"))
+              erc--target (erc--target-from-string "#chan")
               erc-default-recipients '("#chan")
               erc-channel-users (make-hash-table :test 'equal)
               erc-network 'ExampleNet)
-- 
2.41.0