diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,192 @@
+concurrent-output (1.10.21) unstable; urgency=medium
+
+  * Allow ansi-terminal-1.1
+
+ -- Joey Hess <id@joeyh.name>  Mon, 29 Apr 2024 10:15:55 -0400
+
+concurrent-output (1.10.20) unstable; urgency=medium
+
+  * Allow text-2.1.
+
+ -- Joey Hess <id@joeyh.name>  Thu, 12 Oct 2023 15:46:36 -0400
+
+concurrent-output (1.10.19) unstable; urgency=medium
+
+  * Support building for WASM.
+
+ -- Joey Hess <id@joeyh.name>  Wed, 30 Aug 2023 16:03:03 -0400
+
+concurrent-output (1.10.18) unstable; urgency=medium
+
+  * Avoid some build warnings on Windows.
+  * Allow ansi-terminal-1.0
+
+ -- Joey Hess <id@joeyh.name>  Mon, 15 May 2023 16:32:50 -0400
+
+concurrent-output (1.10.17) unstable; urgency=medium
+
+  * Fix ansi-terminal dependency to only include >= 0.6.2,
+    which is needed for hSupportsANSI.
+
+ -- Joey Hess <id@joeyh.name>  Tue, 10 Jan 2023 15:40:11 -0400
+
+concurrent-output (1.10.16) unstable; urgency=medium
+
+  * Allow transformers-0.6
+    (Thanks, Janus Troelsen)
+
+ -- Joey Hess <id@joeyh.name>  Fri, 20 May 2022 19:31:56 -0400
+
+concurrent-output (1.10.15) unstable; urgency=medium
+
+  * Allow text-2.0
+
+ -- Joey Hess <id@joeyh.name>  Fri, 04 Feb 2022 15:32:58 -0400
+
+concurrent-output (1.10.14) unstable; urgency=medium
+
+  * Allow ansi-terminal-0.11.1.
+
+ -- Joey Hess <id@joeyh.name>  Wed, 24 Nov 2021 09:18:20 -0400
+
+concurrent-output (1.10.13) unstable; urgency=medium
+
+  * outputConcurrent and errorConcurrent are now safe to call from a thread
+    that receives async exceptions.
+  * Fix compilation with GHC head.
+    (Thanks, Ellie Hermaszewska)
+
+ -- Joey Hess <id@joeyh.name>  Mon, 22 Nov 2021 11:19:21 -0400
+
+concurrent-output (1.10.12) unstable; urgency=medium
+
+  * Bugfix: createProcessConcurrent would sometimes send the process's
+    output to the console when it was supposed to be piped to a handle.
+    (1.7.5 introduced this bug)
+  * System.Console.Regions should be safe to use in a thread that
+    receives async exceptions; displayConsoleRegions and withConsoleRegion
+    have been made async exception safe.
+    (However, System.Console.Concurrent is not async exception safe.)
+  * Improve documentation about when withConcurrentOutput should be used.
+  * Remove some unused code.
+  * Allow building with older ansi-terminal versions, not only 0.9+.
+
+ -- Joey Hess <id@joeyh.name>  Wed, 10 Jun 2020 17:22:39 -0400
+
+concurrent-output (1.10.11) unstable; urgency=medium
+
+  * Improve documentation of how to use ANSI color codes.
+  * Allow ansi-terminal-0.10.
+
+ -- Joey Hess <id@joeyh.name>  Sun, 06 Oct 2019 15:14:42 -0400
+
+concurrent-output (1.10.10) unstable; urgency=medium
+
+  * Allow ansi-terminal-0.9.
+  * Fix a build warning.
+
+ -- Joey Hess <id@joeyh.name>  Sun, 12 May 2019 16:29:18 -0400
+
+concurrent-output (1.10.9) unstable; urgency=medium
+
+  * waitDisplayChange would deadlock if the STM action passed to it did not
+    cause any changes to the display. That is now safe to do.
+
+ -- Joey Hess <id@joeyh.name>  Mon, 19 Nov 2018 18:40:55 -0400
+
+concurrent-output (1.10.8) unstable; urgency=medium
+
+  * Fix bug in waitDisplayChange that could result in an STM deadlock
+    exception.
+
+ -- Joey Hess <id@joeyh.name>  Mon, 19 Nov 2018 17:01:12 -0400
+
+concurrent-output (1.10.7) unstable; urgency=medium
+
+  * Allow stm-2.5.
+
+ -- Joey Hess <id@joeyh.name>  Sat, 22 Sep 2018 17:14:59 -0400
+
+concurrent-output (1.10.6) unstable; urgency=medium
+
+  * Relax unix bound for ghc 8.6.
+
+ -- Joey Hess <id@joeyh.name>  Mon, 02 Jul 2018 10:58:59 -0400
+
+concurrent-output (1.10.5) unstable; urgency=medium
+
+  * Allow exceptions-0.10.
+
+ -- Joey Hess <id@joeyh.name>  Tue, 13 Mar 2018 11:50:39 -0400
+
+concurrent-output (1.10.4) unstable; urgency=medium
+
+  * Allow exceptions-0.9.
+
+ -- Joey Hess <id@joeyh.name>  Mon, 26 Feb 2018 11:45:08 -0400
+
+concurrent-output (1.10.3) unstable; urgency=medium
+
+  * Allow async-2.2.
+
+ -- Joey Hess <id@joeyh.name>  Mon, 05 Feb 2018 11:07:27 -0400
+
+concurrent-output (1.10.2) unstable; urgency=medium
+
+  * Allow ansi-terminal-0.8.
+
+ -- Joey Hess <id@joeyh.name>  Thu, 25 Jan 2018 11:51:43 -0400
+
+concurrent-output (1.10.1) unstable; urgency=medium
+
+  * Allow ansi-terminal-0.7.
+
+ -- Joey Hess <id@joeyh.name>  Sat, 25 Nov 2017 17:11:50 -0400
+
+concurrent-output (1.10.0) unstable; urgency=medium
+
+  * Simplified by removing workaround for waitForProcess race
+    condition (https://github.com/haskell/process/issues/46).
+  * Depends on process-1.6.0.0 which fixed that race.
+  * ConcurrentProcessHandle is now a type alias for ProcessHandle,
+    and waitForProcessConcurrent simply calls waitForProcess.
+    These are now only provided to avoid breaking backwards
+    compatability.
+
+ -- Joey Hess <id@joeyh.name>  Fri, 19 May 2017 11:54:49 -0400
+
+concurrent-output (1.9.2) unstable; urgency=medium
+
+  * Allow process-1.6.0.0.
+
+ -- Joey Hess <id@joeyh.name>  Fri, 19 May 2017 11:36:35 -0400
+
+concurrent-output (1.9.1) unstable; urgency=medium
+
+  * Documentation fix: createProcessConcurrent has been available on
+    Windows since 1.7.5 but the docs said not.
+  * When all regions are closed, flush stdout so that the display gets
+    updated, which didn't happen before.
+
+ -- Joey Hess <id@joeyh.name>  Tue, 16 May 2017 16:49:43 -0400
+
+concurrent-output (1.9.0) unstable; urgency=medium
+
+  * Replaced displayUpdateNotifier with a simpler and safer
+    waitDisplayChange interface.
+
+ -- Joey Hess <id@joeyh.name>  Fri, 12 May 2017 17:17:09 -0400
+
+concurrent-output (1.8.0) unstable; urgency=medium
+
+  * Added displayUpdateNotifier, which can be used to wait for
+    changes to console regions to be displayed.
+    (stmdemo has an example of using that to temporarily shut down the
+    region based display to run a bash prompt, and restore the region
+    display later.)
+
+ -- Joey Hess <id@joeyh.name>  Fri, 12 May 2017 16:27:41 -0400
+
 concurrent-output (1.7.9) unstable; urgency=medium
 
   * Allow lazy text to be used as an Outputable value, and as
diff --git a/LICENSE b/LICENSE
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright © 2015 Joey Hess <id@joeyh.name>
+Copyright © 2015-2017 Joey Hess <id@joeyh.name>
 Copyright © 2009 Joachim Breitner
 
 Redistribution and use in source and binary forms, with or without
diff --git a/System/Console/Concurrent.hs b/System/Console/Concurrent.hs
--- a/System/Console/Concurrent.hs
+++ b/System/Console/Concurrent.hs
@@ -7,7 +7,7 @@
 -- > import Control.Concurrent.Async
 -- > import System.Console.Concurrent
 -- >
--- > main = withConcurrentOutput $
+-- > main = withConcurrentOutput $ do
 -- > 	outputConcurrent "washed the car\n"
 -- > 		`concurrently`
 -- >	outputConcurrent "walked the dog\n"
@@ -20,12 +20,12 @@
 	Outputable(..),
 	outputConcurrent,
 	errorConcurrent,
-	ConcurrentProcessHandle,
 	createProcessConcurrent,
-	waitForProcessConcurrent,
 	createProcessForeground,
 	flushConcurrentOutput,
 	lockOutput,
+	ConcurrentProcessHandle,
+	waitForProcessConcurrent,
 	-- * Low level access to the output buffer
 	OutputBuffer,
 	StdHandle(..),
diff --git a/System/Console/Concurrent/Internal.hs b/System/Console/Concurrent/Internal.hs
--- a/System/Console/Concurrent/Internal.hs
+++ b/System/Console/Concurrent/Internal.hs
@@ -40,8 +40,6 @@
 	, outputBuffer :: TMVar OutputBuffer
 	, errorBuffer :: TMVar OutputBuffer
 	, outputThreads :: TMVar Integer
-	, processWaiters :: TMVar [Async ()]
-	, waitForProcessLock :: TMVar ()
 	}
 
 data Lock = Locked
@@ -54,8 +52,6 @@
 	<*> newTMVarIO (OutputBuffer [])
 	<*> newTMVarIO (OutputBuffer [])
 	<*> newTMVarIO 0
-	<*> newTMVarIO []
-	<*> newEmptyTMVarIO
 
 -- | Holds a lock while performing an action. This allows the action to
 -- perform its own output to the console, without using functions from this
@@ -106,7 +102,8 @@
 dropOutputLock = withLock $ void . takeTMVar
 
 -- | Use this around any actions that use `outputConcurrent`
--- or `createProcessConcurrent`
+-- or `createProcessConcurrent`, unless 
+-- `System.Console.Regions.displayConsoleRegions` is being used.
 --
 -- This is necessary to ensure that buffered concurrent output actually
 -- gets displayed before the program exits.
@@ -147,15 +144,20 @@
 
 -- | Displays a value to stdout.
 --
--- No newline is appended to the value, so if you want a newline, be sure
--- to include it yourself.
---
 -- Uses locking to ensure that the whole output occurs atomically
 -- even when other threads are concurrently generating output.
 --
+-- No newline is appended to the value, so if you want a newline, be sure
+-- to include it yourself.
+--
 -- When something else is writing to the console at the same time, this does
 -- not block. It buffers the value, so it will be displayed once the other
 -- writer is done.
+--
+-- When outputConcurrent is used within a call to
+-- `System.Console.Regions.displayConsoleRegions`, the output is displayed
+-- above the currently open console regions. Only lines ending in a newline
+-- are displayed in this case (it uses `waitCompleteLines`).
 outputConcurrent :: Outputable v => v -> IO ()
 outputConcurrent = outputConcurrent' StdOut
 
@@ -166,7 +168,12 @@
 errorConcurrent = outputConcurrent' StdErr
 
 outputConcurrent' :: Outputable v => StdHandle -> v -> IO ()
-outputConcurrent' stdh v = bracket setup cleanup go
+outputConcurrent' stdh v = do
+	-- Use a worker thread. This is so any async exception that
+	-- is thrown to the current thread does not affect
+	-- tryTakeOutputLock, which is not async exception safe.
+	worker <- async $ bracket setup cleanup go
+	wait worker
   where
 	setup = tryTakeOutputLock
 	cleanup False = return ()
@@ -181,69 +188,13 @@
 	h = toHandle stdh
 	bv = bufferFor stdh
 
-newtype ConcurrentProcessHandle = ConcurrentProcessHandle P.ProcessHandle
-
-toConcurrentProcessHandle :: (Maybe Handle, Maybe Handle, Maybe Handle, P.ProcessHandle) -> (Maybe Handle, Maybe Handle, Maybe Handle, ConcurrentProcessHandle)
-toConcurrentProcessHandle (i, o, e, h) = (i, o, e, ConcurrentProcessHandle h)
+-- | This alias is provided to avoid breaking backwards compatibility.
+type ConcurrentProcessHandle = P.ProcessHandle
 
--- | Use this to wait for processes started with 
--- `createProcessConcurrent` and `createProcessForeground`, and get their
--- exit status.
---
--- Note that such processes are actually automatically waited for
--- internally, so not calling this explicitly will not result
--- in zombie processes. This behavior differs from `P.waitForProcess`
+-- | Same as `P.waitForProcess`; provided to avoid breaking backwards
+-- compatibility.
 waitForProcessConcurrent :: ConcurrentProcessHandle -> IO ExitCode
-waitForProcessConcurrent (ConcurrentProcessHandle h) = 
-	bracket lock unlock checkexit
-  where
-	lck = waitForProcessLock globalOutputHandle
-	lock = atomically $ tryPutTMVar lck ()
-	unlock True = atomically $ takeTMVar lck
-	unlock False = return ()
-	checkexit locked = maybe (waitsome locked) return
-		=<< P.getProcessExitCode h
-	waitsome True = do
-		let v = processWaiters globalOutputHandle
-		l <- atomically $ readTMVar v
-		if null l
-			-- Avoid waitAny [] which blocks forever
-			then P.waitForProcess h
-			else do
-				-- Wait for any of the running
-				-- processes to exit. It may or may not
-				-- be the one corresponding to the
-				-- ProcessHandle. If it is,
-				-- getProcessExitCode will succeed.
-				void $ tryIO $ waitAny l
-				checkexit True
-	waitsome False = do
-		-- Another thread took the lck first. Wait for that thread to
-		-- wait for one of the running processes to exit.
-		atomically $ do
-			putTMVar lck ()
-			takeTMVar lck
-		checkexit False
-
--- Registers an action that waits for a process to exit,
--- adding it to the processWaiters list, and removing it once the action
--- completes.
-asyncProcessWaiter :: IO () -> IO ()
-asyncProcessWaiter waitaction = do
-	regdone <- newEmptyTMVarIO
-	waiter <- async $ do
-		self <- atomically (takeTMVar regdone)
-		waitaction `finally` unregister self
-	register waiter regdone
-  where
-	v = processWaiters globalOutputHandle
-  	register waiter regdone = atomically $ do
-		l <- takeTMVar v
-		putTMVar v (waiter:l)
-		putTMVar regdone waiter
-	unregister waiter = atomically $ do
-		l <- takeTMVar v
-		putTMVar v (filter (/= waiter) l)
+waitForProcessConcurrent = P.waitForProcess
 
 -- | Wrapper around `System.Process.createProcess` that prevents 
 -- multiple processes that are running concurrently from writing
@@ -262,8 +213,10 @@
 -- redirected to a buffer. The buffered output will be displayed as soon
 -- as the output lock becomes free.
 --
--- Currently only available on Unix systems, not Windows.
-createProcessConcurrent :: P.CreateProcess -> IO (Maybe Handle, Maybe Handle, Maybe Handle, ConcurrentProcessHandle) 
+-- Note that the the process is waited for by a background thread,
+-- so unlike createProcess, neglecting to call waitForProcess will not
+-- result in zombie processess.
+createProcessConcurrent :: P.CreateProcess -> IO (Maybe Handle, Maybe Handle, Maybe Handle, P.ProcessHandle) 
 createProcessConcurrent p
 	| willOutput (P.std_out p) || willOutput (P.std_err p) =
 		ifM tryTakeOutputLock
@@ -272,31 +225,34 @@
 			)
 	| otherwise = do
 		r@(_, _, _, h) <- P.createProcess p
-		asyncProcessWaiter $
-			void $ tryIO $ P.waitForProcess h
-		return (toConcurrentProcessHandle r)
+		_ <- async $ void $ tryIO $ P.waitForProcess h
+		return r
 
 -- | Wrapper around `System.Process.createProcess` that makes sure a process
 -- is run in the foreground, with direct access to stdout and stderr.
 -- Useful when eg, running an interactive process.
-createProcessForeground :: P.CreateProcess -> IO (Maybe Handle, Maybe Handle, Maybe Handle, ConcurrentProcessHandle)
+--
+-- Note that the the process is waited for by a background thread,
+-- so unlike createProcess, neglecting to call waitForProcess will not
+-- result in zombie processess.
+createProcessForeground :: P.CreateProcess -> IO (Maybe Handle, Maybe Handle, Maybe Handle, P.ProcessHandle)
 createProcessForeground p = do
 	takeOutputLock
 	fgProcess p
 
-fgProcess :: P.CreateProcess -> IO (Maybe Handle, Maybe Handle, Maybe Handle, ConcurrentProcessHandle)
+fgProcess :: P.CreateProcess -> IO (Maybe Handle, Maybe Handle, Maybe Handle, P.ProcessHandle)
 fgProcess p = do
 	r@(_, _, _, h) <- P.createProcess p
 		`onException` dropOutputLock
 	registerOutputThread
 	-- Wait for the process to exit and drop the lock.
-	asyncProcessWaiter $ do
+	_ <- async $ do
 		void $ tryIO $ P.waitForProcess h
 		unregisterOutputThread
 		dropOutputLock
-	return (toConcurrentProcessHandle r)
+	return r
 
-bgProcess :: P.CreateProcess -> IO (Maybe Handle, Maybe Handle, Maybe Handle, ConcurrentProcessHandle)
+bgProcess :: P.CreateProcess -> IO (Maybe Handle, Maybe Handle, Maybe Handle, P.ProcessHandle)
 bgProcess p = do
 	let p' = p
 		{ P.std_out = rediroutput (P.std_out p)
@@ -311,15 +267,20 @@
 		, mungeret (P.std_err p) stderr_h
 		, h
 		)
-	asyncProcessWaiter $ void $ tryIO $ P.waitForProcess h
-	outbuf <- setupOutputBuffer StdOut stdout_h
-	errbuf <- setupOutputBuffer StdErr stderr_h
+	-- Wait for the process for symmetry with fgProcess,
+	-- which does the same.
+	_ <- async $ void $ tryIO $ P.waitForProcess h
+	outbuf <- setupOutputBuffer StdOut (mungebuf (P.std_out p) stdout_h)
+	errbuf <- setupOutputBuffer StdErr (mungebuf (P.std_err p) stderr_h)
 	void $ async $ bufferWriter [outbuf, errbuf]
-	return (toConcurrentProcessHandle r)
+	return r
   where
 	rediroutput ss
 		| willOutput ss = P.CreatePipe
 		| otherwise = ss
+	mungebuf ss mh
+		| willOutput ss = mh
+		| otherwise = Nothing
 	mungeret ss mh
 		| willOutput ss = Nothing
 		| otherwise = mh
diff --git a/System/Console/Regions.hs b/System/Console/Regions.hs
--- a/System/Console/Regions.hs
+++ b/System/Console/Regions.hs
@@ -9,7 +9,9 @@
 -- License: BSD-2-clause
 -- 
 -- Console regions are displayed near the bottom of the console, and can be
--- updated concurrently by threads. Any other output displayed using
+-- updated concurrently by threads. 
+--
+-- Any other output lines displayed using
 -- `outputConcurrent` and `createProcessConcurrent`
 -- will scroll up above the open console regions.
 --
@@ -108,6 +110,7 @@
 	consoleWidth,
 	consoleHeight,
 	regionList,
+	waitDisplayChange,
 ) where
 
 import Data.Monoid
@@ -123,17 +126,19 @@
 import Control.Concurrent.STM.TSem
 import Control.Concurrent.Async
 import System.Console.ANSI
-import qualified System.Console.Terminal.Size as Console
 import System.IO
 import System.IO.Unsafe (unsafePerformIO)
 import Text.Read
-import Data.List
+import Data.List (intercalate, nubBy)
+import Control.Applicative
+import Prelude
+#ifdef VERSION_terminal_size
+import qualified System.Console.Terminal.Size as Console
 #ifndef mingw32_HOST_OS
 import System.Posix.Signals
 import System.Posix.Signals.Exts
 #endif
-import Control.Applicative
-import Prelude
+#endif
 
 import System.Console.Concurrent
 import Utility.Monad
@@ -142,6 +147,7 @@
 -- | Controls how a region is laid out in the console.
 --
 -- Here's an annotated example of how the console layout works.
+-- Each sequence of the same letter represents a distinct region.
 --
 -- > scrolling......
 -- > scrolling......
@@ -178,22 +184,26 @@
 regionList :: TMVar [ConsoleRegion]
 regionList = unsafePerformIO newEmptyTMVarIO
 
--- | On Unix systems, this TVar is automatically updated when the
--- terminal is resized. On Windows, it is only initialized on program start
--- with the current terminal size.
+data ConsoleSize = ConsoleSize
+	{ _consoleHeight :: Int
+	, _consoleWidth :: Int
+	}
+
 {-# NOINLINE consoleSize #-}
-consoleSize :: TVar (Console.Window Int)
-consoleSize = unsafePerformIO $ newTVarIO $ 
-	Console.Window { Console.width = 80, Console.height = 25}
+consoleSize :: TVar ConsoleSize
+consoleSize = unsafePerformIO $ newTVarIO $
+	ConsoleSize { _consoleWidth = 80, _consoleHeight = 25}
 
 type Width = Int
 
 -- | Gets the width of the console.
 --
 -- On Unix, this is automatically updated when the terminal is resized.
--- On Windows, it is only initialized on program start.
+-- On Windows, it is determined at start. On WASM,
+-- the console width is hard coded to 80 since WASI does not provide a way
+-- to determine it.
 consoleWidth :: STM Int
-consoleWidth = munge . Console.width <$> readTVar consoleSize
+consoleWidth = munge . _consoleWidth <$> readTVar consoleSize
   where
 #ifndef mingw32_HOST_OS
 	munge = id
@@ -204,11 +214,15 @@
 #endif
 
 -- | Get the height of the console.
+--
+-- On Unix, this is automatically updated when the terminal is resized.
+-- On Windows, it is determined at start. On WASM,
+-- the console heigth is hard coded to 25 since WASI does not provide a way
+-- to determine it.
 consoleHeight :: STM Int
-consoleHeight = Console.height <$> readTVar consoleSize
+consoleHeight = _consoleHeight <$> readTVar consoleSize
 
--- | The RegionList TMVar is left empty when `displayConsoleRegions`
--- is not running.
+-- | Check if `displayConsoleRegions` is running.
 regionDisplayEnabled :: IO Bool
 regionDisplayEnabled = atomically $ not <$> isEmptyTMVar regionList
 
@@ -256,10 +270,26 @@
 -- necessary.
 --
 -- The value can include ANSI SGR escape sequences for changing
--- the colors etc of all or part of a region.
+-- the colors of all or part of a region. For this to display properly,
+-- a reset escape sequence must be included to get the color back
+-- to default. System.Console.ANSI makes it easy to construct such
+-- values. For example:
+--
+-- > import System.Console.ANSI
+-- > 
+-- > setConsoleRegion region 
+-- > 	( "hello "
+-- > 	<> setSGRCode [SetColor Foreground Vivid Red] 
+-- >	<> "Mars" 
+-- >	<> setSGRCode [Reset]
+-- > 	<> "!"
+-- >	)
 -- 
 -- Other ANSI escape sequences, especially those doing cursor
 -- movement, will mess up the layouts of regions. Caveat emptor.
+--
+-- ANSI SGR escape sequences that span multiple lines do not currently
+-- display as you might hope. (Patches would be accepted.)
 setConsoleRegion :: (ToRegionContent v, LiftRegion m) => ConsoleRegion -> v -> m ()
 setConsoleRegion r v = liftRegion $
 	modifyRegion r $ const $ pure $ toRegionContent v
@@ -293,7 +323,9 @@
 -- | Runs the action with a new console region, closing the region when
 -- the action finishes or on exception.
 withConsoleRegion :: (MonadIO m, MonadMask m) => RegionLayout -> (ConsoleRegion -> m a) -> m a
-withConsoleRegion ly = bracketIO (openConsoleRegion ly) (closeConsoleRegion)
+withConsoleRegion ly = bracketIO
+	(openConsoleRegion ly)
+	(uninterruptibleMask_ . closeConsoleRegion)
 
 -- | Opens a new console region.
 openConsoleRegion :: LiftRegion m => RegionLayout -> m ConsoleRegion
@@ -402,7 +434,9 @@
 -- Note that this uses `lockOutput`, so it takes over all output to the
 -- console while the passed IO action is running. As well as displaying
 -- the console regions, this handles display of anything buffered by
--- `outputConcurrent` and `createProcessConcurrent`.
+-- `outputConcurrent` and `createProcessConcurrent`. So,
+-- `withConcurrentOutput` and `flushConcurrentOutput` should not be run
+-- while this is in use, and will block.
 --
 -- When standard output is not an ANSI capable terminal,
 -- console regions are not displayed.
@@ -412,17 +446,17 @@
 	, lockOutput $ bracket setup cleanup (const a)
 	)
   where
-	setup = liftIO $ do
+	setup = liftIO $ uninterruptibleMask $ \unmask -> do
 		atomically $ putTMVar regionList []
 		endsignal <- atomically $ do
 			s <- newTSem 1
 			waitTSem s
 			return s
 		isterm <- liftIO $ hSupportsANSI stdout
-		when isterm trackConsoleWidth
-		da <- async $ displayThread isterm endsignal
+		when isterm (unmask trackConsoleWidth)
+		da <- async $ unmask $ displayThread isterm endsignal
 		return (isterm, da, endsignal)
-	cleanup (isterm, da, endsignal) = liftIO $ do
+	cleanup (isterm, da, endsignal) = liftIO $ uninterruptibleMask_ $ do
 		atomically $ signalTSem endsignal
 		void $ wait da
 		void $ atomically $ takeTMVar regionList
@@ -430,48 +464,107 @@
 			installResizeHandler Nothing
 
 trackConsoleWidth :: IO ()
+#ifdef VERSION_terminal_size
 trackConsoleWidth = do
-	let getwidth = maybe noop (atomically . writeTVar consoleSize)
+	let getsz = maybe noop (atomically . writeTVar consoleSize . conv)
 		=<< Console.size
-	getwidth
-	installResizeHandler (Just getwidth)
+	getsz
+	installResizeHandler (Just getsz)
+  where
+	conv wsz = ConsoleSize
+		{ _consoleWidth = Console.width wsz
+		, _consoleHeight = Console.height wsz
+		}
+#else
+trackConsoleWidth = return ()
+#endif
 
 data DisplayChange
-	= BufferChange (StdHandle, OutputBuffer)
+	= BufferChange BufferSnapshot 
 	| RegionChange RegionSnapshot
+	| RegionListChange RegionSnapshot
 	| TerminalResize Width
-	| EndSignal ()
+	| Shutdown
+	| DisplayChangeBarrier Barrier
 
+type BufferSnapshot = (StdHandle, OutputBuffer)
 type RegionSnapshot = ([ConsoleRegion], [R], [[Text]])
+type Barrier = Integer
 
+-- | This is a broadcast TChan, which gets a DisplayChange written to it
+-- after the display has been updated. It can be used to wait for something
+-- to be displayed.
+{-# NOINLINE displayUpdateNotifier #-}
+displayUpdateNotifier :: TChan DisplayChange
+displayUpdateNotifier = unsafePerformIO $ newBroadcastTChanIO
+
+{-# NOINLINE displayChangeBarrier #-}
+displayChangeBarrier :: TVar Barrier
+displayChangeBarrier = unsafePerformIO $ newTVarIO 0
+
+-- | Runs a STM action, and waits for the display to be fully updated
+-- with any changes that action makes to the displayed regions.
+waitDisplayChange :: STM a -> IO a
+waitDisplayChange a = do
+	c <- atomically $ dupTChan displayUpdateNotifier
+	bv <- newEmptyTMVarIO
+	_ <- setbarrier bv `concurrently` waitchange c bv
+	snd <$> atomically (readTMVar bv)
+  where
+	setbarrier bv = atomically $ do
+		!b <- succ <$> readTVar displayChangeBarrier
+		r <- a
+		writeTVar displayChangeBarrier b
+		putTMVar bv (b, r)
+	waitchange c bv = do
+		change <- atomically $ readTChan c
+		-- this blocks until the STM action has run, and the
+		-- barrier is set.
+		b <- fst <$> atomically (readTMVar bv)
+		case change of
+			DisplayChangeBarrier b' | b' >= b -> return ()
+			_ -> waitchange c bv
+
 displayThread :: Bool -> TSem -> IO ()
 displayThread isterm endsignal = do
 	origwidth <- atomically consoleWidth
-	go ([], [], []) origwidth
+	origbarrier <- atomically (readTVar displayChangeBarrier)
+	go ([], [], []) origwidth origbarrier
   where
-	go origsnapshot@(orighandles, origregions, origlines) origwidth = do
+	go origsnapshot@(orighandles, origregions, origlines) origwidth origbarrier = do
 		let waitwidthchange = do
 			w <- consoleWidth
 			if w == origwidth then retry else return w
+		let waitbarrierchange = do
+			b <- readTVar displayChangeBarrier
+			if b /= origbarrier
+				then return b
+				else retry
 		let waitanychange =
 			(RegionChange <$> regionWaiter origsnapshot origwidth)
 				`orElse`
-			(RegionChange <$> regionListWaiter origsnapshot)
+			(RegionListChange <$> regionListWaiter origsnapshot)
 				`orElse`
 			(BufferChange <$> outputBufferWaiterSTM waitCompleteLines)
 				`orElse`
 			(TerminalResize <$> waitwidthchange)
 				`orElse`
-			(EndSignal <$> waitTSem endsignal)
+			(waitTSem endsignal >> pure Shutdown)
+				`orElse`
+			-- Must come last, so the changes above are
+			-- processed before barriers.
+			(DisplayChangeBarrier <$> waitbarrierchange)
 		(change, height) <- atomically $ (,)
 			<$> waitanychange
 			<*> consoleHeight
 		let onscreen = take (height - 1) . concat
-		case change of
-			RegionChange snapshot@(_, _, newlines) -> do
-				when isterm $ do
-					changedLines (onscreen origlines) (onscreen newlines)
-				go snapshot origwidth
+		let update snapshot@(_, _, newlines) = do
+			when isterm $
+				changedLines (onscreen origlines) (onscreen newlines)
+			return $ go snapshot origwidth origbarrier
+		next <- case change of
+			RegionChange snapshot -> update snapshot
+			RegionListChange snapshot -> update snapshot
 			BufferChange (h, buf) -> do
 				-- Note that even when every available line
 				-- is dedicated to visible regions, the
@@ -481,13 +574,19 @@
 				let origlines' = onscreen origlines
 				inAreaAbove isterm (length origlines') origlines' $
 					emitOutputBuffer h buf
-				go origsnapshot origwidth
+				return $ go origsnapshot origwidth origbarrier
 			TerminalResize newwidth -> do
 				newlines <- atomically (mapM (resizeRegion newwidth) orighandles)
 				when isterm $ do
 					resizeRecovery (onscreen newlines)
-				go (orighandles, origregions, newlines) newwidth
-			EndSignal () -> return ()
+				return $ go (orighandles, origregions, newlines) newwidth origbarrier
+			Shutdown ->
+				return $ return ()
+			DisplayChangeBarrier b ->
+				return $ go origsnapshot origwidth b
+		hFlush stdout
+		atomically $ writeTChan displayUpdateNotifier change
+		next
 
 readRegions :: [ConsoleRegion] -> STM [R]
 readRegions = mapM (\(ConsoleRegion h) -> readTVar h)
@@ -576,7 +675,6 @@
 #endif
 		cursorDown (sum (map (snd . fst) l'))
 		setCursorColumn 0
-		hFlush stdout
   where
 	l' = changeOffsets l 1 []
 
@@ -608,7 +706,6 @@
 	when isterm $ do
 		setCursorColumn 0 -- just in case the output lacked a newline
 		displayLines (reverse ls)
-	hFlush stdout
 
 displayLines :: [Text] -> IO ()
 displayLines = mapM_ $ \l -> do
@@ -617,11 +714,15 @@
 
 installResizeHandler :: Maybe (IO ()) -> IO ()
 #ifndef mingw32_HOST_OS
+#ifdef VERSION_terminal_size
 installResizeHandler h = void $
 	installHandler windowChange (maybe Default Catch h) Nothing
 #else
 installResizeHandler _ = return ()
 #endif
+#else
+installResizeHandler _ = return ()
+#endif
 
 calcRegionLines :: R -> Width -> STM [Text]
 calcRegionLines r width = do
@@ -694,6 +795,8 @@
 endSGR :: Char
 endSGR = 'm'
 
+#ifndef mingw32_HOST_OS
+
 -- | Finds the least expensive output to make a console that was displaying
 -- the old line display the new line. Cursor starts at far left.
 --
@@ -829,3 +932,5 @@
 		| x >= 90 && x <= 97 = Just (Foreground, x)
 		| x >= 100 && x <= 107 = Just (Background, x)
 		| otherwise = Nothing
+
+#endif
diff --git a/System/Process/Concurrent.hs b/System/Process/Concurrent.hs
--- a/System/Process/Concurrent.hs
+++ b/System/Process/Concurrent.hs
@@ -9,26 +9,14 @@
 module System.Process.Concurrent where
 
 import System.Console.Concurrent
-import System.Console.Concurrent.Internal (ConcurrentProcessHandle(..))
 import System.Process hiding (createProcess, waitForProcess)
 import System.IO
 import System.Exit
 
 -- | Calls `createProcessConcurrent`
---
--- You should use the waitForProcess in this module on the resulting
--- ProcessHandle. Using System.Process.waitForProcess instead can have
--- mildly unexpected results.
 createProcess :: CreateProcess -> IO (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)
-createProcess p = do
-	(i, o, e, ConcurrentProcessHandle h) <- createProcessConcurrent p
-	return (i, o, e, h)
+createProcess = createProcessConcurrent
 
 -- | Calls `waitForProcessConcurrent`
---
--- You should only use this on a ProcessHandle obtained by calling
--- createProcess from this module. Using this with a ProcessHandle
--- obtained from System.Process.createProcess etc will have extremely
--- unexpected results; it can wait a very long time before returning.
 waitForProcess :: ProcessHandle -> IO ExitCode
-waitForProcess = waitForProcessConcurrent . ConcurrentProcessHandle
+waitForProcess = waitForProcessConcurrent
diff --git a/TODO b/TODO
--- a/TODO
+++ b/TODO
@@ -0,0 +1,53 @@
+* System.Console.Regions handling of outputConcurrent and errorConcurrent
+  when the output lacks a newline is surprising. The output may never be
+  displayed. A better approach:
+
+  When output arrives that lacks a newline, allocate a region for it
+  and buffer it in that region, so it displays immediately and more
+  output will go to the same region. When a newline arrives, close the
+  region and scroll up the completed line the same way any completed region
+  is displayed.
+
+* Parts of System.Console.Concurrent may not be async exception safe.
+
+  If a thread is running an action from that module and an async exception
+  is sent to it, it may result in a deadlock or other problem.
+
+  (System.Console.Regions has been made safe,
+  also outputConcurrent and errorConcurrent.)
+
+  Particularly problematic is takeOutputLock', which takes the lock
+  and then outputs buffers to the console. If the emitOutputBuffer
+  calls are interrupted by async exception, it will be left locked
+  and the buffered output is also lost. But masking them is not good
+  because emitOutputBuffer could run for a long time in some situations.
+
+  fgProcess and bgProcess also do stuff with registerOutputThread/unregisterOutputThread
+  that may not be async exception safe. 
+
+  And createProcessForeground uses takeOutputLock but then calls fgProcess,
+  which could be interrupted (during its call to registerOutputThread)
+  before it starts the async thread that drops the lock.
+
+  One approach to all this might be to fork off a worker thread,
+  which will thus be immune to any async exception directed at the calling
+  thread.
+
+* Calling setConsoleRegion with something that throws an error
+  will cause no further display updates to happen.
+  
+  The exception in the thunk will crash the displayThread,
+  and that is not waited on until the action passed to displayConsoleRegions
+  finishes, so the exception is deferred to that point. Of course if the
+  action never finishes, that hides the exception.
+
+  It does seem this should be improved, by catching the crashing
+  displayThread and ideally propigating the exception immediately.
+  But displayConsoleRegions would need to use a different class than
+  the current MonadIO to be able to do that. Eg, MonadBaseControl
+  so lifted-async can be used, or a class that lets the action be
+  canceled.
+
+  Or it could be dealt with by having setConsoleRegion force the thunk
+  before passing it over to the displayThread. But the instance
+  ToRegionContent (STM Text) seems to prevent doing that.
diff --git a/Utility/Data.hs b/Utility/Data.hs
deleted file mode 100644
--- a/Utility/Data.hs
+++ /dev/null
@@ -1,19 +0,0 @@
-{- utilities for simple data types
- -
- - Copyright 2013 Joey Hess <id@joeyh.name>
- -
- - License: BSD-2-clause
- -}
-
-{-# OPTIONS_GHC -fno-warn-tabs #-}
-
-module Utility.Data where
-
-{- First item in the list that is not Nothing. -}
-firstJust :: Eq a => [Maybe a] -> Maybe a
-firstJust ms = case dropWhile (== Nothing) ms of
-	[] -> Nothing
-	(md:_) -> md
-
-eitherToMaybe :: Either a b -> Maybe b
-eitherToMaybe = either (const Nothing) Just
diff --git a/Utility/Exception.hs b/Utility/Exception.hs
--- a/Utility/Exception.hs
+++ b/Utility/Exception.hs
@@ -31,8 +31,6 @@
 import System.IO.Error (isDoesNotExistError, ioeGetErrorType)
 import GHC.IO.Exception (IOErrorType(..))
 
-import Utility.Data
-
 {- Catches IO errors and returns a Bool -}
 catchBoolIO :: MonadCatch m => m Bool -> m Bool
 catchBoolIO = catchDefaultIO False
@@ -86,7 +84,7 @@
 tryWhenExists :: MonadCatch m => m a -> m (Maybe a)
 tryWhenExists a = do
 	v <- tryJust (guard . isDoesNotExistError) a
-	return (eitherToMaybe v)
+	return (either (const Nothing) Just v)
 
 {- Catches only exceptions caused by hardware faults.
  - Ie, disk IO error. -}
diff --git a/Utility/Monad.hs b/Utility/Monad.hs
--- a/Utility/Monad.hs
+++ b/Utility/Monad.hs
@@ -9,62 +9,11 @@
 
 module Utility.Monad where
 
-import Data.Maybe
-import Control.Monad
-
-{- Return the first value from a list, if any, satisfying the given
- - predicate -}
-firstM :: Monad m => (a -> m Bool) -> [a] -> m (Maybe a)
-firstM _ [] = return Nothing
-firstM p (x:xs) = ifM (p x) (return $ Just x , firstM p xs)
-
-{- Runs the action on values from the list until it succeeds, returning
- - its result. -}
-getM :: Monad m => (a -> m (Maybe b)) -> [a] -> m (Maybe b)
-getM _ [] = return Nothing
-getM p (x:xs) = maybe (getM p xs) (return . Just) =<< p x
-
-{- Returns true if any value in the list satisfies the predicate,
- - stopping once one is found. -}
-anyM :: Monad m => (a -> m Bool) -> [a] -> m Bool
-anyM p = liftM isJust . firstM p
-
-allM :: Monad m => (a -> m Bool) -> [a] -> m Bool
-allM _ [] = return True
-allM p (x:xs) = p x <&&> allM p xs
-
-{- Runs an action on values from a list until it succeeds. -}
-untilTrue :: Monad m => [a] -> (a -> m Bool) -> m Bool
-untilTrue = flip anyM
-
 {- if with a monadic conditional. -}
 ifM :: Monad m => m Bool -> (m a, m a) -> m a
 ifM cond (thenclause, elseclause) = do
 	c <- cond
 	if c then thenclause else elseclause
-
-{- short-circuiting monadic || -}
-(<||>) :: Monad m => m Bool -> m Bool -> m Bool
-ma <||> mb = ifM ma ( return True , mb )
-
-{- short-circuiting monadic && -}
-(<&&>) :: Monad m => m Bool -> m Bool -> m Bool
-ma <&&> mb = ifM ma ( mb , return False )
-
-{- Same fixity as && and || -}
-infixr 3 <&&>
-infixr 2 <||>
-
-{- Runs an action, passing its value to an observer before returning it. -}
-observe :: Monad m => (a -> m b) -> m a -> m a
-observe observer a = do
-	r <- a
-	_ <- observer r
-	return r
-
-{- b `after` a runs first a, then b, and returns the value of a -}
-after :: Monad m => m b -> m a -> m a
-after = observe . const
 
 {- do nothing -}
 noop :: Monad m => m ()
diff --git a/concurrent-output.cabal b/concurrent-output.cabal
--- a/concurrent-output.cabal
+++ b/concurrent-output.cabal
@@ -1,14 +1,14 @@
 Name: concurrent-output
-Version: 1.7.9
-Cabal-Version: >= 1.8
+Version: 1.10.21
+Cabal-Version: >= 1.10
 License: BSD2
 Maintainer: Joey Hess <id@joeyh.name>
 Author: Joey Hess, Joachim Breitner
 Stability: Stable
-Copyright: 2015 Joey Hess, 2009 Joachim Breitner
+Copyright: 2015-2021 Joey Hess, 2009 Joachim Breitner
 License-File: LICENSE
 Build-Type: Simple
-Category: User Interfaces
+Category: User Interfaces, Concurrency
 Synopsis: Ungarble output from several threads or commands
 Description:
  Lets multiple threads and external processes concurrently output to the
@@ -29,17 +29,17 @@
   stmdemo.hs
 
 Library
+  Default-Language: Haskell2010
   GHC-Options: -Wall -fno-warn-tabs -O2
   Build-Depends: base (>= 4.6), base < 5
-    , text (>= 0.11.0 && < 1.3.0)
-    , async (>= 2.0 && < 2.2)
-    , stm (>= 2.0 && < 2.5)
-    , process (>= 1.1.0 && < 1.5.0)
+    , text (>= 0.11.0 && < 2.2)
+    , async (>= 2.0 && < 2.3)
+    , stm (>= 2.0 && < 2.6)
+    , process (>= 1.6.0 && < 1.7.0)
     , directory (>= 1.2.0 && < 1.4.0)
-    , transformers (>= 0.3.0 && < 0.6.0)
-    , exceptions (>= 0.6.0 && < 0.9.0)
-    , ansi-terminal (>= 0.6.0 && < 0.7.0)
-    , terminal-size (>= 0.3.0 && < 0.4.0)
+    , transformers (>= 0.3.0 && < 0.7.0)
+    , exceptions (>= 0.6.0 && < 0.11.0)
+    , ansi-terminal (>= 0.6.2 && < 1.2.0)
   Exposed-Modules:
     System.Console.Concurrent
     System.Console.Concurrent.Internal
@@ -47,11 +47,12 @@
     System.Process.Concurrent
   Other-Modules:
     Utility.Monad
-    Utility.Data
     Utility.Exception
 
   if (! os(Windows))
-    Build-Depends: unix (>= 2.7.0 && < 2.8.0)
+    Build-Depends: unix (>= 2.7.0 && < 2.9.0)
+  if (! arch(wasm32))
+    Build-Depends: terminal-size (>= 0.3.0 && < 0.4.0)
 
 source-repository head
   type: git
diff --git a/demo2.hs b/demo2.hs
--- a/demo2.hs
+++ b/demo2.hs
@@ -1,21 +1,16 @@
 import Control.Concurrent.Async
 import Control.Concurrent
-import System.Console.Concurrent
 import System.Console.Regions
-import System.Console.Terminal.Size
-import qualified Data.Text as T
-import Control.Concurrent.STM
-import Control.Applicative
-import Data.Time.Clock
 import Control.Monad
 
-main = displayConsoleRegions $ mapConcurrently id
+main :: IO ()
+main = displayConsoleRegions $ void $ mapConcurrently id
 	[ spinner 100 1 "Pinwheels!!" setConsoleRegion "/-\\|" (withtitle 1)
 	, spinner 100 1 "Bubbles!!!!" setConsoleRegion ".oOo." (withtitle 1)
 	, spinner 100 1 "Dots......!" appendConsoleRegion "."  (const (take 3))
 	, spinner  30 2 "KleisiFish?" setConsoleRegion "  <=<   <=<  " (withtitle 10)
-	, spinner  10 9 "Countdowns!" setConsoleRegion
-		(reverse [1..10])
+	, spinner   9 9 "Countdowns!" setConsoleRegion
+		(reverse ([1..10] :: [Int]))
 		(\t n -> t ++ show (head n))
 	]
   where
diff --git a/stmdemo.hs b/stmdemo.hs
--- a/stmdemo.hs
+++ b/stmdemo.hs
@@ -7,15 +7,23 @@
 import Data.Time.Clock
 import Control.Monad
 import Data.Monoid
+import System.Process
 
 main :: IO ()
 main = void $ displayConsoleRegions $ do
+	void titleRegion
 	ir <- infoRegion
 	cr <- clockRegion
 	rr <- rulerRegion
-	growingDots
+	growingDots `concurrently` runBash
 	mapM_ closeConsoleRegion [ir, cr]
 
+titleRegion :: IO ConsoleRegion
+titleRegion = do
+	r <- openConsoleRegion Linear
+	setConsoleRegion r "STM demo!"
+	return r
+
 infoRegion :: IO ConsoleRegion
 infoRegion = do
 	r <- openConsoleRegion Linear
@@ -48,24 +56,36 @@
 		rightAlign r
 		return r
 
+rulerRegion :: IO ConsoleRegion
+rulerRegion = do
+	r <- openConsoleRegion Linear
+	setConsoleRegion r $ do
+		width <- consoleWidth
+		return $ T.pack $ take width nums
+	return r
+  where
+	nums = cycle $ concatMap show [0..9]
+
 rightAlign :: ConsoleRegion -> STM ()
 rightAlign r = tuneDisplay r $ \t -> do
         w <- consoleWidth
         return (T.replicate (w - T.length t) (T.singleton ' ') <> t)
 
+growingDots :: IO ()
 growingDots = withConsoleRegion Linear $ \r -> do
 	atomically $ rightAlign r
 	width <- atomically consoleWidth
-	replicateM width $ do
+	void $ replicateM width $ do
 		appendConsoleRegion r "." 
 		threadDelay (100000)
 
-rulerRegion :: IO ConsoleRegion
-rulerRegion = do
-	r <- openConsoleRegion Linear
-	setConsoleRegion r $ do
-		width <- consoleWidth
-		return $ T.pack $ take width nums
-	return r
-  where
-	nums = cycle $ concatMap show [0..9]
+runBash :: IO ()
+runBash = do
+	-- Wait for growingDots to display some.
+	threadDelay 1000000
+	-- Temporarily clear whatever console regions are open.
+	rs <- waitDisplayChange $ swapTMVar regionList []
+	putStrLn "We interrupt this demo to run a shell prompt. exit to continue!"
+	callCommand "bash"
+	-- Restore the console regions.
+	void $ atomically $ swapTMVar regionList rs
