diff --git a/BlogLiterately.cabal b/BlogLiterately.cabal
--- a/BlogLiterately.cabal
+++ b/BlogLiterately.cabal
@@ -1,5 +1,5 @@
 Name:           BlogLiterately
-Version:        0.8.8.1
+Version:        0.8.8.2
 Synopsis:       A tool for posting Haskelly articles to blogs
 Description:    Write blog posts in Markdown format, then use BlogLiterately
                 to do syntax highlighting, format ghci sessions, and upload
@@ -26,7 +26,7 @@
 Maintainer:     Brent Yorgey <byorgey@gmail.com>
 Stability:      experimental
 Build-Type:     Simple
-Tested-With:    GHC ==8.6.5 || ==8.8.4 || ==8.10.7 || ==9.0.2 || ==9.2.1
+Tested-With:    GHC ==8.6.5 || ==8.8.4 || ==8.10.7 || ==9.0.2 || ==9.2.4 || ==9.4.1
 Extra-Source-Files: CHANGES.md
                     README.markdown
                     doc/BlogLiteratelyDoc.lhs
@@ -39,7 +39,7 @@
   location: git://github.com/byorgey/BlogLiterately.git
 
 Library
-  Build-Depends:   base >= 4.0 && < 4.17,
+  Build-Depends:   base >= 4.0 && < 4.18,
                    process,
                    filepath,
                    directory,
@@ -58,12 +58,12 @@
                    blaze-html >= 0.5 && < 0.10,
                    cmdargs >= 0.9.5 && < 0.11,
                    haxr >= 3000.11 && < 3000.12,
-                   pandoc >= 2.8 && < 2.19,
-                   pandoc-types >= 1.20 && < 1.23,
-                   citeproc >= 0.5 && < 0.8,
+                   pandoc >= 2.8 && < 3.1,
+                   pandoc-types >= 1.20 && < 1.24,
+                   citeproc >= 0.5 && < 0.9,
                    highlighting-kate >= 0.5 && < 0.7,
                    data-default >= 0.5 && < 0.8,
-                   lens >= 3.8 && < 5.2,
+                   lens >= 3.8 && < 5.3,
                    tagsoup >= 0.13.4 && < 0.15,
                    HTTP >= 4000.3 && < 4000.5
   Exposed-modules: Text.BlogLiterately
diff --git a/CHANGES.md b/CHANGES.md
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,3 +1,14 @@
+0.8.8.2 (8 Feb 2023)
+--------------------
+
+  - Support GHC through 9.4
+  - Bump upper bounds to allow:
+    - `base-4.17`
+    - `pandoc-3.0`
+    - `pandoc-types-1.23`
+    - `citeproc-0.8`
+    - `lens-5.2`
+
 0.8.8.1 (25 August 2022)
 ------------------------
 
diff --git a/src/Text/BlogLiterately/Transform.hs b/src/Text/BlogLiterately/Transform.hs
--- a/src/Text/BlogLiterately/Transform.hs
+++ b/src/Text/BlogLiterately/Transform.hs
@@ -65,8 +65,7 @@
 import           Data.List.Split                   (splitOn)
 import qualified Data.Map                          as M
 import           Data.Maybe                        (fromMaybe)
-import           Data.Monoid                       (mappend)
-import           Data.Monoid                       (mempty, (<>))
+import           Data.Monoid                       (mappend, mempty, (<>))
 import qualified Data.Set                          as S
 import           Data.Text                         (Text)
 import qualified Data.Text                         as T
@@ -419,7 +418,7 @@
       (errs, opts) <- queryWith extractOptions <$> gets snd
       mapM_ (liftIO . print) errs
       _1 %= (<> opts)
-      _2 %= bottomUp killOptionBlocks
+      _2 %= bottomUp (concatMap killOptionBlocks)
 
 -- | Take a block and extract from it a list of parse errors and an
 --   options record.  If the blog is not tagged with @[BLOpts]@ these
@@ -428,8 +427,8 @@
 extractOptions = onTag "blopts" (const (readBLOptions . T.unpack)) (const mempty)
 
 -- | Delete any blocks tagged with @[BLOpts]@.
-killOptionBlocks :: Block -> Block
-killOptionBlocks = onTag "blopts" (const (const Null)) id
+killOptionBlocks :: Block -> [Block]
+killOptionBlocks = onTag "blopts" (const (const [])) (:[])
 
 -- | Prompt the user for a password if the @blog@ field is set but no
 --   password has been provided.
