Subject: bug#62833: 30.0.50; ERC 5.6: Rethink buffer-display options and behavior

From: "J.P." <[email-masked]>
Date: Mon, 24 Apr 2023 07:34:53 -0700

"J.P." <[email-masked]> writes:

> A troubling discovery has come to light regarding the option
> `erc-reconnect-display' (new in 5.5 and Emacs 29), which was the main
> product of bug#51753 before it pivoted to that frame-isolation feature.
> (This bug thread exists in part to move past that confusion.) The issue
> here concerns the time interval during which `erc-reconnect-display'
> takes precedence over its fellow buffer-display options, like
> `erc-join-buffer'. As things stand, this interval only ends for a
> session when `erc-cmd-JOIN' runs in the server buffer. Without that
> specific intervention, the option remains in effect for the remainder of
> the session.

This issue will be noted in the option's doc string in Emacs 29.

>> A more daring and arguably more meaningful move would be to repurpose
>> `erc-auto-query' (newly aliased to `erc-receive-query-display') as
>> something like a more general `erc-receive-display', which could cover
>> display handling for anything protocol driven (i.e.,
>> "non-interactive").
>
> Given the more pressing concerns noted above, I haven't yet devoted any
> thought to this but promise to eventually.

I'm pretty much convinced that there's no way to generalize this option
without wading back into bug#51753 territory. If a user already has it
customized to `buffer' or `window', the same risk of accidental sharing
exists, and rigging up warnings to intercept them on the first such
happening doesn't seem worth it. So, for this latest iteration, I've
abandoned this notion and have instead focused on making good on the
claims put forth by `erc-query-on-unjoined-chan-privmsg' and its doc
string decades ago.

Basically, this involves changing the current meaning of
`erc-auto-query' when nil to mean "defer to `erc-join-buffer'," which
brings it in line with all the other buffer-display options. An
obligatory compatibility flag for users to access the old behavior
rounds out the change. The basic wager we're making here is that the
chances of someone having `erc-join-buffer' set to 'buffer' or 'window'
while also having this option set to nil is negligible. The upside to be
gained is having `erc-query-on-unjoined-chan-privmsg', which I've
renamed `erc-ensure-target-buffer-on-privmsg', affect query buffers as
well as channels, which is a minor but solid UX win, IMO.

>> There's also the matter of assigning Custom groups for these options.
>> It'd be "nice" if we could tag these with multiple groups rather than
>> confine them to exclusive ownership. They're currently spread over
>> `erc-buffers', `erc-query', and `erc-display'. All seem to have valid
>> claims when you consider their respective constituencies.
>
> Actually, that's slightly untrue: the `erc-display' group doesn't
> include any buffer-display options. Regardless, for this iteration, I've
> stuck with the current group assignments, which are `erc-buffers' for
> all but `erc-receive-query-display', which lives in `erc-query'.

I've since learned (from Corwin) that options can indeed belong to
multiple groups, which is explained clearly enough in the manual. Armed
with this fact, I'm now thinking it would behoove us to assign
`erc-auto-query' and `erc-query-on-unjoined-chan-privmsg' to
`erc-buffers' but keep them in `erc-query' as well, for compatibility.

>> It's also been casually suggested that we might consider deferring to
>> `erc-setup-buffer' in areas not directly involved in message handling,
>> such as in erc-sidebar, to allow the options in question to influence
>> how buffers are displayed more generally. Not sure I have an opinion on
>> this quite yet, but if anyone else does, please share.
>
> This is also still to do.

And still.

>From dd9f78ea159958ccef4eabf3f782d4f29d4d24e7 Mon Sep 17 00:00:00 2001
From: "F. Jason Park" <[email-masked]>
Date: Mon, 24 Apr 2023 06:29:49 -0700
Subject: [PATCH 0/4] *** NOT A PATCH ***

*** BLURB HERE ***

F. Jason Park (4):
  [5.6] Revive option erc-query-on-unjoined-chan-privmsg
  [5.6] Move ERC's buffer-display tests to separate file
  [5.6] Extend erc-interactive-display to cover /JOINs
  [5.6] Ignore erc-reconnect-display after a timeout

 etc/ERC-NEWS                                  |  36 ++-
 lisp/erc/erc-backend.el                       |  41 ++-
 lisp/erc/erc.el                               | 160 ++++++++----
 test/lisp/erc/erc-scenarios-base-attach.el    | 191 ++++++++++++++
 .../erc/erc-scenarios-base-buffer-display.el  | 236 ++++++++++++++++++
 test/lisp/erc/erc-scenarios-base-reconnect.el |  89 -------
 test/lisp/erc/erc-tests.el                    |  13 +-
 .../base/channel-buffer-revival/reattach.eld  |  56 +++++
 8 files changed, 660 insertions(+), 162 deletions(-)
 create mode 100644 test/lisp/erc/erc-scenarios-base-attach.el
 create mode 100644 test/lisp/erc/erc-scenarios-base-buffer-display.el
 create mode 100644 test/lisp/erc/resources/base/channel-buffer-revival/reattach.eld

Interdiff:
diff --git a/etc/ERC-NEWS b/etc/ERC-NEWS
index e9de48b2e34..f1fac8e5baa 100644
--- a/etc/ERC-NEWS
+++ b/etc/ERC-NEWS
@@ -48,9 +48,16 @@ of interactive invocations, hence the borrowing of an old option,
 'erc-query-display', and the bestowing of a new alias,
 'erc-interactive-display', which better describes its expanded role as
 a more general buffer-display knob for interactive commands ("/QUERY"
-still among them).  Accompanying this addition are "display"-suffixed
-aliases for related options 'erc-join-buffer' and 'erc-auto-query',
-which users have reported as being difficult to discover and remember.
+still among them).
+
+Accompanying this addition are "display"-suffixed aliases for related
+options 'erc-join-buffer' and 'erc-auto-query', which users have
+reported as being difficult to discover and remember.  As for the
+latter option specifically (now known as 'erc-receive-query-display'),
+assigning it a nil value now tells ERC to use 'erc-join-buffer' in its
+place, much like with related buffer-display options, like
+'erc-interactive-display'.  The old nil behavior can still be gotten
+via a new compatibility flag, 'erc-receive-query-display-defer'.
 
 ** Setting a module's mode variable via Customize earns a warning.
 Trying and failing to activate a module via its minor mode's Custom
@@ -111,7 +118,8 @@ other than the symbol 'erc-button-buttonize-nicks' appearing in the
 "FORM" field (third element) of this entry are considered deprecated
 and will incur a warning.
 
-** The option erc-query-on-unjoined-chan-privmsg has been restored.
+** The option 'erc-query-on-unjoined-chan-privmsg' has been restored.
+(Although it's now called 'erc-ensure-target-buffer-on-privmsg'.)
 In ERC 5.5, this option was removed from the default client code and
 thus prevented from influencing PRIVMSG handling because its precise
 purpose could not be determined with any confidence.  After some
diff --git a/lisp/erc/erc-backend.el b/lisp/erc/erc-backend.el
index c72c769f2e1..d14640e798d 100644
--- a/lisp/erc/erc-backend.el
+++ b/lisp/erc/erc-backend.el
@@ -102,11 +102,11 @@
 (require 'erc-common)
 
 (defvar erc--target)
-(defvar erc-auto-query)
 (defvar erc-channel-list)
 (defvar erc-channel-users)
 (defvar erc-default-nicks)
 (defvar erc-default-recipients)
+(defvar erc-ensure-target-buffer-on-privmsg)
 (defvar erc-format-nick-function)
 (defvar erc-format-query-as-channel-p)
 (defvar erc-hide-prompt)
@@ -123,7 +123,8 @@ erc-nick
 (defvar erc-nick-change-attempt-count)
 (defvar erc-prompt-for-channel-key)
 (defvar erc-prompt-hidden)
-(defvar erc-query-on-unjoined-chan-privmsg)
+(defvar erc-receive-query-display)
+(defvar erc-receive-query-display-defer)
 (defvar erc-reuse-buffers)
 (defvar erc-verbose-server-ping)
 (defvar erc-whowas-on-nosuchnick)
@@ -1854,11 +1855,15 @@ define-erc-response-handler
         (unless (or buffer noticep (string-empty-p tgt) (eq ?$ (aref tgt 0))
                     (erc-is-message-ctcp-and-not-action-p msg))
           (if privp
-              (when erc-auto-query
-                (let ((erc-join-buffer erc-auto-query))
-                  (setq buffer (erc--open-target nick))))
-            ;; A channel buffer has been killed but is still joined
-            (when erc-query-on-unjoined-chan-privmsg
+              (when-let ((erc-join-buffer
+                          (or (and (not erc-receive-query-display-defer)
+                                   erc-receive-query-display)
+                              (and erc-ensure-target-buffer-on-privmsg
+                                   (or erc-receive-query-display
+                                       erc-join-buffer)))))
+                (setq buffer (erc--open-target nick)))
+            ;; A channel buffer has been killed but is still joined.
+            (when erc-ensure-target-buffer-on-privmsg
               (setq buffer (erc--open-target tgt)))))
         (when buffer
           (with-current-buffer buffer
diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el
index 85b3d8bb650..847e27904b1 100644
--- a/lisp/erc/erc.el
+++ b/lisp/erc/erc.el
@@ -1521,16 +1521,16 @@ erc-buffer-display
   `bury'            - bury it in a new buffer,
   `buffer'          - in place of the current buffer,
 
-See related options `erc-interactive-display' and
-`erc-reconnect-display' as well as `erc-receive-query-display',
-which resides in the customization group `erc-query'."
+See related options `erc-interactive-display',
+`erc-reconnect-display', and `erc-receive-query-display'."
   :package-version '(ERC . "5.5")
   :group 'erc-buffers
   :type '(choice (const :tag "Split window and select" window)
                  (const :tag "Split window, don't select" window-noselect)
                  (const :tag "New frame" frame)
                  (const :tag "Bury in new buffer" bury)
-                 (const :tag "Use current buffer" buffer)))
+                 (const :tag "Use current buffer" buffer)
+                 (const :tag "Use current buffer" t)))
 
 (defvaralias 'erc-query-display 'erc-interactive-display)
 (defcustom erc-interactive-display 'window
@@ -1557,7 +1557,7 @@ erc-reconnect-display
 `erc-buffer-display' for a description of possible values."
   :package-version '(ERC . "5.5")
   :group 'erc-buffers
-  :type '(choice (const :tag "Use value of `erc-join-buffer'" nil)
+  :type '(choice (const :tag "Use value of `erc-buffer-display'" nil)
                  (const :tag "Split window and select" window)
                  (const :tag "Split window, don't select" window-noselect)
                  (const :tag "New frame" frame)
@@ -1569,7 +1569,7 @@ erc-reconnect-display-timeout
 The countdown starts on MOTD and is canceled early by any
 \"slash\" command."
   :type 'integer
-  :group 'erc-buffer)
+  :group 'erc-buffers)
 
 (defcustom erc-frame-alist nil
   "Alist of frame parameters for creating erc frames.
@@ -3984,7 +3984,7 @@ erc-cmd-QUOTE
 (defun erc-cmd-QUERY (&optional user)
   "Open a query with USER.
 How the query is displayed (in a new window, frame, etc.) depends
-on the value of `erc-query-display'."
+on the value of `erc-interactive-display'."
   ;; FIXME: The doc string used to say at the end:
   ;; "If USER is omitted, close the current query buffer if one exists
   ;; - except this is broken now ;-)"
@@ -3995,8 +3995,8 @@ erc-cmd-QUERY
   (unless user
       ;; currently broken, evil hack to display help anyway
                                         ;(erc-delete-query))))
-    (signal 'wrong-number-of-arguments ""))
-  (let ((erc-join-buffer erc-query-display))
+    (signal 'wrong-number-of-arguments '(erc-cmd-QUERY 0)))
+  (let ((erc-join-buffer erc-interactive-display))
     (erc-with-server-buffer
      (erc--open-target user))))
 
@@ -4747,44 +4747,64 @@ erc-query
 
 (defvaralias 'erc-auto-query 'erc-receive-query-display)
 (defcustom erc-receive-query-display 'window-noselect
-  "How to display a query buffer when you receive a private message.
+  "If non-nil, create a query buffer each time you receive a private message.
 If the buffer doesn't already exist, it is created.
 
 This can be set to a symbol, to control how the new query window
 should appear.  The default behavior is to display the buffer in
-a new window, but not to select it.  See the documentation for
-`erc-buffer-display' for a description of available values.  Note
-that setting this option to nil forgoes buffer creation entirely.
-It does not mean \"fall back on `erc-buffer-display'\", like it
-does for buffer-display options, like `erc-interactive-display',
-that reside in the customization group `erc-buffers'."
+a new window but not to select it.  See the documentation for
+`erc-buffer-display' for a description of available values.
+
+Note that the legacy behavior of forgoing buffer creation
+entirely when this option is nil requires setting the
+compatibility flag `erc-receive-query-display-defer' to nil.  Use
+`erc-ensure-target-buffer-on-privmsg' to achieve the same effect."
+  :package-version '(ERC . "5.6")
+  :group 'erc-buffers
   :group 'erc-query
-  :type '(choice (const :tag "Don't create query window" nil)
+  :type '(choice (const :tag "Defer to value of `erc-buffer-display'" nil)
                  (const :tag "Split window and select" window)
                  (const :tag "Split window, don't select" window-noselect)
                  (const :tag "New frame" frame)
                  (const :tag "Bury in new buffer" bury)
-                 (const :tag "Use current buffer" buffer)))
+                 (const :tag "Use current buffer" buffer)
+                 (const :tag "Use current buffer" t)))
+
+(defcustom erc-receive-query-display-defer t
+  "How to interpret a null `erc-receive-query-display'.
+When this option is non-nil, ERC defers to `erc-buffer-display'
+upon seeing a nil value for `erc-receive-query-display', much
+like it does with other buffer-display options, like
+`erc-interactive-display'.  Otherwise, when this option is nil,
+ERC retains the legacy behavior of not creating a new query
+buffer."
+  :package-version '(ERC . "5.6")
+  :group 'erc-buffers
+  :group 'erc-query
+  :type 'boolean)
 
-(defcustom erc-query-on-unjoined-chan-privmsg t
-  "If non-nil create query buffer on receiving any PRIVMSG at all.
+(defvaralias 'erc-query-on-unjoined-chan-privmsg
+  'erc-ensure-target-buffer-on-privmsg)
+(defcustom erc-ensure-target-buffer-on-privmsg t
+  "When non-nil, create a target buffer upon receiving a PRIVMSG.
 This includes PRIVMSGs directed to channels.  If you are using an IRC
 bouncer, such as dircproxy, to keep a log of channels when you are
 disconnected, you should set this option to t.
 
+For queries (direct messages), this option's non-nil meaning is
+straightforward: if a buffer doesn't exist for the sender, create
+one.  For channels, the use case is more niche and usually
+involves receiving playback (via commands like ZNC's
+\"PLAYBUFFER\") for channels to which your bouncer is joined but
+from which you've \"detached\".
+
 Note that this option was absent from ERC 5.5 because knowledge
 of its intended role was \"unavailable\" during a major
 refactoring involving buffer management.  The option has since
-been restored in ERC 5.6 with the caveat that its purpose is
-assumed to more or less reflect the following, which can be
-thought of as an updated version of the paragraph above:
-
-If non-nil, create a channel buffer on receiving a PRIVMSG if
-none exists.  This only affects messages targeting a channel
-whose buffer was killed but to which you're still joined or one
-whose buffer never existed, presumably because a bouncer is
-sending playback summoned with a command similar to ZNC's
-PLAYBUFFER."
+been restored in ERC 5.6 but now also affects queries in the
+manner implied above, which was lost sometime before ERC 5.4."
+  :package-version '(ERC . "5.6") ; revived
+  :group 'erc-buffers
   :group 'erc-query
   :type 'boolean)
 
diff --git a/test/lisp/erc/erc-scenarios-base-attach.el b/test/lisp/erc/erc-scenarios-base-attach.el
index 91763842879..ccf5d1f9582 100644
--- a/test/lisp/erc/erc-scenarios-base-attach.el
+++ b/test/lisp/erc/erc-scenarios-base-attach.el
@@ -61,9 +61,9 @@
   (let ((load-path (cons (ert-resource-directory) load-path)))
     (require 'erc-scenarios-common)))
 
-(ert-deftest erc-scenarios-base-attach--query-on-unjoined-enabled ()
+(ert-deftest erc-scenarios-base-attach--ensure-target-buffer--enabled ()
   :tags '(:expensive-test)
-  (should erc-query-on-unjoined-chan-privmsg)
+  (should erc-ensure-target-buffer-on-privmsg)
 
   (erc-scenarios-common-with-cleanup
       ((erc-scenarios-common-dialog "base/channel-buffer-revival")
@@ -103,16 +103,16 @@ erc-scenarios-base-attach--query-on-unjoined-enabled
         (erc-scenarios-common-say "bob: hi")
         (funcall expect 10 "Pawn me to this")))))
 
-(ert-deftest erc-scenarios-base-attach--query-on-unjoined-disabled ()
+(ert-deftest erc-scenarios-base-attach--ensure-target-buffer--disabled ()
   :tags '(:expensive-test)
-  (should erc-query-on-unjoined-chan-privmsg)
+  (should erc-ensure-target-buffer-on-privmsg)
 
   (erc-scenarios-common-with-cleanup
       ((erc-scenarios-common-dialog "base/channel-buffer-revival")
        (dumb-server (erc-d-run "localhost" t 'reattach))
        (port (process-contact dumb-server :service))
        (erc-server-flood-penalty 0.1)
-       (erc-query-on-unjoined-chan-privmsg nil) ; off
+       (erc-ensure-target-buffer-on-privmsg nil) ; off
        (expect (erc-d-t-make-expecter)))
 
     (ert-info ("Connect to foonet")
@@ -144,4 +144,48 @@ erc-scenarios-base-attach--query-on-unjoined-disabled
         (erc-scenarios-common-say "bob: hi")
         (funcall expect 10 "Pawn me to this")))))
 
+
+;; We omit the `enabled' case for queries because it's the default for
+;; this option and already covered many times over by other tests in
+;; this directory.
+
+(ert-deftest erc-scenarios-base-attach--ensure-target-buffer--disabled-query ()
+  :tags '(:expensive-test)
+  (should erc-ensure-target-buffer-on-privmsg)
+
+  (erc-scenarios-common-with-cleanup
+      ((erc-scenarios-common-dialog "base/assoc/queries")
+       (dumb-server (erc-d-run "localhost" t 'non-erc))
+       (port (process-contact dumb-server :service))
+       (expect (erc-d-t-make-expecter))
+       (erc-ensure-target-buffer-on-privmsg nil)
+       (erc-server-flood-penalty 0.1))
+
+    (ert-info ("Connect to foonet")
+      (with-current-buffer (erc :server "127.0.0.1"
+                                :port port
+                                :nick "tester"
+                                :user "tester"
+                                :full-name "tester")
+        (erc-scenarios-common-assert-initial-buf-name nil port)
+        (erc-d-t-wait-for 5 (eq erc-network 'foonet))
+        (funcall expect 15 "debug mode")))
+
+    (ert-info ("User dummy's greeting appears in server buffer")
+      (erc-d-t-wait-for -1 (get-buffer "dummy"))
+      (with-current-buffer "foonet"
+        (funcall expect 5 "hi")
+
+        (ert-info ("Option being nil doesn't queries we create")
+          (with-current-buffer (erc-cmd-QUERY "nitwit")
+            (should (equal (buffer-name) "nitwit"))
+            (erc-scenarios-common-say "hola")
+            (funcall expect 5 "ciao")))
+
+        (erc-scenarios-common-say "howdy")
+        (funcall expect 5 "no target")
+        (erc-cmd-MSG "dummy howdy")
+        (funcall expect 5 "bye")
+        (erc-cmd-QUIT "")))))
+
 ;;; erc-scenarios-base-attach.el ends here
-- 
2.39.2

>From 71f41ca13c61d8f8ba5f2d824e9cb1ae89e8e6ef Mon Sep 17 00:00:00 2001
From: "F. Jason Park" <[email-masked]>
Date: Thu, 13 Apr 2023 00:00:02 -0700
Subject: [PATCH 1/4] [5.6] Revive option erc-query-on-unjoined-chan-privmsg

* etc/ERC-NEWS: Mention reinstated option
`erc-query-on-unjoined-chan-privmsg'.
* lisp/erc/erc-backend.el (erc-server-PRIVMSG): Consider new option
`erc-receive-query-display-defer' and revived option
`erc-query-unjoined-chan-privmsg' when deciding whether to create a
new query buffer.  And only "open" a buffer for an unknown target when
`erc-query-on-unjoined-chan-privmsg' is non-nil.
* lisp/erc/erc.el (erc-query): Fix deprecation message.
(erc-auto-query, erc-receive-query-display): Swap alias and aliased
and add option to `erc-buffers' group.  Mention the nonstandard
meaning of nil for this option.
(erc-receive-query-display-defer): Add compatibility switch to access
legacy behavior for `erc-auto-query'.
(erc-query-on-unjoined-chan-privmsg): Revise doc string.
* test/lisp/erc/erc-scenarios-base-attach.el: New file.
* test/lisp/erc/resources/base/channel-buffer-revival/reattach.eld:
New file.  (Bug#62833)
---
 etc/ERC-NEWS                                  |  21 +-
 lisp/erc/erc-backend.el                       |  19 +-
 lisp/erc/erc.el                               |  63 ++++--
 test/lisp/erc/erc-scenarios-base-attach.el    | 191 ++++++++++++++++++
 .../base/channel-buffer-revival/reattach.eld  |  56 +++++
 5 files changed, 326 insertions(+), 24 deletions(-)
 create mode 100644 test/lisp/erc/erc-scenarios-base-attach.el
 create mode 100644 test/lisp/erc/resources/base/channel-buffer-revival/reattach.eld

diff --git a/etc/ERC-NEWS b/etc/ERC-NEWS
index 8f1b89f268b..2ee88a80351 100644
--- a/etc/ERC-NEWS
+++ b/etc/ERC-NEWS
@@ -45,9 +45,16 @@ security issue led to new ERC buffers being "buried" on creation.  On
 further reflection, this was judged to have been an overcorrection in
 the case of interactive invocations, hence the new option
 'erc-interactive-display', which is set to 'buffer' (i.e., "take me
-there") by default.  Accompanying this addition are "display"-suffixed
-aliases for related options 'erc-join-buffer' and 'erc-auto-query',
-which users have reported as being difficult to discover and remember.
+there") by default.
+
+Accompanying this addition are "display"-suffixed aliases for related
+options 'erc-join-buffer' and 'erc-auto-query', which users have
+reported as being difficult to discover and remember.  As for the
+latter option specifically (now known as 'erc-receive-query-display'),
+assigning it a nil value now tells ERC to use 'erc-join-buffer' in its
+place, much like with related buffer-display options, like
+'erc-interactive-display'.  The old nil behavior can still be gotten
+via a new compatibility flag, 'erc-receive-query-display-defer'.
 
 ** Setting a module's mode variable via Customize earns a warning.
 Trying and failing to activate a module via its minor mode's Custom
@@ -108,6 +115,14 @@ other than the symbol 'erc-button-buttonize-nicks' appearing in the
 "FORM" field (third element) of this entry are considered deprecated
 and will incur a warning.
 
+** The option 'erc-query-on-unjoined-chan-privmsg' has been restored.
+(Although it's now called 'erc-ensure-target-buffer-on-privmsg'.)
+In ERC 5.5, this option was removed from the default client code and
+thus prevented from influencing PRIVMSG handling because its precise
+purpose could not be determined with any confidence.  After some
+consideration, it's now been wired back in with a slightly revised
+role contingent on a few assumptions explained in its doc string.
+
 ** Miscellaneous UX changes.
 Some minor quality-of-life niceties have finally made their way to
 ERC.  For example, the function 'erc-echo-timestamp' is now
diff --git a/lisp/erc/erc-backend.el b/lisp/erc/erc-backend.el
index bdf4e2ddca2..98a1c117cfa 100644
--- a/lisp/erc/erc-backend.el
+++ b/lisp/erc/erc-backend.el
@@ -102,11 +102,11 @@
 (require 'erc-common)
 
 (defvar erc--target)
-(defvar erc-auto-query)
 (defvar erc-channel-list)
 (defvar erc-channel-users)
 (defvar erc-default-nicks)
 (defvar erc-default-recipients)
+(defvar erc-ensure-target-buffer-on-privmsg)
 (defvar erc-format-nick-function)
 (defvar erc-format-query-as-channel-p)
 (defvar erc-hide-prompt)
@@ -123,6 +123,8 @@ erc-nick
 (defvar erc-nick-change-attempt-count)
 (defvar erc-prompt-for-channel-key)
 (defvar erc-prompt-hidden)
+(defvar erc-receive-query-display)
+(defvar erc-receive-query-display-defer)
 (defvar erc-reuse-buffers)
 (defvar erc-verbose-server-ping)
 (defvar erc-whowas-on-nosuchnick)
@@ -1831,11 +1833,16 @@ define-erc-response-handler
         (unless (or buffer noticep (string-empty-p tgt) (eq ?$ (aref tgt 0))
                     (erc-is-message-ctcp-and-not-action-p msg))
           (if privp
-              (when erc-auto-query
-                (let ((erc-join-buffer erc-auto-query))
-                  (setq buffer (erc--open-target nick))))
-            ;; A channel buffer has been killed but is still joined
-            (setq buffer (erc--open-target tgt))))
+              (when-let ((erc-join-buffer
+                          (or (and (not erc-receive-query-display-defer)
+                                   erc-receive-query-display)
+                              (and erc-ensure-target-buffer-on-privmsg
+                                   (or erc-receive-query-display
+                                       erc-join-buffer)))))
+                (setq buffer (erc--open-target nick)))
+            ;; A channel buffer has been killed but is still joined.
+            (when erc-ensure-target-buffer-on-privmsg
+              (setq buffer (erc--open-target tgt)))))
         (when buffer
           (with-current-buffer buffer
             (when privp (erc--unhide-prompt))
diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el
index 284990e2d43..1e15f3d4280 100644
--- a/lisp/erc/erc.el
+++ b/lisp/erc/erc.el
@@ -3978,8 +3978,8 @@ erc-cmd-QUERY
   (unless user
       ;; currently broken, evil hack to display help anyway
                                         ;(erc-delete-query))))
-    (signal 'wrong-number-of-arguments ""))
-  (let ((erc-join-buffer erc-query-display))
+    (signal 'wrong-number-of-arguments '(erc-cmd-QUERY 0)))
+  (let ((erc-join-buffer erc-interactive-display))
     (erc-with-server-buffer
      (erc--open-target user))))
 
@@ -4722,23 +4722,30 @@ erc-query
   "Open a query buffer on TARGET using SERVER-BUFFER.
 To change how this query window is displayed, use `let' to bind
 `erc-join-buffer' before calling this."
-  (declare (obsolete "bind `erc-cmd-query' and call `erc-cmd-QUERY'" "29.1"))
+  (declare (obsolete "call `erc-open' in a live server buffer" "29.1"))
   (unless (buffer-live-p server-buffer)
     (error "Couldn't switch to server buffer"))
   (with-current-buffer server-buffer
     (erc--open-target target)))
 
-(defvaralias 'erc-receive-query-display 'erc-auto-query)
-(defcustom erc-auto-query 'window-noselect
+(defvaralias 'erc-auto-query 'erc-receive-query-display)
+(defcustom erc-receive-query-display 'window-noselect
   "If non-nil, create a query buffer each time you receive a private message.
 If the buffer doesn't already exist, it is created.
 
 This can be set to a symbol, to control how the new query window
 should appear.  The default behavior is to display the buffer in
-a new window, but not to select it.  See the documentation for
-`erc-join-buffer' for a description of the available choices."
+a new window but not to select it.  See the documentation for
+`erc-buffer-display' for a description of available values.
+
+Note that the legacy behavior of forgoing buffer creation
+entirely when this option is nil requires setting the
+compatibility flag `erc-receive-query-display-defer' to nil.  Use
+`erc-ensure-target-buffer-on-privmsg' to achieve the same effect."
+  :package-version '(ERC . "5.6")
+  :group 'erc-buffers
   :group 'erc-query
-  :type '(choice (const :tag "Don't create query window" nil)
+  :type '(choice (const :tag "Defer to value of `erc-buffer-display'" nil)
                  (const :tag "Split window and select" window)
                  (const :tag "Split window, don't select" window-noselect)
                  (const :tag "New frame" frame)
@@ -4746,15 +4753,41 @@ erc-auto-query
                  (const :tag "Use current buffer" buffer)
                  (const :tag "Use current buffer" t)))
 
-;; FIXME either retire this or put it to use after determining how
-;; it's meant to work.  Clearly, the doc string does not describe
-;; current behavior.  It's currently only used by the obsolete
-;; function `erc-auto-query'.
-(defcustom erc-query-on-unjoined-chan-privmsg t
-  "If non-nil create query buffer on receiving any PRIVMSG at all.
+(defcustom erc-receive-query-display-defer t
+  "How to interpret a null `erc-receive-query-display'.
+When this option is non-nil, ERC defers to `erc-buffer-display'
+upon seeing a nil value for `erc-receive-query-display', much
+like it does with other buffer-display options, like
+`erc-interactive-display'.  Otherwise, when this option is nil,
+ERC retains the legacy behavior of not creating a new query
+buffer."
+  :package-version '(ERC . "5.6")
+  :group 'erc-buffers
+  :group 'erc-query
+  :type 'boolean)
+
+(defvaralias 'erc-query-on-unjoined-chan-privmsg
+  'erc-ensure-target-buffer-on-privmsg)
+(defcustom erc-ensure-target-buffer-on-privmsg t
+  "When non-nil, create a target buffer upon receiving a PRIVMSG.
 This includes PRIVMSGs directed to channels.  If you are using an IRC
 bouncer, such as dircproxy, to keep a log of channels when you are
-disconnected, you should set this option to t."
+disconnected, you should set this option to t.
+
+For queries (direct messages), this option's non-nil meaning is
+straightforward: if a buffer doesn't exist for the sender, create
+one.  For channels, the use case is more niche and usually
+involves receiving playback (via commands like ZNC's
+\"PLAYBUFFER\") for channels to which your bouncer is joined but
+from which you've \"detached\".
+
+Note that this option was absent from ERC 5.5 because knowledge
+of its intended role was \"unavailable\" during a major
+refactoring involving buffer management.  The option has since
+been restored in ERC 5.6 but now also affects queries in the
+manner implied above, which was lost sometime before ERC 5.4."
+  :package-version '(ERC . "5.6") ; revived
+  :group 'erc-buffers
   :group 'erc-query
   :type 'boolean)
 
diff --git a/test/lisp/erc/erc-scenarios-base-attach.el b/test/lisp/erc/erc-scenarios-base-attach.el
new file mode 100644
index 00000000000..ccf5d1f9582
--- /dev/null
+++ b/test/lisp/erc/erc-scenarios-base-attach.el
@@ -0,0 +1,191 @@
+;;; erc-scenarios-base-attach.el --- Reattach scenarios -*- lexical-binding: t -*-
+
+;; Copyright (C) 2023 Free Software Foundation, Inc.
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; See also: `erc-scenarios-base-channel-buffer-revival'.
+;;
+;; ERC 5.5 silently dropped support for the ancient option
+;; `erc-query-on-unjoined-chan-privmsg' because the tangled logic in
+;; and around the function `erc-auto-query' made it difficult to
+;; divine its purpose.
+;;
+;; Based on the name, it was thought this option likely involved
+;; controlling the creation of query buffers for unsolicited messages
+;; from users with whom you don't share a common channel.  However,
+;; additional spelunking has recently revealed that it was instead
+;; meant to service a feature offered by most bouncers that sends
+;; PRIVMSGs directed at a channel you're no longer in and that you
+;; haven't received a(nother) JOIN message for.  IOW, this is meant to
+;; support the following sequence of events:
+;;
+;;   1. /detach #chan
+;;   2. kill buffer #chan or reconnect in new Emacs session
+;;   3. /playbuffer #chan
+;;
+;; Note that the above slash commands are bouncer-specific aliases.
+;;
+;; Interested users can find more info by looking at this change set
+;; from the ancient CVS repo:
+;;
+;;   Author:     Mario Lang <[email-masked]>
+;;   AuthorDate: Mon Nov 26 18:33:19 2001 +0000
+;;
+;;   * new function erc-BBDB-NICK to handle nickname anotation ...
+;;   * Applied antifuchs/mhp patches, the latest on erc-help, unmodified
+;;   * New variable: erc-reuse-buffers default to t.
+;;   * Modified erc-generate-new-buffer-name to use it. it checks if
+;;     server and port are the same, then one can assume thats the same
+;;     channel/query target again.
+
+;;; Code:
+
+(require 'ert-x)
+(eval-and-compile
+  (let ((load-path (cons (ert-resource-directory) load-path)))
+    (require 'erc-scenarios-common)))
+
+(ert-deftest erc-scenarios-base-attach--ensure-target-buffer--enabled ()
+  :tags '(:expensive-test)
+  (should erc-ensure-target-buffer-on-privmsg)
+
+  (erc-scenarios-common-with-cleanup
+      ((erc-scenarios-common-dialog "base/channel-buffer-revival")
+       (dumb-server (erc-d-run "localhost" t 'reattach))
+       (port (process-contact dumb-server :service))
+       (erc-server-flood-penalty 0.1)
+       (expect (erc-d-t-make-expecter)))
+
+    (ert-info ("Connect to foonet")
+      (with-current-buffer (erc :server "127.0.0.1"
+                                :port port
+                                :nick "tester"
+                                :password "tester@vanilla/foonet:changeme"
+                                :full-name "tester")
+        (should (string= (buffer-name) (format "127.0.0.1:%d" port)))))
+
+    (with-current-buffer (erc-d-t-wait-for 10 (get-buffer "foonet"))
+      (erc-cmd-MSG "*status playbuffer #chan"))
+
+    (ert-info ("Playback appears in buffer #chan")
+      (with-current-buffer (erc-d-t-wait-for 10 (get-buffer "#chan"))
+        (funcall expect 10 "Buffer Playback...")
+        (funcall expect 10 "Was I a child")
+        (funcall expect 10 "Thou counterfeit'st most lively")
+        (funcall expect 10 "Playback Complete")))
+
+    (with-current-buffer "foonet"
+      (erc-cmd-MSG "*status attach #chan"))
+
+    (ert-info ("Live output from #chan after more playback")
+      (with-current-buffer "#chan"
+        (funcall expect 10 "Buffer Playback...")
+        (funcall expect 10 "With what it loathes")
+        (funcall expect 10 "Not by his breath")
+        (funcall expect 10 "Playback Complete")
+        (funcall expect 10 "Ay, and the captain")
+        (erc-scenarios-common-say "bob: hi")
+        (funcall expect 10 "Pawn me to this")))))
+
+(ert-deftest erc-scenarios-base-attach--ensure-target-buffer--disabled ()
+  :tags '(:expensive-test)
+  (should erc-ensure-target-buffer-on-privmsg)
+
+  (erc-scenarios-common-with-cleanup
+      ((erc-scenarios-common-dialog "base/channel-buffer-revival")
+       (dumb-server (erc-d-run "localhost" t 'reattach))
+       (port (process-contact dumb-server :service))
+       (erc-server-flood-penalty 0.1)
+       (erc-ensure-target-buffer-on-privmsg nil) ; off
+       (expect (erc-d-t-make-expecter)))
+
+    (ert-info ("Connect to foonet")
+      (with-current-buffer (erc :server "127.0.0.1"
+                                :port port
+                                :nick "tester"
+                                :password "tester@vanilla/foonet:changeme"
+                                :full-name "tester")
+        (should (string= (buffer-name) (format "127.0.0.1:%d" port)))))
+
+    (with-current-buffer (erc-d-t-wait-for 10 (get-buffer "foonet"))
+      (erc-cmd-MSG "*status playbuffer #chan")
+      (ert-info ("Playback appears in buffer server buffer")
+        (erc-d-t-ensure-for -1 (not (get-buffer "#chan")))
+        (funcall expect 10 "Buffer Playback...")
+        (funcall expect 10 "Was I a child")
+        (funcall expect 10 "Thou counterfeit'st most lively")
+        (funcall expect 10 "Playback Complete"))
+      (should-not (get-buffer "#chan"))
+      (erc-cmd-MSG "*status attach #chan"))
+
+    (ert-info ("Buffer #chan joined")
+      (with-current-buffer (erc-d-t-wait-for 10 (get-buffer "#chan"))
+        (funcall expect 10 "Buffer Playback...")
+        (funcall expect 10 "With what it loathes")
+        (funcall expect 10 "Not by his breath")
+        (funcall expect 10 "Playback Complete")
+        (funcall expect 10 "Ay, and the captain")
+        (erc-scenarios-common-say "bob: hi")
+        (funcall expect 10 "Pawn me to this")))))
+
+
+;; We omit the `enabled' case for queries because it's the default for
+;; this option and already covered many times over by other tests in
+;; this directory.
+
+(ert-deftest erc-scenarios-base-attach--ensure-target-buffer--disabled-query ()
+  :tags '(:expensive-test)
+  (should erc-ensure-target-buffer-on-privmsg)
+
+  (erc-scenarios-common-with-cleanup
+      ((erc-scenarios-common-dialog "base/assoc/queries")
+       (dumb-server (erc-d-run "localhost" t 'non-erc))
+       (port (process-contact dumb-server :service))
+       (expect (erc-d-t-make-expecter))
+       (erc-ensure-target-buffer-on-privmsg nil)
+       (erc-server-flood-penalty 0.1))
+
+    (ert-info ("Connect to foonet")
+      (with-current-buffer (erc :server "127.0.0.1"
+                                :port port
+                                :nick "tester"
+                                :user "tester"
+                                :full-name "tester")
+        (erc-scenarios-common-assert-initial-buf-name nil port)
+        (erc-d-t-wait-for 5 (eq erc-network 'foonet))
+        (funcall expect 15 "debug mode")))
+
+    (ert-info ("User dummy's greeting appears in server buffer")
+      (erc-d-t-wait-for -1 (get-buffer "dummy"))
+      (with-current-buffer "foonet"
+        (funcall expect 5 "hi")
+
+        (ert-info ("Option being nil doesn't queries we create")
+          (with-current-buffer (erc-cmd-QUERY "nitwit")
+            (should (equal (buffer-name) "nitwit"))
+            (erc-scenarios-common-say "hola")
+            (funcall expect 5 "ciao")))
+
+        (erc-scenarios-common-say "howdy")
+        (funcall expect 5 "no target")
+        (erc-cmd-MSG "dummy howdy")
+        (funcall expect 5 "bye")
+        (erc-cmd-QUIT "")))))
+
+;;; erc-scenarios-base-attach.el ends here
diff --git a/test/lisp/erc/resources/base/channel-buffer-revival/reattach.eld b/test/lisp/erc/resources/base/channel-buffer-revival/reattach.eld
new file mode 100644
index 00000000000..c3791ac3d49
--- /dev/null
+++ b/test/lisp/erc/resources/base/channel-buffer-revival/reattach.eld
@@ -0,0 +1,56 @@
+;; -*- mode: lisp-data; -*-
+((pass 10 "PASS :tester@vanilla/foonet:changeme"))
+((nick 10 "NICK tester"))
+((user 10 "USER user 0 * :tester")
+ (0.00 ":irc.foonet.org 001 tester :Welcome to the foonet IRC Network tester")
+ (0.00 ":irc.foonet.org 002 tester :Your host is irc.foonet.org, running version ergo-v2.11.1")
+ (0.00 ":irc.foonet.org 003 tester :This server was created Thu, 13 Apr 2023 05:55:22 UTC")
+ (0.00 ":irc.foonet.org 004 tester irc.foonet.org ergo-v2.11.1 BERTZios CEIMRUabefhiklmnoqstuv Iabefhkloqv")
+ (0.00 ":irc.foonet.org 005 tester AWAYLEN=390 BOT=B CASEMAPPING=ascii CHANLIMIT=#:100 CHANMODES=Ibe,k,fl,CEMRUimnstu CHANNELLEN=64 CHANTYPES=# CHATHISTORY=1000 ELIST=U EXCEPTS EXTBAN=,m FORWARD=f INVEX :are supported by this server")
+ (0.00 ":irc.foonet.org 005 tester KICKLEN=390 MAXLIST=beI:60 MAXTARGETS=4 MODES MONITOR=100 NETWORK=foonet NICKLEN=32 PREFIX=(qaohv)~&@%+ STATUSMSG=~&@%+ TARGMAX=NAMES:1,LIST:1,KICK:,WHOIS:1,USERHOST:10,PRIVMSG:4,TAGMSG:4,NOTICE:4,MONITOR:100 TOPICLEN=390 UTF8ONLY WHOX :are supported by this server")
+ (0.00 ":irc.foonet.org 005 tester draft/CHATHISTORY=1000 :are supported by this server")
+ (0.00 ":irc.foonet.org 251 tester :There are 0 users and 3 invisible on 1 server(s)")
+ (0.01 ":irc.foonet.org 252 tester 0 :IRC Operators online")
+ (0.00 ":irc.foonet.org 254 tester 1 :channels formed")
+ (0.00 ":irc.foonet.org 255 tester :I have 3 clients and 0 servers")
+ (0.00 ":irc.foonet.org 265 tester 3 3 :Current local users 3, max 3")
+ (0.00 ":irc.foonet.org 266 tester 3 3 :Current global users 3, max 3")
+ (0.00 ":irc.foonet.org 422 tester :MOTD File is missing"))
+
+((mode 10 "MODE tester +i")
+ (0.01 ":irc.foonet.org 221 tester +Zi"))
+
+((privmsg-play 10 "PRIVMSG *status :playbuffer #chan")
+ (0.05 ":[email-masked] PRIVMSG #chan :Buffer Playback...")
+ (0.02 ":[email-masked] PRIVMSG #chan :[06:08:24] alice: Was I a child, to fear I know not what.")
+ (0.02 ":[email-masked] PRIVMSG #chan :[06:08:29] bob: My lord, I do confess the ring was hers.")
+ (0.01 ":[email-masked] PRIVMSG #chan :[06:08:40] alice: My sons would never so dishonour me.")
+ (0.01 ":[email-masked] PRIVMSG #chan :[06:09:54] bob: By the hand of a soldier, I will undertake it.")
+ (0.01 ":[email-masked] PRIVMSG #chan :[06:09:57] alice: Thou counterfeit'st most lively.")
+ (0.01 ":[email-masked] PRIVMSG #chan :Playback Complete."))
+
+((privmsg-attach 10 "PRIVMSG *status :attach #chan")
+ (0.01 ":[email-masked] JOIN #chan"))
+
+((mode-chan 10 "MODE #chan")
+ (0.01 ":irc.foonet.org 353 tester = #chan :@alice bob tester")
+ (0.00 ":irc.foonet.org 366 tester #chan :End of /NAMES list.")
+ (0.00 ":[email-masked] PRIVMSG #chan :Buffer Playback...")
+ (0.00 ":[email-masked] PRIVMSG #chan :[06:10:01] bob: With what it loathes for that which is away.")
+ (0.00 ":[email-masked] PRIVMSG #chan :[06:10:30] alice: Ties up my tongue, and will not let me speak.")
+ (0.00 ":[email-masked] PRIVMSG #chan :[06:11:26] bob: They say he is already in the forest of Arden, and a many merry men with him; and there they live like the old Robin Hood of England. They say many young gentlemen flock to him every day, and fleet the time carelessly, as they did in the golden world.")
+ (0.01 ":[email-masked] PRIVMSG #chan :[06:11:29] alice: Not by his breath that is more miserable.")
+ (0.00 ":[email-masked] PRIVMSG #chan :Playback Complete.")
+ (0.00 ":[email-masked] PRIVMSG tester :There was 1 channel matching [#chan]")
+ (0.03 ":[email-masked] PRIVMSG tester :Attached 1 channel")
+ (0.00 ":irc.foonet.org 324 tester #chan +Cnt")
+ (0.00 ":irc.foonet.org 329 tester #chan 1681365340")
+ (0.03 ":[email-masked] PRIVMSG #chan :bob: Five or six thousand horse, I said,I will say true,or thereabouts, set down, for I'll speak truth.")
+ (0.02 ":[email-masked] PRIVMSG #chan :alice: Riddling confession finds but riddling shrift.")
+ (0.04 ":[email-masked] PRIVMSG #chan :bob: Ay, and the captain of his horse, Count Rousillon."))
+
+((privmsg-bob 10 "PRIVMSG #chan :bob: hi")
+ (0.02 ":[email-masked] PRIVMSG #chan :alice: But thankful even for hate, that is meant love.")
+ (0.02 ":[email-masked] PRIVMSG #chan :tester: Come, come, elder brother, you are too young in this.")
+ (0.02 ":[email-masked] PRIVMSG #chan :bob: Sir, we have known together in Orleans.")
+ (0.05 ":[email-masked] PRIVMSG #chan :alice: Pawn me to this your honour, she is his."))
-- 
2.39.2

>From 186ee38aca3da61419a8e7daa9b089ec4db100d0 Mon Sep 17 00:00:00 2001
From: "F. Jason Park" <[email-masked]>
Date: Thu, 20 Apr 2023 19:23:54 -0700
Subject: [PATCH 2/4] [5.6] Move ERC's buffer-display tests to separate file

* test/lisp/erc/erc-scenarios-base-buffer-display.el: New file.
* test/lisp/erc/erc-scenarios-base-reconnect.el
(erc-scenarios-common--base-reconnect-options,
erc-scenarios-base-reconnect-options--buffer,
erc-scenarios-base-reconnect-options--default): Move to new file and
rename.  (Bug#62833)
---
 .../erc/erc-scenarios-base-buffer-display.el  | 121 ++++++++++++++++++
 test/lisp/erc/erc-scenarios-base-reconnect.el |  89 -------------
 2 files changed, 121 insertions(+), 89 deletions(-)
 create mode 100644 test/lisp/erc/erc-scenarios-base-buffer-display.el

diff --git a/test/lisp/erc/erc-scenarios-base-buffer-display.el b/test/lisp/erc/erc-scenarios-base-buffer-display.el
new file mode 100644
index 00000000000..d511c8ff738
--- /dev/null
+++ b/test/lisp/erc/erc-scenarios-base-buffer-display.el
@@ -0,0 +1,121 @@
+;;; erc-scenarios-base-buffer-display.el --- Buffer display scenarios -*- lexical-binding: t -*-
+
+;; Copyright (C) 2023 Free Software Foundation, Inc.
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Code:
+
+(require 'ert-x)
+(eval-and-compile
+  (let ((load-path (cons (ert-resource-directory) load-path)))
+    (require 'erc-scenarios-common)))
+
+(eval-when-compile (require 'erc-join))
+
+;; These first couple `erc-reconnect-display' tests used to live in
+;; erc-scenarios-base-reconnect but have since been renamed.
+
+(defun erc-scenarios-base-buffer-display--reconnect-common (test)
+  (erc-scenarios-common-with-cleanup
+      ((erc-scenarios-common-dialog "base/reconnect")
+       (dumb-server (erc-d-run "localhost" t 'options 'options-again))
+       (port (process-contact dumb-server :service))
+       (expect (erc-d-t-make-expecter))
+       (erc-server-flood-penalty 0.1)
+       (erc-server-auto-reconnect t)
+       erc-autojoin-channels-alist
+       erc-server-buffer)
+
+    (should (memq 'autojoin erc-modules))
+
+    (ert-info ("Connect to foonet")
+      (setq erc-server-buffer (erc :server "127.0.0.1"
+                                   :port port
+                                   :nick "tester"
+                                   :password "changeme"
+                                   :full-name "tester"))
+      (with-current-buffer erc-server-buffer
+        (should (string= (buffer-name) (format "127.0.0.1:%d" port)))
+        (funcall expect 10 "debug mode")))
+
+    (ert-info ("Wait for some output in channels")
+      (with-current-buffer (erc-d-t-wait-for 10 (get-buffer "#chan"))
+        (funcall expect 10 "welcome")))
+
+    (ert-info ("Server buffer shows connection failed")
+      (with-current-buffer erc-server-buffer
+        (funcall expect 10 "Connection failed!  Re-establishing")))
+
+    (should (equal erc-autojoin-channels-alist '((FooNet "#chan"))))
+
+    (funcall test)
+
+    ;; A manual /JOIN command tells ERC we're done auto-reconnecting
+    (with-current-buffer "FooNet" (erc-cmd-JOIN "#spam"))
+
+    (erc-d-t-ensure-for 1 "Newly joined chan ignores `erc-reconnect-display'"
+      (not (eq (window-buffer) (get-buffer "#spam"))))
+
+    (ert-info ("Wait for auto reconnect")
+      (with-current-buffer erc-server-buffer
+        (funcall expect 10 "still in debug mode")))
+
+    (ert-info ("Wait for activity to recommence in channels")
+      (with-current-buffer (erc-d-t-wait-for 10 (get-buffer "#chan"))
+        (funcall expect 10 "forest of Arden"))
+      (with-current-buffer (erc-d-t-wait-for 10 (get-buffer "#spam"))
+        (funcall expect 10 "her elves come here anon")))))
+
+(ert-deftest erc-scenarios-base-reconnect-options--buffer ()
+  :tags '(:expensive-test)
+  (should (eq erc-join-buffer 'bury))
+  (should-not erc-reconnect-display)
+
+  ;; FooNet (the server buffer) is not switched to because it's
+  ;; already current (but not shown) when `erc-open' is called.  See
+  ;; related conditional guard towards the end of that function.
+
+  (let ((erc-reconnect-display 'buffer))
+    (erc-scenarios-base-buffer-display--reconnect-common
+     (lambda ()
+       (pop-to-buffer-same-window "*Messages*")
+
+       (erc-d-t-ensure-for 1 "Server buffer not shown"
+         (not (eq (window-buffer) (get-buffer "FooNet"))))
+
+       (erc-d-t-wait-for 5 "Channel #chan shown when autojoined"
+         (eq (window-buffer) (get-buffer "#chan")))))))
+
+(ert-deftest erc-scenarios-base-reconnect-options--default ()
+  :tags '(:expensive-test)
+  (should (eq erc-join-buffer 'bury))
+  (should-not erc-reconnect-display)
+
+  (erc-scenarios-base-buffer-display--reconnect-common
+
+   (lambda ()
+     (pop-to-buffer-same-window "*Messages*")
+
+     (erc-d-t-ensure-for 1 "Server buffer not shown"
+       (not (eq (window-buffer) (get-buffer "FooNet"))))
+
+     (erc-d-t-ensure-for 3 "Channel #chan not shown"
+       (not (eq (window-buffer) (get-buffer "#chan"))))
+
+     (should (eq (window-buffer) (messages-buffer))))))
+
+;;; erc-scenarios-base-buffer-display.el ends here
diff --git a/test/lisp/erc/erc-scenarios-base-reconnect.el b/test/lisp/erc/erc-scenarios-base-reconnect.el
index 5b4dc549042..7bd16d1ed14 100644
--- a/test/lisp/erc/erc-scenarios-base-reconnect.el
+++ b/test/lisp/erc/erc-scenarios-base-reconnect.el
@@ -65,95 +65,6 @@ erc-scenarios-base-reconnect-timer
       (should (equal (list (get-buffer (format "127.0.0.1:%d" port)))
                      (erc-scenarios-common-buflist "127.0.0.1"))))))
 
-(defun erc-scenarios-common--base-reconnect-options (test)
-  (erc-scenarios-common-with-cleanup
-      ((erc-scenarios-common-dialog "base/reconnect")
-       (dumb-server (erc-d-run "localhost" t 'options 'options-again))
-       (port (process-contact dumb-server :service))
-       (expect (erc-d-t-make-expecter))
-       (erc-server-flood-penalty 0.1)
-       (erc-server-auto-reconnect t)
-       erc-autojoin-channels-alist
-       erc-server-buffer)
-
-    (should (memq 'autojoin erc-modules))
-
-    (ert-info ("Connect to foonet")
-      (setq erc-server-buffer (erc :server "127.0.0.1"
-                                   :port port
-                                   :nick "tester"
-                                   :password "changeme"
-                                   :full-name "tester"))
-      (with-current-buffer erc-server-buffer
-        (should (string= (buffer-name) (format "127.0.0.1:%d" port)))
-        (funcall expect 10 "debug mode")))
-
-    (ert-info ("Wait for some output in channels")
-      (with-current-buffer (erc-d-t-wait-for 10 (get-buffer "#chan"))
-        (funcall expect 10 "welcome")))
-
-    (ert-info ("Server buffer shows connection failed")
-      (with-current-buffer erc-server-buffer
-        (funcall expect 10 "Connection failed!  Re-establishing")))
-
-    (should (equal erc-autojoin-channels-alist '((FooNet "#chan"))))
-
-    (funcall test)
-
-    ;; A manual /JOIN command tells ERC we're done auto-reconnecting
-    (with-current-buffer "FooNet" (erc-cmd-JOIN "#spam"))
-
-    (erc-d-t-ensure-for 1 "Newly joined chan ignores `erc-reconnect-display'"
-      (not (eq (window-buffer) (get-buffer "#spam"))))
-
-    (ert-info ("Wait for auto reconnect")
-      (with-current-buffer erc-server-buffer
-        (funcall expect 10 "still in debug mode")))
-
-    (ert-info ("Wait for activity to recommence in channels")
-      (with-current-buffer (erc-d-t-wait-for 10 (get-buffer "#chan"))
-        (funcall expect 10 "forest of Arden"))
-      (with-current-buffer (erc-d-t-wait-for 10 (get-buffer "#spam"))
-        (funcall expect 10 "her elves come here anon")))))
-
-(ert-deftest erc-scenarios-base-reconnect-options--buffer ()
-  :tags '(:expensive-test)
-  (should (eq erc-join-buffer 'bury))
-  (should-not erc-reconnect-display)
-
-  ;; FooNet (the server buffer) is not switched to because it's
-  ;; already current (but not shown) when `erc-open' is called.  See
-  ;; related conditional guard towards the end of that function.
-
-  (let ((erc-reconnect-display 'buffer))
-    (erc-scenarios-common--base-reconnect-options
-     (lambda ()
-       (pop-to-buffer-same-window "*Messages*")
-
-       (erc-d-t-ensure-for 1 "Server buffer not shown"
-         (not (eq (window-buffer) (get-buffer "FooNet"))))
-
-       (erc-d-t-wait-for 5 "Channel #chan shown when autojoined"
-         (eq (window-buffer) (get-buffer "#chan")))))))
-
-(ert-deftest erc-scenarios-base-reconnect-options--default ()
-  :tags '(:expensive-test)
-  (should (eq erc-join-buffer 'bury))
-  (should-not erc-reconnect-display)
-
-  (erc-scenarios-common--base-reconnect-options
-
-   (lambda ()
-     (pop-to-buffer-same-window "*Messages*")
-
-     (erc-d-t-ensure-for 1 "Server buffer not shown"
-       (not (eq (window-buffer) (get-buffer "FooNet"))))
-
-     (erc-d-t-ensure-for 3 "Channel #chan not shown"
-       (not (eq (window-buffer) (get-buffer "#chan"))))
-
-     (eq (window-buffer) (messages-buffer)))))
-
 ;; Upon reconnecting, playback for channel and target buffers is
 ;; routed correctly.  Autojoin is irrelevant here, but for the
 ;; skeptical, see `erc-scenarios-common--join-network-id', which
-- 
2.39.2

>From 6995cc5aab16280d0b4a00ec60277531d81591dc Mon Sep 17 00:00:00 2001
From: "F. Jason Park" <[email-masked]>
Date: Mon, 10 Apr 2023 17:58:05 -0700
Subject: [PATCH 3/4] [5.6] Extend erc-interactive-display to cover /JOINs

* lisp/erc/erc.el (erc-display): Mention that buffer-related display
options live in the customization group `erc-buffers'.
(erc-buffer-display, erc-join-buffer): Swap alias and aliased so that
the favored name, `erc-buffer-display', appears in the definition and
in the Customize menu.  Also note related buffer-display options in
the doc string.
(erc-query-display, erc-interactive-display): Make the former an alias
for the latter because their roles were functionally redundant and
thus confusing.  Inherit the default value from `erc-query-display'
because users are more familiar with the pop-up window behavior than a
single-window replacement.
(erc-reconnect-display): Use preferred name for cross-referencing
fallback option `erc-buffer-display' in doc string and explain how
/reconnect handling differs.
(erc--called-as-input-p): New variable for "slash" commands, like
`erc-cmd-FOO', to detect whether they're being called "interactively"
as a result of input given at ERC's prompt.
(erc-process-input-line): Bind `erc--called-as-input-p' when running
slash commands.
(erc-cmd-JOIN): When called interactively, schedule a callback to wrap
the response handler and control how new buffers are thus displayed.
(erc-cmd-QUERY): Use preferred alias for `erc-query-display'.
* test/lisp/erc/erc-scenarios-base-buffer-display.el:
(erc-scenarios-base-buffer-display--interactive-default): New test.
* test/lisp/erc/erc-tests.el (erc-process-input-line,
erc-select-read-args, erc-tls, erc--interactive): Change expected
default value of `erc-interactive-display' from `buffer' to
`window'.  (Bug#62833)
---
 etc/ERC-NEWS                                  | 17 ++--
 lisp/erc/erc.el                               | 82 +++++++++++--------
 .../erc/erc-scenarios-base-buffer-display.el  | 37 +++++++++
 test/lisp/erc/erc-tests.el                    | 13 +--
 4 files changed, 100 insertions(+), 49 deletions(-)

diff --git a/etc/ERC-NEWS b/etc/ERC-NEWS
index 2ee88a80351..f1fac8e5baa 100644
--- a/etc/ERC-NEWS
+++ b/etc/ERC-NEWS
@@ -37,15 +37,18 @@ decade overdue, this is no longer the case.  Other UX improvements in
 this area aim to make the process of connecting interactively slightly
 more streamlined and less repetitive, even for veteran users.
 
-** New buffer-display option 'erc-interactive-display'.
+** Revised buffer-display handling for interactive commands.
 A point of friction for new users and one only just introduced with
 ERC 5.5 has been the lack of visual feedback when first connecting via
-M-x erc.  As explained below in the news for 5.5, the discovery of a
-security issue led to new ERC buffers being "buried" on creation.  On
-further reflection, this was judged to have been an overcorrection in
-the case of interactive invocations, hence the new option
-'erc-interactive-display', which is set to 'buffer' (i.e., "take me
-there") by default.
+M-x erc or when issuing a "/JOIN" command at the prompt.  As explained
+below, in the news for 5.5, the discovery of a security issue led to
+most new ERC buffers being "buried" on creation.  On further
+reflection, this was judged to have been an overcorrection in the case
+of interactive invocations, hence the borrowing of an old option,
+'erc-query-display', and the bestowing of a new alias,
+'erc-interactive-display', which better describes its expanded role as
+a more general buffer-display knob for interactive commands ("/QUERY"
+still among them).
 
 Accompanying this addition are "display"-suffixed aliases for related
 options 'erc-join-buffer' and 'erc-auto-query', which users have
diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el
index 1e15f3d4280..aee4f983beb 100644
--- a/lisp/erc/erc.el
+++ b/lisp/erc/erc.el
@@ -98,7 +98,9 @@ erc-buffers
   :group 'erc)
 
 (defgroup erc-display nil
-  "Settings for how various things are displayed."
+  "Settings controlling how various things are displayed.
+See the customization group `erc-buffers' for display options
+concerning buffers."
   :group 'erc)
 
 (defgroup erc-mode-line-and-header nil
@@ -1507,9 +1509,9 @@ erc-default-port-tls
   "IRC port to use for encrypted connections if it cannot be \
 detected otherwise.")
 
-(defvaralias 'erc-buffer-display 'erc-join-buffer)
-(defcustom erc-join-buffer 'bury
-  "Determines how to display a newly created IRC buffer.
+(defvaralias 'erc-join-buffer 'erc-buffer-display)
+(defcustom erc-buffer-display 'bury
+  "How to display a newly created or reassociated ERC buffer.
 
 The available choices are:
 
@@ -1518,7 +1520,9 @@ erc-join-buffer
   `frame'           - in another frame,
   `bury'            - bury it in a new buffer,
   `buffer'          - in place of the current buffer,
-  any other value  - in place of the current buffer."
+
+See related options `erc-interactive-display',
+`erc-reconnect-display', and `erc-receive-query-display'."
   :package-version '(ERC . "5.5")
   :group 'erc-buffers
   :type '(choice (const :tag "Split window and select" window)
@@ -1528,13 +1532,17 @@ erc-join-buffer
                  (const :tag "Use current buffer" buffer)
                  (const :tag "Use current buffer" t)))
 
-(defcustom erc-interactive-display 'buffer
-  "How and whether to display server buffers for M-x erc.
-See `erc-buffer-display' and friends for a description of
-possible values."
+(defvaralias 'erc-query-display 'erc-interactive-display)
+(defcustom erc-interactive-display 'window
+  "How to display buffers as a result of user interaction.
+This affects commands like /QUERY and /JOIN when issued
+interactively at the prompt.  It does not apply when calling a
+handler for such a command, like `erc-cmd-JOIN', from lisp code.
+See `erc-buffer-display' for a full description of available
+values."
   :package-version '(ERC . "5.6") ; FIXME sync on release
   :group 'erc-buffers
-  :type '(choice (const :tag "Use value of `erc-join-buffer'" nil)
+  :type '(choice (const :tag "Use value of `erc-buffer-display'" nil)
                  (const :tag "Split window and select" window)
                  (const :tag "Split window, don't select" window-noselect)
                  (const :tag "New frame" frame)
@@ -1542,15 +1550,14 @@ erc-interactive-display
                  (const :tag "Use current buffer" buffer)))
 
 (defcustom erc-reconnect-display nil
-  "How (and whether) to display a channel buffer upon reconnecting.
-
-This only affects automatic reconnections and is ignored when
-issuing a /reconnect command or reinvoking `erc-tls' with the
-same args (assuming success, of course).  See `erc-join-buffer'
-for a description of possible values."
+  "How and whether to display a channel buffer when auto-reconnecting.
+This only affects automatic reconnections and is ignored, like
+all other buffer-display options, when issuing a /RECONNECT or
+successfully reinvoking `erc-tls' with similar arguments.  See
+`erc-buffer-display' for a description of possible values."
   :package-version '(ERC . "5.5")
   :group 'erc-buffers
-  :type '(choice (const :tag "Use value of `erc-join-buffer'" nil)
+  :type '(choice (const :tag "Use value of `erc-buffer-display'" nil)
                  (const :tag "Split window and select" window)
                  (const :tag "Split window, don't select" window-noselect)
                  (const :tag "New frame" frame)
@@ -3057,6 +3064,10 @@ erc-message-type-member
   (let ((prop-val (erc-get-parsed-vector position)))
     (and prop-val (member (erc-response.command prop-val) list))))
 
+(defvar erc--called-as-input-p nil
+  "Non-nil when a user types a \"/slash\" command.
+Remains bound until `erc-cmd-SLASH' returns.")
+
 (defvar-local erc-send-input-line-function 'erc-send-input-line
   "Function for sending lines lacking a leading user command.
 When a line typed into a buffer contains an explicit command, like /msg,
@@ -3110,7 +3121,8 @@ erc-process-input-line
     (if (and command-list
              (not no-command))
         (let* ((cmd  (nth 0 command-list))
-               (args (nth 1 command-list)))
+               (args (nth 1 command-list))
+               (erc--called-as-input-p t))
           (condition-case nil
               (if (listp args)
                   (apply cmd args)
@@ -3584,6 +3596,21 @@ erc-cmd-JOIN
                    (erc-get-channel-user (erc-current-nick)))))
           (switch-to-buffer existing)
         (setq erc--server-last-reconnect-count 0)
+        (when-let* ; bind `erc-join-buffer' when /JOIN issued
+            ((erc--called-as-input-p)
+             (fn (lambda (proc parsed)
+                   (when-let* ; `fn' wrapper already removed from hook
+                       (((equal (car (erc-response.command-args parsed))
+                                channel))
+                        (sn (erc-extract-nick (erc-response.sender parsed)))
+                        ((erc-nick-equal-p sn (erc-current-nick)))
+                        (erc-join-buffer (or erc-interactive-display
+                                             erc-join-buffer)))
+                     (run-hook-with-args-until-success
+                      'erc-server-JOIN-functions proc parsed)
+                     t))))
+          (erc-with-server-buffer
+            (erc-once-with-server-event "JOIN" fn)))
         (erc-server-join-channel nil chnl key))))
   t)
 
@@ -3947,27 +3974,10 @@ erc-cmd-QUOTE
    (t nil)))
 (put 'erc-cmd-QUOTE 'do-not-parse-args t)
 
-(defcustom erc-query-display 'window
-  "How to display query buffers when using the /QUERY command to talk to someone.
-
-The default behavior is to display the message in a new window
-and bring it to the front.  See the documentation for
-`erc-join-buffer' for a description of the available choices.
-
-See also `erc-auto-query' to decide how private messages from
-other people should be displayed."
-  :group 'erc-query
-  :type '(choice (const :tag "Split window and select" window)
-                 (const :tag "Split window, don't select" window-noselect)
-                 (const :tag "New frame" frame)
-                 (const :tag "Bury in new buffer" bury)
-                 (const :tag "Use current buffer" buffer)
-                 (const :tag "Use current buffer" t)))
-
 (defun erc-cmd-QUERY (&optional user)
   "Open a query with USER.
 How the query is displayed (in a new window, frame, etc.) depends
-on the value of `erc-query-display'."
+on the value of `erc-interactive-display'."
   ;; FIXME: The doc string used to say at the end:
   ;; "If USER is omitted, close the current query buffer if one exists
   ;; - except this is broken now ;-)"
diff --git a/test/lisp/erc/erc-scenarios-base-buffer-display.el b/test/lisp/erc/erc-scenarios-base-buffer-display.el
index d511c8ff738..3ed7a83653e 100644
--- a/test/lisp/erc/erc-scenarios-base-buffer-display.el
+++ b/test/lisp/erc/erc-scenarios-base-buffer-display.el
@@ -118,4 +118,41 @@ erc-scenarios-base-reconnect-options--default
 
      (should (eq (window-buffer) (messages-buffer))))))
 
+
+;; This shows that the option `erc-interactive-display' overrides
+;; `erc-join-buffer' during cold opens and interactive /JOINs.
+
+(ert-deftest erc-scenarios-base-buffer-display--interactive-default ()
+  :tags '(:expensive-test)
+  (should (eq erc-join-buffer 'bury))
+  (should (eq erc-interactive-display 'window))
+
+  (erc-scenarios-common-with-cleanup
+      ((erc-scenarios-common-dialog "join/legacy")
+       (dumb-server (erc-d-run "localhost" t 'foonet))
+       (port (process-contact dumb-server :service))
+       (url (format "tester:[email-masked]:%d\r\r" port))
+       (expect (erc-d-t-make-expecter))
+       (erc-server-flood-penalty 0.1)
+       (erc-server-auto-reconnect t)
+       (erc-user-full-name "tester"))
+
+    (ert-info ("Connect to foonet")
+      (with-current-buffer (let (inhibit-interaction)
+                             (ert-simulate-keys url
+                               (call-interactively #'erc)))
+        (should (string= (buffer-name) (format "127.0.0.1:%d" port)))
+
+        (erc-d-t-wait-for 10 "Server buffer shown"
+          (eq (window-buffer) (current-buffer)))
+        (funcall expect 10 "debug mode")
+        (erc-scenarios-common-say "/JOIN #chan")))
+
+    (ert-info ("Wait for output in #chan")
+      (with-current-buffer (erc-d-t-wait-for 10 (get-buffer "#chan"))
+        (funcall expect 10 "welcome")
+        (erc-d-t-ensure-for 3 "Channel #chan shown"
+          (eq (window-buffer) (current-buffer)))
+        (funcall expect 10 "be prosperous")))))
+
 ;;; erc-scenarios-base-buffer-display.el ends here
diff --git a/test/lisp/erc/erc-tests.el b/test/lisp/erc/erc-tests.el
index 29bda7e742d..88b9babf206 100644
--- a/test/lisp/erc/erc-tests.el
+++ b/test/lisp/erc/erc-tests.el
@@ -1292,6 +1292,7 @@ erc-process-input-line
       (cl-letf (((symbol-function 'erc-cmd-MSG)
                  (lambda (line)
                    (push line calls)
+                   (should erc--called-as-input-p)
                    (funcall orig-erc-cmd-MSG line)))
                 ((symbol-function 'erc-server-buffer)
                  (lambda () (current-buffer)))
@@ -1469,7 +1470,7 @@ erc-select-read-args
                          :nick (user-login-name)
                          '&interactive-env
                          '((erc-server-connect-function . erc-open-tls-stream)
-                           (erc-join-buffer . buffer))))))
+                           (erc-join-buffer . window))))))
 
   (ert-info ("Switches to TLS when port matches default TLS port")
     (should (equal (ert-simulate-keys "irc.gnu.org\r6697\r\r\r"
@@ -1479,7 +1480,7 @@ erc-select-read-args
                          :nick (user-login-name)
                          '&interactive-env
                          '((erc-server-connect-function . erc-open-tls-stream)
-                           (erc-join-buffer . buffer))))))
+                           (erc-join-buffer . window))))))
 
   (ert-info ("Switches to TLS when URL is ircs://")
     (should (equal (ert-simulate-keys "ircs://irc.gnu.org\r\r\r\r"
@@ -1489,7 +1490,7 @@ erc-select-read-args
                          :nick (user-login-name)
                          '&interactive-env
                          '((erc-server-connect-function . erc-open-tls-stream)
-                           (erc-join-buffer . buffer))))))
+                           (erc-join-buffer . window))))))
 
   (setq-local erc-interactive-display nil) ; cheat to save space
 
@@ -1625,7 +1626,7 @@ erc-tls
                        '("localhost" 6667 "nick" "unknown" t "sesame"
                          nil nil nil nil "user" nil)))
         (should (equal (pop env)
-                       '((erc-join-buffer buffer)
+                       '((erc-join-buffer window)
                          (erc-server-connect-function erc-open-tls-stream)))))
 
       (ert-info ("Custom connect function")
@@ -1686,7 +1687,7 @@ erc--interactive
                        '("irc.libera.chat" 6697 "tester" "unknown" t nil
                          nil nil nil nil "user" nil)))
         (should (equal (pop env)
-                       '((erc-join-buffer buffer) (erc-server-connect-function
+                       '((erc-join-buffer window) (erc-server-connect-function
                                                    erc-open-tls-stream)))))
 
       (ert-info ("Nick supplied, decline TLS upgrade")
@@ -1696,7 +1697,7 @@ erc--interactive
                        '("irc.libera.chat" 6667 "dummy" "unknown" t nil
                          nil nil nil nil "user" nil)))
         (should (equal (pop env)
-                       '((erc-join-buffer buffer)
+                       '((erc-join-buffer window)
                          (erc-server-connect-function
                           erc-open-network-stream))))))))
 
-- 
2.39.2

>From dd9f78ea159958ccef4eabf3f782d4f29d4d24e7 Mon Sep 17 00:00:00 2001
From: "F. Jason Park" <[email-masked]>
Date: Thu, 20 Apr 2023 19:20:59 -0700
Subject: [PATCH 4/4] [5.6] Ignore erc-reconnect-display after a timeout

* lisp/erc/erc-backend.el (erc--server-reconnect-display-timer): New
variable to store active timer that, upon firing, zeroes out
`erc--server-last-reconnect-count'.
(erc--server-last-reconnect-on-disconnect): New function to run on
`erc-disconnected-hook'.
(erc--server-last-reconnect-display-reset): New function that ensures
the reconnect-display window is closed.
* lisp/erc/erc.el (erc-reconnect-display-timeout): New option to
control how long `erc-reconnect-display' affects the displaying of new
or reassociated buffers following an automatic reconnection.
(erc-process-input-line): Ensure user input marks the end of the
reconnect-display window.
(erc-cmd-JOIN): Don't bother resetting
`erc--server-last-reconnect-count' because it's now handled by the
caller, `erc-process-input-line'.
(erc-connection-established): Schedule timer and register hook to
reset last-reconnect count and close the reconnect-display window.
* test/lisp/erc/erc-scenarios-base-buffer-display.el:
(erc-scenarios-base-buffer-display--reconnect-common): Add new args to
test fixture.
(erc-scenarios-base-reconnect-options--buffer,
erc-scenarios-base-buffer-display--defwin-recbury-intbuf): Rename
former to latter and rework to better reflect realistic settings
for the relevant display options.
(erc-scenarios-base-reconnect-options--default,
erc-scenarios-base-buffer-display--defwino-recbury-intbuf): Rename
former to latter and rework to be more realistic.
(erc-scenarios-base-buffer-display--count-reset-timeout): New
test for new option `erc-reconnect-display-timeout'.  (Bug#62833)
---
 lisp/erc/erc-backend.el                       |  22 +++
 lisp/erc/erc.el                               |  15 +-
 .../erc/erc-scenarios-base-buffer-display.el  | 168 +++++++++++++-----
 3 files changed, 159 insertions(+), 46 deletions(-)

diff --git a/lisp/erc/erc-backend.el b/lisp/erc/erc-backend.el
index 98a1c117cfa..d14640e798d 100644
--- a/lisp/erc/erc-backend.el
+++ b/lisp/erc/erc-backend.el
@@ -299,6 +299,12 @@ erc-server-connected
 (defvar-local erc-server-reconnect-count 0
   "Number of times we have failed to reconnect to the current server.")
 
+(defvar-local erc--server-reconnect-display-timer nil
+  "Timer that resets `erc--server-last-reconnect-count' to zero.
+Becomes non-nil in all server buffers when an IRC connection is
+first \"established\" and carries out its duties
+`erc-reconnect-display-timeout' seconds later.")
+
 (defvar-local erc--server-last-reconnect-count 0
   "Snapshot of reconnect count when the connection was established.")
 
@@ -903,6 +909,22 @@ erc-server-reconnect-p
         erc-server-reconnecting)
       (erc--server-reconnect-p event)))
 
+(defun erc--server-last-reconnect-on-disconnect (&rest _)
+  (remove-hook 'erc-disconnected-hook
+               #'erc--server-last-reconnect-on-disconnect t)
+  (erc--server-last-reconnect-display-reset (current-buffer)))
+
+(defun erc--server-last-reconnect-display-reset (buffer)
+  "Deactivate `erc-reconnect-display'."
+  (when (buffer-live-p buffer)
+    (with-current-buffer buffer
+      (when erc--server-reconnect-display-timer
+        (cancel-timer erc--server-reconnect-display-timer)
+        (remove-hook 'erc-disconnected-hook
+                     #'erc--server-last-reconnect-display-reset t)
+        (setq erc--server-reconnect-display-timer nil
+              erc--server-last-reconnect-count 0)))))
+
 (defconst erc--mode-line-process-reconnecting
   '(:eval (erc-with-server-buffer
             (and erc--server-reconnect-timer
diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el
index aee4f983beb..847e27904b1 100644
--- a/lisp/erc/erc.el
+++ b/lisp/erc/erc.el
@@ -1564,6 +1564,13 @@ erc-reconnect-display
                  (const :tag "Bury in new buffer" bury)
                  (const :tag "Use current buffer" buffer)))
 
+(defcustom erc-reconnect-display-timeout 10
+  "Duration `erc-reconnect-display' remains active.
+The countdown starts on MOTD and is canceled early by any
+\"slash\" command."
+  :type 'integer
+  :group 'erc-buffers)
+
 (defcustom erc-frame-alist nil
   "Alist of frame parameters for creating erc frames.
 A value of nil means to use `default-frame-alist'."
@@ -3123,6 +3130,7 @@ erc-process-input-line
         (let* ((cmd  (nth 0 command-list))
                (args (nth 1 command-list))
                (erc--called-as-input-p t))
+          (erc--server-last-reconnect-display-reset (erc-server-buffer))
           (condition-case nil
               (if (listp args)
                   (apply cmd args)
@@ -3595,7 +3603,6 @@ erc-cmd-JOIN
                 ((with-current-buffer existing
                    (erc-get-channel-user (erc-current-nick)))))
           (switch-to-buffer existing)
-        (setq erc--server-last-reconnect-count 0)
         (when-let* ; bind `erc-join-buffer' when /JOIN issued
             ((erc--called-as-input-p)
              (fn (lambda (proc parsed)
@@ -5204,6 +5211,12 @@ erc-connection-established
         (setq erc-server-connected t)
         (setq erc--server-last-reconnect-count erc-server-reconnect-count
               erc-server-reconnect-count 0)
+        (setq erc--server-reconnect-display-timer
+              (run-at-time erc-reconnect-display-timeout nil
+                           #'erc--server-last-reconnect-display-reset
+                           (current-buffer)))
+        (add-hook 'erc-disconnected-hook
+                  #'erc--server-last-reconnect-on-disconnect nil t)
         (erc-update-mode-line)
         (erc-set-initial-user-mode nick buffer)
         (erc-server-setup-periodical-ping buffer)
diff --git a/test/lisp/erc/erc-scenarios-base-buffer-display.el b/test/lisp/erc/erc-scenarios-base-buffer-display.el
index 3ed7a83653e..53a3d7e8ef7 100644
--- a/test/lisp/erc/erc-scenarios-base-buffer-display.el
+++ b/test/lisp/erc/erc-scenarios-base-buffer-display.el
@@ -29,7 +29,8 @@
 ;; These first couple `erc-reconnect-display' tests used to live in
 ;; erc-scenarios-base-reconnect but have since been renamed.
 
-(defun erc-scenarios-base-buffer-display--reconnect-common (test)
+(defun erc-scenarios-base-buffer-display--reconnect-common
+    (assert-server assert-chan assert-rest)
   (erc-scenarios-common-with-cleanup
       ((erc-scenarios-common-dialog "base/reconnect")
        (dumb-server (erc-d-run "localhost" t 'options 'options-again))
@@ -37,87 +38,164 @@ erc-scenarios-base-buffer-display--reconnect-common
        (expect (erc-d-t-make-expecter))
        (erc-server-flood-penalty 0.1)
        (erc-server-auto-reconnect t)
-       erc-autojoin-channels-alist
-       erc-server-buffer)
+       erc-autojoin-channels-alist)
 
     (should (memq 'autojoin erc-modules))
 
     (ert-info ("Connect to foonet")
-      (setq erc-server-buffer (erc :server "127.0.0.1"
-                                   :port port
-                                   :nick "tester"
-                                   :password "changeme"
-                                   :full-name "tester"))
-      (with-current-buffer erc-server-buffer
+      (with-current-buffer (erc :server "127.0.0.1"
+                                :port port
+                                :nick "tester"
+                                :password "changeme"
+                                :full-name "tester")
+        (funcall assert-server expect)
         (should (string= (buffer-name) (format "127.0.0.1:%d" port)))
         (funcall expect 10 "debug mode")))
 
     (ert-info ("Wait for some output in channels")
       (with-current-buffer (erc-d-t-wait-for 10 (get-buffer "#chan"))
+        (funcall assert-chan expect)
         (funcall expect 10 "welcome")))
 
     (ert-info ("Server buffer shows connection failed")
-      (with-current-buffer erc-server-buffer
+      (with-current-buffer "FooNet"
         (funcall expect 10 "Connection failed!  Re-establishing")))
 
     (should (equal erc-autojoin-channels-alist '((FooNet "#chan"))))
-
-    (funcall test)
-
-    ;; A manual /JOIN command tells ERC we're done auto-reconnecting
-    (with-current-buffer "FooNet" (erc-cmd-JOIN "#spam"))
-
-    (erc-d-t-ensure-for 1 "Newly joined chan ignores `erc-reconnect-display'"
-      (not (eq (window-buffer) (get-buffer "#spam"))))
+    (delete-other-windows)
+    (pop-to-buffer-same-window "*Messages*")
 
     (ert-info ("Wait for auto reconnect")
-      (with-current-buffer erc-server-buffer
-        (funcall expect 10 "still in debug mode")))
+      (with-current-buffer "FooNet" (funcall expect 10 "still in debug mode")))
 
-    (ert-info ("Wait for activity to recommence in channels")
+    (funcall assert-rest expect)
+
+    (ert-info ("Wait for activity to recommence in both channels")
       (with-current-buffer (erc-d-t-wait-for 10 (get-buffer "#chan"))
         (funcall expect 10 "forest of Arden"))
       (with-current-buffer (erc-d-t-wait-for 10 (get-buffer "#spam"))
         (funcall expect 10 "her elves come here anon")))))
 
-(ert-deftest erc-scenarios-base-reconnect-options--buffer ()
+(ert-deftest erc-scenarios-base-buffer-display--defwin-recbury-intbuf ()
   :tags '(:expensive-test)
-  (should (eq erc-join-buffer 'bury))
+  (should (eq erc-buffer-display 'bury))
+  (should (eq erc-interactive-display 'window))
   (should-not erc-reconnect-display)
 
-  ;; FooNet (the server buffer) is not switched to because it's
-  ;; already current (but not shown) when `erc-open' is called.  See
-  ;; related conditional guard towards the end of that function.
+  (let ((erc-buffer-display 'window)
+        (erc-interactive-display 'buffer)
+        (erc-reconnect-display 'bury))
 
-  (let ((erc-reconnect-display 'buffer))
     (erc-scenarios-base-buffer-display--reconnect-common
-     (lambda ()
-       (pop-to-buffer-same-window "*Messages*")
 
-       (erc-d-t-ensure-for 1 "Server buffer not shown"
-         (not (eq (window-buffer) (get-buffer "FooNet"))))
+     (lambda (_)
+       (should (eq (window-buffer) (current-buffer)))
+       (should-not (frame-root-window-p (selected-window))))
 
-       (erc-d-t-wait-for 5 "Channel #chan shown when autojoined"
-         (eq (window-buffer) (get-buffer "#chan")))))))
+     (lambda (_)
+       (should (eq (window-buffer) (current-buffer)))
+       (should (equal (get-buffer "FooNet") (window-buffer (next-window)))))
 
-(ert-deftest erc-scenarios-base-reconnect-options--default ()
-  :tags '(:expensive-test)
-  (should (eq erc-join-buffer 'bury))
-  (should-not erc-reconnect-display)
+     (lambda (_)
+       (with-current-buffer "FooNet"
+         (should (eq (window-buffer) (messages-buffer)))
+         (should (frame-root-window-p (selected-window))))
 
-  (erc-scenarios-base-buffer-display--reconnect-common
+       ;; A manual /JOIN command tells ERC we're done auto-reconnecting
+       (with-current-buffer "FooNet" (erc-scenarios-common-say "/JOIN #spam"))
 
-   (lambda ()
-     (pop-to-buffer-same-window "*Messages*")
+       (ert-info ("#spam ignores `erc-reconnect-display'")
+         ;; Uses `erc-interactive-display' instead.
+         (with-current-buffer (erc-d-t-wait-for 10 (get-buffer "#spam"))
+           (should (eq (window-buffer) (get-buffer "#spam")))
+           ;; Option `buffer' replaces entire window (no split)
+           (should (frame-root-window-p (selected-window)))))))))
 
-     (erc-d-t-ensure-for 1 "Server buffer not shown"
-       (not (eq (window-buffer) (get-buffer "FooNet"))))
+(ert-deftest erc-scenarios-base-buffer-display--defwino-recbury-intbuf ()
+  :tags '(:expensive-test)
+  (should (eq erc-buffer-display 'bury))
+  (should (eq erc-interactive-display 'window))
+  (should-not erc-reconnect-display)
 
-     (erc-d-t-ensure-for 3 "Channel #chan not shown"
-       (not (eq (window-buffer) (get-buffer "#chan"))))
+  (let ((erc-buffer-display 'window-noselect)
+        (erc-reconnect-display 'bury)
+        (erc-interactive-display 'buffer))
+    (erc-scenarios-base-buffer-display--reconnect-common
 
-     (should (eq (window-buffer) (messages-buffer))))))
+     (lambda (_)
+       ;; Selected window shows some non-ERC buffer.  New server
+       ;; buffer appears in another window (other side of split).
+       (should-not (frame-root-window-p (selected-window)))
+       (should-not (eq (window-buffer) (current-buffer)))
+       (with-current-buffer (window-buffer)
+         (should-not (derived-mode-p 'erc-mode)))
+       (should (eq (current-buffer) (window-buffer (next-window)))))
+
+     (lambda (_)
+       (should-not (frame-root-window-p (selected-window)))
+       ;; Current split likely shows scratch.
+       (with-current-buffer (window-buffer)
+         (should-not (derived-mode-p 'erc-mode)))
+       (should (eq (current-buffer) (window-buffer (next-window)))))
+
+     (lambda (_)
+       (with-current-buffer "FooNet"
+         (should (eq (window-buffer) (messages-buffer)))
+         (should (frame-root-window-p (selected-window))))
+
+       ;; A non-interactive JOIN command doesn't signal that we're
+       ;; done auto-reconnecting, and `erc-interactive-display' is
+       ;; ignored, so `erc-buffer-display' is again in charge (here,
+       ;; that means `window-noselect').
+       (ert-info ("Join chan noninteractively and open a /QUERY")
+         (with-current-buffer "FooNet"
+           (erc-cmd-JOIN "#spam")
+           ;; However this will reset the option.
+           (erc-scenarios-common-say "/QUERY bob")
+           (should (eq (window-buffer) (get-buffer "bob")))
+           (should (frame-root-window-p (selected-window)))))
+
+       (ert-info ("Newly joined chan ignores `erc-reconnect-display'")
+         (with-current-buffer (erc-d-t-wait-for 10 (get-buffer "#spam"))
+           (should (eq (window-buffer) (get-buffer "bob")))
+           (should-not (frame-root-window-p (selected-window)))
+           (should (eq (current-buffer) (window-buffer (next-window))))))))))
+
+(ert-deftest erc-scenarios-base-buffer-display--count-reset-timeout ()
+  :tags '(:expensive-test)
+  (should (eq erc-buffer-display 'bury))
+  (should (eq erc-interactive-display 'window))
+  (should (eq erc-reconnect-display-timeout 10))
+  (should-not erc-reconnect-display)
 
+  (let ((erc-buffer-display 'window-noselect)
+        (erc-reconnect-display 'bury)
+        (erc-interactive-display 'buffer)
+        ;; Try changing this value to 1.  The last `ert-info' block
+        ;; should fail.
+        (erc-reconnect-display-timeout 0.1))
+    (erc-scenarios-base-buffer-display--reconnect-common
+     #'ignore #'ignore ; These two are identical to the previous test.
+
+     (lambda (_)
+       (with-current-buffer "FooNet"
+         (should (eq (window-buffer) (messages-buffer)))
+         (should (frame-root-window-p (selected-window))))
+
+       ;; A non-interactive JOIN command doesn't signal that we're
+       ;; done auto-reconnecting
+       (ert-info ("Join chan noninteractively")
+         (with-current-buffer "FooNet"
+           (sit-for 0.1)
+           (erc-cmd-JOIN "#spam")))
+
+       (ert-info ("Newly joined chan ignores `erc-reconnect-display'")
+         (with-current-buffer (erc-d-t-wait-for 10 (get-buffer "#spam"))
+           (should (eq (window-buffer) (messages-buffer)))
+           ;; If `erc-reconnect-display-timeout' were left alone, this
+           ;; would be (frame-root-window-p #<window 1 on *scratch*>).
+           (should-not (frame-root-window-p (selected-window)))
+           (should (eq (current-buffer) (window-buffer (next-window))))))))))
 
 ;; This shows that the option `erc-interactive-display' overrides
 ;; `erc-join-buffer' during cold opens and interactive /JOINs.
-- 
2.39.2