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

From: "J.P." <[email-masked]>
Date: Fri, 14 Apr 2023 06:56:16 -0700

Tags: patch

Hi,

I'm hoping this can serve as a unified "omnibug" for all the overlapping
discourse scattered about regarding buffer-display options and the
behavior they produce. The main focus will be those aspects impacting
ERC 5.6 and how they integrate with the upstream display-buffer facility
provided by window.el. In a sense, this is a spiritual successor to

  bug#51753: ERC switches to channel buffer on reconnect

and will inherit the outstanding display-related portions of

  bug#60428: 30.0.50; ERC >5.5: Make M-x erc a more welcoming entry point

the most recent being this proposal:

> This bug's original patch set tried to walk back an austere aspect of
> bug#51753's changes that lumped interactive buffer creation in with
> protocol driven creation. It mainly did so by introducing a new option,
> `erc-interactive-display', that reinstated direct window-buffer
> replacement when running M-x erc. However, as pointed out by Corwin on
> Libera, this did not account for interactive /JOINs.
>
> The attached patch aims to address this oversight as well as simplify
> the display-options picture a bit further by consolidating the roles of
> `erc-interactive-display' and `erc-query-display'. The thinking is that
> both options cover the same basic ground involving buffers created as a
> consequence of issuing slash commands. This patch also adds an interface
> for other such commands to use for detecting when they're being called
> from the command line.

Despite this, I'm still rather unsure about merging our ancient display
options. Aliasing `erc-interactive-display' to `erc-query-display' seems
sane because the latter only appears once, in an interactive command. 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").
Among the reasons to abstain might be

  1. it would necessarily involve redefining the meaning of the option's
     nil value to mean "fall back to erc-join-buffer" instead of "print
     to the server buffer instead," which has no available alternative

  2. it invites an initial security risk (reminiscent of bug#51753)
     or at least a service interruption

For the first point, redefining the nil value is probably justified in
theory because all other values only concern themselves with where a
buffer is displayed, not where messages are routed for printing or
whether buffers are even created, which is the province of dedicated
switches (see note below re `erc-query-on-unjoined-chan-privmsg'). So I
guess this really comes down to whether providing an alternative
companion option and noting it in the doc string is an acceptable trade
off. Point two is more challenging but could perhaps be addressed by a
one-off warning requiring a click-through, before which
`erc-join-buffer' (now `erc-buffer-display') would remain in effect.

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.

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.

Lastly, I've tacked on another patch that's somewhat related in the
buffer-creation (but not so much -display) sense. It concerns the
restoration of the option `erc-query-on-unjoined-chan-privmsg', which
was orphaned in 5.5 (by me) both out of ignorance and an abundance of
caution.

Thanks.

In GNU Emacs 30.0.50 (build 1, x86_64-pc-linux-gnu, GTK+ Version
 3.24.35, cairo version 1.17.6) of 2023-04-12 built on localhost
Repository revision: 861cf3a5c9d2081d811dcfc2c5ce5357f3dc44d4
Repository branch: master
Windowing system distributor 'The X.Org Foundation', version 11.0.12014000
System Description: Fedora Linux 36 (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
  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 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
move-toolbar gtk x-toolkit xinput2 x multi-tty make-network-process
emacs)

Memory information:
((conses 16 63110 9012)
 (symbols 48 8532 0)
 (strings 32 23158 1921)
 (string-bytes 1 669049)
 (vectors 16 14968)
 (vector-slots 8 206726 6655)
 (floats 8 24 39)
 (intervals 56 231 0)
 (buffers 976 10))

>From 6b7f66a1eed5d01c312d125f016e48cf4e8ce0c9 Mon Sep 17 00:00:00 2001
From: "F. Jason Park" <[email-masked]>
Date: Thu, 13 Apr 2023 00:12:06 -0700
Subject: [PATCH 0/2] *** NOT A PATCH ***

*** BLURB HERE ***

F. Jason Park (2):
  [5.6] Revive option erc-query-on-unjoined-chan-privmsg
  [5.6] Extend erc-interactive-display to cover /JOINs

 etc/ERC-NEWS                                  |  24 ++-
 lisp/erc/erc-backend.el                       |   4 +-
 lisp/erc/erc.el                               |  81 +++++----
 test/lisp/erc/erc-scenarios-base-attach.el    | 147 ++++++++++++++++
 .../erc/erc-scenarios-base-buffer-display.el  | 158 ++++++++++++++++++
 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, 439 insertions(+), 133 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 8f1b89f268b..e9de48b2e34 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.  Accompanying this addition are "display"-suffixed
+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 reported as being difficult to discover and remember.
 
@@ -108,6 +111,13 @@ 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.
+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..5d44f478719 100644
--- a/lisp/erc/erc-backend.el
+++ b/lisp/erc/erc-backend.el
@@ -123,6 +123,7 @@ 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-reuse-buffers)
 (defvar erc-verbose-server-ping)
 (defvar erc-whowas-on-nosuchnick)
@@ -1835,7 +1836,8 @@ define-erc-response-handler
                 (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 erc-query-on-unjoined-chan-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 a3a9b03a93c..2088a755da5 100644
--- a/lisp/erc/erc.el
+++ b/lisp/erc/erc.el
@@ -1530,16 +1530,16 @@ erc-buffer-display
 
 (defvaralias 'erc-query-display 'erc-interactive-display)
 (defcustom erc-interactive-display 'window
-  "How to display buffers created by issuing a /SLASH command.
-Examples include /QUERY and /JOIN.  See `erc-buffer-display' for
-a full description of available choices.
-
-Note that this does not affect the behavior of commands like
-`erc-cmd-JOIN' when called from lisp code.  Also, this option
-used to be called `erc-query-display' but now applies to more
-than just /QUERY.  See option `erc-receive-query-display' in the
-Custom group `erc-query' to decide how private messages from
-other people should be displayed."
+  "How to display buffers created from user interaction.
+Examples include the \"slash\" commands /QUERY and /JOIN.  See
+`erc-buffer-display' for a full description of available choices.
+
+Note that this doesn't affect commands like `erc-cmd-JOIN' when
+called from lisp code.  Formerly known as `erc-query-display',
+this option now applies to more than just /QUERY.  See option
+`erc-receive-display' in the same Custom group to decide how
+buffers created as a result of server-initiated activity should
+be displayed."
   :package-version '(ERC . "5.6") ; FIXME sync on release
   :group 'erc-buffers
   :type '(choice (const :tag "Use value of `erc-buffer-display'" nil)
@@ -4757,15 +4757,25 @@ 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.
 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.
+
+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."
   :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..91763842879
--- /dev/null
+++ b/test/lisp/erc/erc-scenarios-base-attach.el
@@ -0,0 +1,147 @@
+;;; 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--query-on-unjoined-enabled ()
+  :tags '(:expensive-test)
+  (should erc-query-on-unjoined-chan-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--query-on-unjoined-disabled ()
+  :tags '(:expensive-test)
+  (should erc-query-on-unjoined-chan-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
+       (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")))))
+
+;;; erc-scenarios-base-attach.el ends here
diff --git a/test/lisp/erc/erc-tests.el b/test/lisp/erc/erc-tests.el
index b516b885895..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)))
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 f4da70c6e5d757aecb8d14894951ddf26409fb24 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/2] [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): Only "open" a buffer
for an unknown target when `erc-query-on-unjoined-chan-privmsg' is t.
* lisp/erc/erc.el (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.
---
 etc/ERC-NEWS                                  |   7 +
 lisp/erc/erc-backend.el                       |   4 +-
 lisp/erc/erc.el                               |  20 ++-
 test/lisp/erc/erc-scenarios-base-attach.el    | 147 ++++++++++++++++++
 .../base/channel-buffer-revival/reattach.eld  |  56 +++++++
 5 files changed, 228 insertions(+), 6 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..31f40e9d0d3 100644
--- a/etc/ERC-NEWS
+++ b/etc/ERC-NEWS
@@ -108,6 +108,13 @@ 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.
+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..5d44f478719 100644
--- a/lisp/erc/erc-backend.el
+++ b/lisp/erc/erc-backend.el
@@ -123,6 +123,7 @@ 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-reuse-buffers)
 (defvar erc-verbose-server-ping)
 (defvar erc-whowas-on-nosuchnick)
@@ -1835,7 +1836,8 @@ define-erc-response-handler
                 (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 erc-query-on-unjoined-chan-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..b38ef38e9f7 100644
--- a/lisp/erc/erc.el
+++ b/lisp/erc/erc.el
@@ -4746,15 +4746,25 @@ 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.
 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.
+
+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."
   :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..91763842879
--- /dev/null
+++ b/test/lisp/erc/erc-scenarios-base-attach.el
@@ -0,0 +1,147 @@
+;;; 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--query-on-unjoined-enabled ()
+  :tags '(:expensive-test)
+  (should erc-query-on-unjoined-chan-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--query-on-unjoined-disabled ()
+  :tags '(:expensive-test)
+  (should erc-query-on-unjoined-chan-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
+       (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")))))
+
+;;; 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 6b7f66a1eed5d01c312d125f016e48cf4e8ce0c9 Mon Sep 17 00:00:00 2001
From: "F. Jason Park" <[email-masked]>
Date: Mon, 10 Apr 2023 17:58:05 -0700
Subject: [PATCH 2/2] [5.6] Extend erc-interactive-display to cover /JOINs

* lisp/erc/erc.el (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.
(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--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.
* 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,
ert-deftest erc-scenarios-base-reconnect-options--buffer,
erc-scenarios-base-reconnect-options--default): Move to new file
erc-scenarios-base-buffer-display.el and rename.
* 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#60428.)
---
 etc/ERC-NEWS                                  |  17 +-
 lisp/erc/erc.el                               |  61 ++++---
 .../erc/erc-scenarios-base-buffer-display.el  | 158 ++++++++++++++++++
 test/lisp/erc/erc-scenarios-base-reconnect.el |  89 ----------
 test/lisp/erc/erc-tests.el                    |  13 +-
 5 files changed, 211 insertions(+), 127 deletions(-)
 create mode 100644 test/lisp/erc/erc-scenarios-base-buffer-display.el

diff --git a/etc/ERC-NEWS b/etc/ERC-NEWS
index 31f40e9d0d3..e9de48b2e34 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.  Accompanying this addition are "display"-suffixed
+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 reported as being difficult to discover and remember.
 
diff --git a/lisp/erc/erc.el b/lisp/erc/erc.el
index b38ef38e9f7..2088a755da5 100644
--- a/lisp/erc/erc.el
+++ b/lisp/erc/erc.el
@@ -1507,8 +1507,8 @@ 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
+(defvaralias 'erc-join-buffer 'erc-buffer-display)
+(defcustom erc-buffer-display 'bury
   "Determines how to display a newly created IRC buffer.
 
 The available choices are:
@@ -1528,13 +1528,21 @@ 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 created from user interaction.
+Examples include the \"slash\" commands /QUERY and /JOIN.  See
+`erc-buffer-display' for a full description of available choices.
+
+Note that this doesn't affect commands like `erc-cmd-JOIN' when
+called from lisp code.  Formerly known as `erc-query-display',
+this option now applies to more than just /QUERY.  See option
+`erc-receive-display' in the same Custom group to decide how
+buffers created as a result of server-initiated activity should
+be displayed."
   :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)
@@ -3057,6 +3065,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 +3122,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 +3597,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,23 +3975,6 @@ 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
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..3ed7a83653e
--- /dev/null
+++ b/test/lisp/erc/erc-scenarios-base-buffer-display.el
@@ -0,0 +1,158 @@
+;;; 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))))))
+
+
+;; 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-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
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