diff --git a/LICENSE b/LICENSE
deleted file mode 100644
--- a/LICENSE
+++ /dev/null
@@ -1,8 +0,0 @@
-Copyright (c) 2020 Tom Sydney Kerckhove
-
-All Rights Reserved
-
-
-You can use this software under the AGPL license **with the additional condition** that it is not used for commercial purposes.
-Alternatively you can automatically receive a commercial license while you are [a github sponsor of NorfairKing](https://github.com/sponsors/NorfairKing) or a contributor.
-
diff --git a/LICENSE.md b/LICENSE.md
new file mode 100644
--- /dev/null
+++ b/LICENSE.md
@@ -0,0 +1,37 @@
+Sydtest License
+
+Copyright (c) 2020-2021 Tom Sydney Kerckhove
+
+**Anyone** can use this software to test your software under the following license:
+
+
+Permissions:
+* The licensed software may be distributed.
+* The licensed software may be modified.
+* The licensed software may be used to test the software under test.
+
+Conditions:
+* _The software under test is not used for commercial purposes._
+* Any modifications to the licensed software must be made public under the same license with the same copyright holder.
+* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+Limitations:
+* The software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and noninfringement.
+* In no event shall the authors or copyright holders be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the software or the use or other dealings in the software.
+
+
+
+
+**Contributors listed in [CONTRIBUTORS](./CONTRIBUTORS.md) or [GitHub sponsors of NorfairKing](https://github.com/sponsors/NorfairKing)** can use this software to test their software under the following conditions:
+
+Permissions: Any
+
+Conditions:
+* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+Limitations:
+* The software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and noninfringement.
+* In no event shall the authors or copyright holders be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the software or the use or other dealings in the software.
+
+
+**You can [contact me](https://cs-syd.eu/contact) for other arrangements (like a permanent license) on a case-to-case basis.**
diff --git a/output-test/Spec.hs b/output-test/Spec.hs
--- a/output-test/Spec.hs
+++ b/output-test/Spec.hs
@@ -9,15 +9,15 @@
 import Control.Exception
 import Control.Monad
 import Data.ByteString (ByteString)
-import qualified Data.ByteString as SB
-import qualified Data.ByteString.Char8 as SB8
+import qualified Data.ByteString.Builder as SBB
+import qualified Data.ByteString.Lazy as LB
 import Data.List
 import Data.Text (Text)
-import Rainbow
 import System.Exit
 import Test.QuickCheck
 import Test.Syd
 import Test.Syd.OptParse
+import Text.Colour
 
 data DangerousRecord = Cons1 {field :: String} | Cons2
 
@@ -30,12 +30,17 @@
 main = do
   sets <- getSettings
   testForest <- execTestDefM sets spec
-  _ <- runSpecForestInterleavedWithOutputSynchronously (settingColour sets) (settingFailFast sets) testForest
-  _ <- runSpecForestInterleavedWithOutputAsynchronously (settingColour sets) (settingFailFast sets) 8 testForest
+  tc <- case settingColour sets of
+    Just False -> pure WithoutColours
+    Just True -> pure With24BitColours
+    Nothing -> getTerminalCapabilitiesFromEnv
+
+  _ <- runSpecForestInterleavedWithOutputSynchronously tc (settingFailFast sets) testForest
+  _ <- runSpecForestInterleavedWithOutputAsynchronously tc (settingFailFast sets) 8 testForest
   rf1 <- timeItT $ runSpecForestSynchronously (settingFailFast sets) testForest
-  printOutputSpecForest (settingColour sets) rf1
+  printOutputSpecForest tc rf1
   rf2 <- timeItT $ runSpecForestAsynchronously (settingFailFast sets) 8 testForest
-  printOutputSpecForest (settingColour sets) rf2
+  printOutputSpecForest tc rf2
   pure ()
 
 spec :: Spec
@@ -101,6 +106,8 @@
     it "shouldNotBe" $ 3 `shouldNotBe` (3 :: Int)
     it "shouldSatisfy" $ (3 :: Int) `shouldSatisfy` even
     it "shouldNotSatisfy" $ (3 :: Int) `shouldNotSatisfy` odd
+    it "shouldSatisfyNamed" $ shouldSatisfyNamed (3 :: Int) "even" even
+    it "shouldNotSatisfyNamed" $ shouldNotSatisfyNamed (3 :: Int) "odd" odd
   pending "pending test"
   describe "Golden" $ do
     it "does not fail the suite when an exception happens while reading" $
@@ -143,7 +150,8 @@
       it "outputs the same as last time" $ do
         pureGoldenByteStringFile
           "test_resources/output.golden"
-          (SB8.intercalate (SB8.pack "\n") $ map SB.concat $ outputSpecForestByteString toByteStringsColors256 (Timed [] 0))
+          (LB.toStrict $ SBB.toLazyByteString $ renderResultReport With24BitColours (Timed [] 0))
+
   doNotRandomiseExecutionOrder $
     describe "Around" $
       do
diff --git a/src/Test/Syd.hs b/src/Test/Syd.hs
--- a/src/Test/Syd.hs
+++ b/src/Test/Syd.hs
@@ -2,6 +2,36 @@
 {-# LANGUAGE ScopedTypeVariables #-}
 {-# OPTIONS_GHC -fno-warn-duplicate-exports #-}
 
+-- |
+--
+-- = Modern testing of Haskell code using /sydtest/
+--
+-- For a full overview of features and comparisons, please see [the README](https://github.com/NorfairKing/sydtest#readme).
+--
+-- == What's in a test
+--
+-- To use @sydtest@, you don't necessarily need to know the following, but for advanced usage you definitely will.
+-- If you're just starting out, you can ignore this section and just follow the examples in the docs below.
+--
+-- * Every test is an instance of the 'IsTest' type class.
+--   A test can be a pure 'Bool', an @IO ()@, a 'GoldenTest', some combination of those, or any type that you can implement 'IsTest' for.
+--
+--  * @sydtest@ allows you to declare resources for use during your tests.
+--    This could be things like a database connection or a server to connect to, for example.
+--
+--  * Every resource is either an outer resource (set up once for a test group) or an inner resource (set up again for each test).
+--
+--  * Every 'IsTest' instance defines two associated types, an 'Arg1' type and an 'Arg2' type.
+--    These correspond to two function arguments. 'Arg1' corresponds to the first and 'Arg2' corresponds to the second.
+--    For example, @IO ()@ is an instance of @IsTest@, but @arg -> IO ()@ and @outerArgs -> innerArg -> IO ()@ are as well.
+--
+--        * For @outerArgs -> innerArgs -> IO ()@, 'Arg1' is @outerArgs@ and 'Arg2' is innerArgs.
+--        * For @arg -> IO ()@, 'Arg1' is '()' and 'Arg2' is @arg@.
+--        * For @IO ()@, both 'Arg1' and 'Arg2' are '()'.
+--
+--  * When using 'it' or 'specify' to define tests, the 'Arg1' and 'Arg2' arguments of the test that you pass in have to correspond to the outer and inner resources of your test suite, respectively.
+--
+--  * You can declare how to set up or tear down resources using the 'around' and 'aroundAll' functions.
 module Test.Syd
   ( -- * Top level API functions
     sydTest,
@@ -148,6 +178,7 @@
     TestDef,
     execTestDefM,
     runTestDefM,
+    IsTest (..),
 
     -- ** Test suite types
     TDef (..),
diff --git a/src/Test/Syd/Def/Around.hs b/src/Test/Syd/Def/Around.hs
--- a/src/Test/Syd/Def/Around.hs
+++ b/src/Test/Syd/Def/Around.hs
@@ -20,25 +20,45 @@
 import Test.Syd.SpecDef
 
 -- | Run a custom action before every spec item, to set up an inner resource 'c'.
-before :: IO c -> TestDefM a c e -> TestDefM a () e
+before ::
+  -- | The function to run before every test, to produce the inner resource
+  IO inner ->
+  TestDefM outers inner result ->
+  TestDefM outers () result
 before action = around (action >>=)
 
 -- | Run a custom action before every spec item without setting up any inner resources.
-before_ :: IO () -> TestDefM a c e -> TestDefM a c e
+before_ ::
+  -- | The function to run before every test
+  IO () ->
+  TestDefM outers inner result ->
+  TestDefM outers inner result
 before_ action = around_ (action >>)
 
 -- | Run a custom action after every spec item, using the inner resource 'c'.
-after :: (c -> IO ()) -> TestDefM a c e -> TestDefM a c e
+after ::
+  -- | The function to run after every test, using the inner resource
+  (inner -> IO ()) ->
+  TestDefM outers inner result ->
+  TestDefM outers inner result
 after action = aroundWith $ \e x -> e x `finally` action x
 
 -- | Run a custom action after every spec item without using any inner resources.
-after_ :: IO () -> TestDefM a c e -> TestDefM a c e
+after_ ::
+  -- | The function to run after every test
+  IO () ->
+  TestDefM outers inner result ->
+  TestDefM outers inner result
 after_ action = after $ \_ -> action
 
 -- | Run a custom action before and/or after every spec item, to provide access to an inner resource 'c'.
 --
 -- See the @FOOTGUN@ note in the docs for 'around_'.
-around :: ((c -> IO ()) -> IO ()) -> TestDefM a c e -> TestDefM a () e
+around ::
+  -- | The function to provide the inner resource around every test
+  ((inner -> IO ()) -> IO ()) ->
+  TestDefM outers inner result ->
+  TestDefM outers () result
 around action = aroundWith $ \e () -> action e
 
 -- | Run a custom action before and/or after every spec item without accessing any inner resources.
@@ -75,35 +95,53 @@
 --
 --
 -- Note: If you're interested in fixing this, talk to me, but only after GHC has gotten impredicative types because that will likely be a requirement.
-around_ :: (IO () -> IO ()) -> TestDefM a c e -> TestDefM a c e
+around_ ::
+  -- | The function to wrap every test with
+  (IO () -> IO ()) ->
+  TestDefM outers inner result ->
+  TestDefM outers inner result
 around_ action = aroundWith $ \e a -> action (e a)
 
 -- | Run a custom action before and/or after every spec item, to provide access to an inner resource 'c' while using the inner resource 'd'.
 --
 -- See the @FOOTGUN@ note in the docs for 'around_'.
-aroundWith :: forall a c d r. ((c -> IO ()) -> (d -> IO ())) -> TestDefM a c r -> TestDefM a d r
+aroundWith ::
+  forall newInner oldInner outers result.
+  ((newInner -> IO ()) -> (oldInner -> IO ())) ->
+  TestDefM outers newInner result ->
+  TestDefM outers oldInner result
 aroundWith func =
   aroundWith' $
-    \(takeAC :: HList a -> c -> IO ()) -- Just to make sure the 'a' is not ambiguous.
+    \(takeAC :: HList outers -> newInner -> IO ()) -- Just to make sure the 'a' is not ambiguous.
      a
      d ->
         func (\c -> takeAC a c) d
 
--- | Run a custom action before and/or after every spec item, to provide access to an inner resource 'c' while using the inner resource 'd' and any outer resource available.
-aroundWith' :: forall a c d r (u :: [Type]). HContains u a => ((a -> c -> IO ()) -> (a -> d -> IO ())) -> TestDefM u c r -> TestDefM u d r
+-- | Run a custom action around every spec item, to provide access to an inner resource 'newInner' while using the inner resource 'oldInner' and any outer resource available.
+aroundWith' ::
+  forall newInner oldInner outer result (outers :: [Type]).
+  HContains outers outer =>
+  -- | The function that provides the new inner resource using the old resource.
+  -- It can also use and modify the outer resource
+  ((outer -> newInner -> IO ()) -> (outer -> oldInner -> IO ())) ->
+  TestDefM outers newInner result ->
+  TestDefM outers oldInner result
 aroundWith' func (TestDefM rwst) = TestDefM $
   flip mapRWST rwst $ \inner -> do
     (res, s, forest) <- inner
+    -- a: outers
+    -- c: newInner
+    -- d: oldInner
     let modifyVal ::
           forall x.
-          HContains x a =>
-          (((HList x -> c -> IO ()) -> IO ()) -> IO TestRunResult) ->
-          ((HList x -> d -> IO ()) -> IO ()) ->
+          HContains x outer =>
+          (((HList x -> newInner -> IO ()) -> IO ()) -> IO TestRunResult) ->
+          ((HList x -> oldInner -> IO ()) -> IO ()) ->
           IO TestRunResult
         modifyVal takeSupplyXC supplyXD =
-          let supplyXC :: (HList x -> c -> IO ()) -> IO ()
+          let supplyXC :: (HList x -> newInner -> IO ()) -> IO ()
               supplyXC takeXC =
-                let takeXD :: HList x -> d -> IO ()
+                let takeXD :: HList x -> oldInner -> IO ()
                     takeXD x d =
                       let takeAC _ c = takeXC x c
                        in func takeAC (getElem x) d
@@ -111,7 +149,8 @@
            in takeSupplyXC supplyXC
 
         -- For this function to work recursively, the first parameter of the input and the output types must be the same
-        modifyTree :: forall x e. HContains x a => SpecDefTree x c e -> SpecDefTree x d e
+        modifyTree ::
+          forall x extra. HContains x outer => SpecDefTree x newInner extra -> SpecDefTree x oldInner extra
         modifyTree = \case
           DefDescribeNode t sdf -> DefDescribeNode t $ modifyForest sdf
           DefPendingNode t mr -> DefPendingNode t mr
@@ -123,8 +162,12 @@
           DefAfterAllNode f sdf -> DefAfterAllNode f $ modifyForest sdf
           DefParallelismNode f sdf -> DefParallelismNode f $ modifyForest sdf
           DefRandomisationNode f sdf -> DefRandomisationNode f $ modifyForest sdf
-        modifyForest :: forall x e. HContains x a => SpecDefForest x c e -> SpecDefForest x d e
+        modifyForest ::
+          forall x extra.
+          HContains x outer =>
+          SpecDefForest x newInner extra ->
+          SpecDefForest x oldInner extra
         modifyForest = map modifyTree
-    let forest' :: SpecDefForest u d ()
+    let forest' :: SpecDefForest outers oldInner ()
         forest' = modifyForest forest
     pure (res, s, forest')
diff --git a/src/Test/Syd/Def/AroundAll.hs b/src/Test/Syd/Def/AroundAll.hs
--- a/src/Test/Syd/Def/AroundAll.hs
+++ b/src/Test/Syd/Def/AroundAll.hs
@@ -18,35 +18,63 @@
 import Test.Syd.SpecDef
 
 -- | Run a custom action before all spec items in a group, to set up an outer resource 'a'.
-beforeAll :: IO a -> TestDefM (a ': l) c e -> TestDefM l c e
+beforeAll ::
+  -- | The function to run (once), beforehand, to produce the outer resource.
+  IO outer ->
+  TestDefM (outer ': otherOuters) inner result ->
+  TestDefM otherOuters inner result
 beforeAll action = wrapRWST $ \forest -> DefBeforeAllNode action forest
 
 -- | Run a custom action before all spec items in a group without setting up any outer resources.
-beforeAll_ :: IO () -> TestDefM a b e -> TestDefM a b e
+beforeAll_ ::
+  -- | The function to run (once), beforehand.
+  IO () ->
+  TestDefM outers inner result ->
+  TestDefM outers inner result
 beforeAll_ action = aroundAll_ (action >>)
 
 -- | Run a custom action before all spec items in a group, to set up an outer resource 'b' by using the outer resource 'a'.
-beforeAllWith :: (b -> IO a) -> TestDefM (a ': b ': l) c e -> TestDefM (b ': l) c e
+beforeAllWith ::
+  -- | The function to run (once), beforehand, to produce a new outer resource while using a previous outer resource
+  (previousOuter -> IO newOuter) ->
+  TestDefM (newOuter ': previousOuter ': otherOuters) inner result ->
+  TestDefM (previousOuter ': otherOuters) inner result
 beforeAllWith action = aroundAllWith $ \func b -> do
   a <- action b
   func a
 
 -- | Run a custom action after all spec items, using the outer resource 'a'.
-afterAll :: (a -> IO ()) -> TestDefM (a ': l) b e -> TestDefM (a ': l) b e
+afterAll ::
+  -- | The function to run (once), afterwards, using the outer resource.
+  (outer -> IO ()) ->
+  TestDefM (outer ': otherOuters) inner result ->
+  TestDefM (outer ': otherOuters) inner result
 afterAll func = afterAll' $ \(HCons a _) -> func a
 
 -- | Run a custom action after all spec items, using all the outer resources.
-afterAll' :: (HList l -> IO ()) -> TestDefM l b e -> TestDefM l b e
+afterAll' ::
+  -- | The function to run (once), afterwards, using all outer resources.
+  (HList outers -> IO ()) ->
+  TestDefM outers inner result ->
+  TestDefM outers inner result
 afterAll' func = wrapRWST $ \forest -> DefAfterAllNode func forest
 
 -- | Run a custom action after all spec items without using any outer resources.
-afterAll_ :: IO () -> TestDefM a b e -> TestDefM a b e
+afterAll_ ::
+  -- | The function to run (once), afterwards.
+  IO () ->
+  TestDefM outers inner result ->
+  TestDefM outers inner result
 afterAll_ action = afterAll' $ \_ -> action
 
 -- | Run a custom action before and/or after all spec items in group, to provide access to a resource 'a'.
 --
 -- See the @FOOTGUN@ note in the docs for 'around_'.
-aroundAll :: ((a -> IO ()) -> IO ()) -> TestDefM (a ': l) c e -> TestDefM l c e
+aroundAll ::
+  -- | The function that provides the outer resource (once), around the tests.
+  ((outer -> IO ()) -> IO ()) ->
+  TestDefM (outer ': otherOuters) inner result ->
+  TestDefM otherOuters inner result
 aroundAll func = wrapRWST $ \forest -> DefAroundAllNode func forest
 
 -- | Run a custom action before and/or after all spec items in a group without accessing any resources.
@@ -82,21 +110,30 @@
 -- In this case, the test will "just work", but it will be executed twice even if the output reports that it only passed once.
 --
 -- Note: If you're interested in fixing this, talk to me, but only after GHC has gotten impredicative types because that will likely be a requirement.
-aroundAll_ :: (IO () -> IO ()) -> TestDefM a b e -> TestDefM a b e
+aroundAll_ ::
+  -- | The function that wraps running the tests.
+  (IO () -> IO ()) ->
+  TestDefM outers inner result ->
+  TestDefM outers inner result
 aroundAll_ func = wrapRWST $ \forest -> DefWrapNode func forest
 
 -- | Run a custom action before and/or after all spec items in a group to provide access to a resource 'a' while using a resource 'b'
 --
 -- See the @FOOTGUN@ note in the docs for 'around_'.
 aroundAllWith ::
-  forall a b c l r.
-  ((a -> IO ()) -> (b -> IO ())) ->
-  TestDefM (a ': b ': l) c r ->
-  TestDefM (b ': l) c r
+  forall newOuter oldOuter otherOuters inner result.
+  -- | The function that provides the new outer resource (once), using the old outer resource.
+  ((newOuter -> IO ()) -> (oldOuter -> IO ())) ->
+  TestDefM (newOuter ': oldOuter ': otherOuters) inner result ->
+  TestDefM (oldOuter ': otherOuters) inner result
 aroundAllWith func = wrapRWST $ \forest -> DefAroundAllWithNode func forest
 
 -- | Declare a node in the spec def forest
-wrapRWST :: (TestForest a c -> TestTree b d) -> TestDefM a c l -> TestDefM b d l
+wrapRWST ::
+  -- | The wrapper node
+  (TestForest outers1 inner1 -> TestTree outers2 inner2) ->
+  TestDefM outers1 inner1 result ->
+  TestDefM outers2 inner2 result
 wrapRWST func (TestDefM rwst) = TestDefM $
   flip mapRWST rwst $ \inner -> do
     (res, s, forest) <- inner
diff --git a/src/Test/Syd/Def/Env.hs b/src/Test/Syd/Def/Env.hs
--- a/src/Test/Syd/Def/Env.hs
+++ b/src/Test/Syd/Def/Env.hs
@@ -38,7 +38,7 @@
 -- >       i <- runDB $ insert person
 -- >       person' <- runDB $ get i
 -- >       liftIO $ person' `shouldBe` person
-eit :: HasCallStack => String -> ReaderT env IO () -> TestDef l env
+eit :: HasCallStack => String -> ReaderT env IO () -> TestDef outers env
 eit s f = it s (\env -> runReaderT f env)
 
 -- | Helper function to run a property test with an 'env'.
diff --git a/src/Test/Syd/Def/SetupFunc.hs b/src/Test/Syd/Def/SetupFunc.hs
--- a/src/Test/Syd/Def/SetupFunc.hs
+++ b/src/Test/Syd/Def/SetupFunc.hs
@@ -1,5 +1,6 @@
 {-# LANGUAGE RankNTypes #-}
 
+-- | The 'SetupFunc' abstraction makes resource provider functions (of type @(a -> IO r) -> IO r@) composable.
 module Test.Syd.Def.SetupFunc where
 
 import Control.Category as Cat
@@ -8,19 +9,21 @@
 import Test.Syd.Def.TestDefM
 import Test.Syd.HList
 
--- | A function that can provide an 'a' given a 'b'.
+-- | A function that can provide an 'new' given an 'old'.
 --
--- You can think of this as a potentially-resource-aware version of 'b -> IO a'.
-newtype SetupFunc b a = SetupFunc
-  { unSetupFunc :: forall r. (a -> IO r) -> (b -> IO r)
+-- You can think of this as a potentially-resource-aware version of 'old -> IO new'.
+--
+-- This type has a monad instance, which means you can now compose setup functions using regular do-notation.
+newtype SetupFunc old new = SetupFunc
+  { unSetupFunc :: forall r. (new -> IO r) -> (old -> IO r)
   }
 
-instance Functor (SetupFunc c) where
+instance Functor (SetupFunc old) where
   fmap f (SetupFunc provideA) = SetupFunc $ \takeB c ->
     let takeA = \a -> takeB $ f a
      in provideA takeA c
 
-instance Applicative (SetupFunc c) where
+instance Applicative (SetupFunc old) where
   pure a = SetupFunc $ \aFunc _ -> aFunc a
   (SetupFunc provideF) <*> (SetupFunc provideA) = SetupFunc $ \takeB c ->
     provideF
@@ -33,7 +36,7 @@
       )
       c
 
-instance Monad (SetupFunc c) where
+instance Monad (SetupFunc old) where
   (SetupFunc provideA) >>= m = SetupFunc $ \takeB c ->
     provideA
       ( \a ->
@@ -42,7 +45,7 @@
       )
       c
 
-instance MonadIO (SetupFunc c) where
+instance MonadIO (SetupFunc old) where
   liftIO ioFunc = SetupFunc $ \takeA _ -> do
     ioFunc >>= takeA
 
@@ -57,13 +60,16 @@
 --
 -- * [Network.Wai.Handler.Warp.testWithApplication](https://hackage.haskell.org/package/warp-3.3.13/docs/Network-Wai-Handler-Warp.html#v:testWithApplication)
 -- * [Path.IO.withSystemTempDir](https://hackage.haskell.org/package/path-io-1.6.2/docs/Path-IO.html#v:withSystemTempDir)
-makeSimpleSetupFunc :: (forall r. (a -> IO r) -> IO r) -> SetupFunc () a
+makeSimpleSetupFunc ::
+  (forall result. (resource -> IO result) -> IO result) ->
+  SetupFunc () resource
 makeSimpleSetupFunc provideA = SetupFunc $ \takeA () -> provideA $ \a -> takeA a
 
 -- | Use a 'SetupFunc ()' as a simple provider function.
 --
 -- This is the opposite of the 'makeSimpleSetupFunc' function
-useSimpleSetupFunc :: SetupFunc () a -> (forall r. (a -> IO r) -> IO r)
+useSimpleSetupFunc ::
+  SetupFunc () resource -> (forall result. (resource -> IO result) -> IO result)
 useSimpleSetupFunc (SetupFunc provideAWithUnit) takeA = provideAWithUnit (\a -> takeA a) ()
 
 -- | Wrap a function that produces a 'SetupFunc' to into a 'SetupFunc'.
@@ -79,7 +85,9 @@
 -- > setupSomething :: SetupFunc () R
 -- > setupSomething :: B -> R -> SetupFunc () C
 -- > somehowCombine :: C -> R -> A
-wrapSetupFunc :: (b -> SetupFunc () a) -> SetupFunc b a
+wrapSetupFunc ::
+  (old -> SetupFunc () new) ->
+  SetupFunc old new
 wrapSetupFunc bFunc = SetupFunc $ \takeA b ->
   let SetupFunc provideAWithUnit = bFunc b
    in provideAWithUnit (\a -> takeA a) ()
@@ -87,14 +95,18 @@
 -- | Unwrap a 'SetupFunc' into a function that produces a 'SetupFunc'
 --
 -- This is the opposite of 'wrapSetupFunc'.
-unwrapSetupFunc :: SetupFunc b a -> (b -> SetupFunc () a)
+unwrapSetupFunc ::
+  SetupFunc old new -> (old -> SetupFunc () new)
 unwrapSetupFunc (SetupFunc provideAWithB) b = SetupFunc $ \takeA () ->
   provideAWithB (\a -> takeA a) b
 
 -- | Compose two setup functions.
 --
--- This is basically '(.)' but for 'SetupFunc's
-composeSetupFunc :: SetupFunc b a -> SetupFunc c b -> SetupFunc c a
+-- This is '(.)' but for 'SetupFunc's
+composeSetupFunc ::
+  SetupFunc newer newest ->
+  SetupFunc old newer ->
+  SetupFunc old newest
 composeSetupFunc (SetupFunc provideAWithB) (SetupFunc provideBWithC) = SetupFunc $ \takeA c ->
   provideBWithC
     ( \b ->
@@ -109,19 +121,32 @@
 --
 -- This is basically 'flip (.)' but for 'SetupFunc's.
 -- It's exactly 'flip composeSetupFunc'.
-connectSetupFunc :: SetupFunc c b -> SetupFunc b a -> SetupFunc c a
+connectSetupFunc ::
+  SetupFunc old newer ->
+  SetupFunc newer newest ->
+  SetupFunc old newest
 connectSetupFunc = flip composeSetupFunc
 
 -- | Use 'around' with a 'SetupFunc'
-setupAround :: SetupFunc () c -> TestDefM a c e -> TestDefM a () e
+setupAround ::
+  SetupFunc () inner ->
+  TestDefM outers inner result ->
+  TestDefM outers () result
 setupAround = setupAroundWith
 
 -- | Use 'aroundWith' with a 'SetupFunc'
-setupAroundWith :: SetupFunc d c -> TestDefM a c e -> TestDefM a d e
+setupAroundWith ::
+  SetupFunc oldInner newInner ->
+  TestDefM outers newInner result ->
+  TestDefM outers oldInner result
 setupAroundWith (SetupFunc f) = aroundWith f
 
 -- | Use 'aroundWith'' with a 'SetupFunc'
-setupAroundWith' :: HContains l a => (a -> SetupFunc d c) -> TestDefM l c e -> TestDefM l d e
+setupAroundWith' ::
+  HContains outers outer =>
+  (outer -> SetupFunc oldInner newInner) ->
+  TestDefM outers newInner result ->
+  TestDefM outers oldInner result
 setupAroundWith' setupFuncFunc = aroundWith' $ \takeAC a d ->
   let (SetupFunc provideCWithD) = setupFuncFunc a
    in provideCWithD (\c -> takeAC a c) d
diff --git a/src/Test/Syd/Def/Specify.hs b/src/Test/Syd/Def/Specify.hs
--- a/src/Test/Syd/Def/Specify.hs
+++ b/src/Test/Syd/Def/Specify.hs
@@ -59,11 +59,15 @@
 -- >         3 + 5 `shouldBe` 8
 -- >     it "adds 4 to 7 to result in 11" $
 -- >         4 + 7 `shouldBe` 11
-describe :: String -> TestDefM a b () -> TestDefM a b ()
+describe ::
+  -- | The test group description
+  String ->
+  TestDefM outers inner () ->
+  TestDefM outers inner ()
 describe s func = censor ((: []) . DefDescribeNode (T.pack s)) func
 
 -- TODO maybe we want to keep all tests below but replace them with a "Pending" instead.
-xdescribe :: String -> TestDefM a b () -> TestDefM a b ()
+xdescribe :: String -> TestDefM outers inner () -> TestDefM outers inner ()
 xdescribe s _ = pending s
 
 -- | Declare a test
@@ -155,7 +159,14 @@
 -- >             writeFile fp cts
 -- >             cts' <- readFile fp
 -- >             cts' `shouldBe` cts
-it :: forall outers test. (HasCallStack, IsTest test, Arg1 test ~ ()) => String -> test -> TestDefM outers (Arg2 test) ()
+it ::
+  forall outers inner test.
+  (HasCallStack, IsTest test, Arg1 test ~ (), Arg2 test ~ inner) =>
+  -- | The description of the test
+  String ->
+  -- | The test itself
+  test ->
+  TestDefM outers inner ()
 it s t = do
   sets <- ask
   let testDef =
@@ -170,9 +181,38 @@
           }
   tell [DefSpecifyNode (T.pack s) testDef ()]
 
-xit :: forall outers test. (HasCallStack, IsTest test, Arg1 test ~ ()) => String -> test -> TestDefM outers (Arg2 test) ()
+xit ::
+  forall outers inner test.
+  (HasCallStack, IsTest test, Arg1 test ~ (), Arg2 test ~ inner) =>
+  -- | The description of the test
+  String ->
+  -- | The test itself
+  test ->
+  TestDefM outers inner ()
 xit s _ = pending s
 
+-- | A synonym for 'it'
+specify ::
+  forall outers inner test.
+  (HasCallStack, IsTest test, Arg1 test ~ (), Arg2 test ~ inner) =>
+  -- | The description of the test
+  String ->
+  -- | The test itself
+  test ->
+  TestDefM outers inner ()
+specify = it
+
+-- | A synonym for 'xit'
+xspecify ::
+  forall outers inner test.
+  (HasCallStack, IsTest test, Arg1 test ~ (), Arg2 test ~ inner) =>
+  -- | The description of the test
+  String ->
+  -- | The test itself
+  test ->
+  TestDefM outers inner ()
+xspecify = xit
+
 -- | Declare a test that uses an outer resource
 --
 -- === Example usage:
@@ -184,7 +224,7 @@
 -- This is quite a rare use-case but here is an example anyway:
 --
 -- > beforeAll (pure 3) $ describe "addition" $
--- >     itWithBoth "adds 3 to 5 to result in 8" $ \i ->
+-- >     itWithOuter "adds 3 to 5 to result in 8" $ \i ->
 -- >         i + 5 == 8
 --
 --
@@ -194,7 +234,7 @@
 --
 -- > let setUpTempDir func = withSystemTempDir $ \tempDir -> func tempDir
 -- > in aroundAll setUpTempDir describe "readFile and writeFile" $
--- >     itWithBoth "reads back what it wrote for this example" $ \tempDir -> do
+-- >     itWithOuter "reads back what it wrote for this example" $ \tempDir -> do
 -- >         let cts = "hello world"
 -- >         let fp = tempDir </> "test.txt"
 -- >         writeFile fp cts
@@ -207,7 +247,7 @@
 -- This is quite a rare use-case but here is an example anyway:
 --
 -- > beforeAll (pure 3) $ describe "multiplication" $
--- >     itWithBoth "is commutative for 5" $ \i ->
+-- >     itWithOuter "is commutative for 5" $ \i ->
 -- >         i * 5 == 5 * 3
 --
 --
@@ -215,13 +255,19 @@
 --
 -- > let setUpTempDir func = withSystemTempDir $ \tempDir -> func tempDir
 -- > in aroundAll setUpTempDir describe "readFile and writeFile" $
--- >     itWithBoth "reads back what it wrote for this example" $ \tempDir ->
+-- >     itWithouter "reads back what it wrote for this example" $ \tempDir ->
 -- >         property $ \cts -> do
 -- >             let fp = tempDir </> "test.txt"
 -- >             writeFile fp cts
 -- >             cts' <- readFile fp
 -- >             cts' `shouldBe` cts
-itWithOuter :: (HasCallStack, IsTest test) => String -> test -> TestDefM (Arg2 test ': l) (Arg1 test) ()
+itWithOuter ::
+  (HasCallStack, IsTest test, Arg1 test ~ inner, Arg2 test ~ outer) =>
+  -- The test description
+  String ->
+  -- The test itself
+  test ->
+  TestDefM (outer ': otherOuters) inner ()
 itWithOuter s t = do
   sets <- ask
   let testDef =
@@ -235,9 +281,35 @@
           }
   tell [DefSpecifyNode (T.pack s) testDef ()]
 
-xitWithOuter :: (HasCallStack, IsTest test) => String -> test -> TestDefM (Arg2 test ': l) (Arg1 test) ()
+xitWithOuter ::
+  (HasCallStack, IsTest test, Arg1 test ~ inner, Arg2 test ~ outer) =>
+  -- The test description
+  String ->
+  -- The test itself
+  test ->
+  TestDefM (outer ': otherOuters) inner ()
 xitWithOuter s _ = pending s
 
+-- | A synonym for 'itWithOuter'
+specifyWithOuter ::
+  (HasCallStack, IsTest test, Arg1 test ~ inner, Arg2 test ~ outer) =>
+  -- The test description
+  String ->
+  -- The test itself
+  test ->
+  TestDefM (outer ': otherOuters) inner ()
+specifyWithOuter = itWithOuter
+
+-- | A synonym for 'xitWithOuter'
+xspecifyWithOuter ::
+  (HasCallStack, IsTest test, Arg1 test ~ inner, Arg2 test ~ outer) =>
+  -- The test description
+  String ->
+  -- The test itself
+  test ->
+  TestDefM (outer ': otherOuters) inner ()
+xspecifyWithOuter = xitWithOuter
+
 -- | Declare a test that uses both an inner and an outer resource
 --
 -- === Example usage:
@@ -285,7 +357,15 @@
 -- >             writeFile fp cts
 -- >             cts' <- readFile fp
 -- >             cts' `shouldBe` cts
-itWithBoth :: (HasCallStack, IsTest test) => String -> test -> TestDefM (Arg1 test ': l) (Arg2 test) ()
+itWithBoth ::
+  ( HasCallStack,
+    IsTest test,
+    Arg1 test ~ outer,
+    Arg2 test ~ inner
+  ) =>
+  String ->
+  test ->
+  TestDefM (outer ': otherOuters) inner ()
 itWithBoth s t = do
   sets <- ask
   let testDef =
@@ -299,9 +379,41 @@
           }
   tell [DefSpecifyNode (T.pack s) testDef ()]
 
-xitWithBoth :: (HasCallStack, IsTest test) => String -> test -> TestDefM (Arg1 test ': l) (Arg2 test) ()
+xitWithBoth ::
+  ( HasCallStack,
+    IsTest test,
+    Arg1 test ~ outer,
+    Arg2 test ~ inner
+  ) =>
+  String ->
+  test ->
+  TestDefM (outer ': otherOuters) inner ()
 xitWithBoth s _ = pending s
 
+-- | A synonym for 'itWithBoth'
+specifyWithBoth ::
+  ( HasCallStack,
+    IsTest test,
+    Arg1 test ~ outer,
+    Arg2 test ~ inner
+  ) =>
+  String ->
+  test ->
+  TestDefM (outer ': otherOuters) inner ()
+specifyWithBoth = itWithBoth
+
+-- | A synonym for 'xitWithBoth'
+xspecifyWithBoth ::
+  ( HasCallStack,
+    IsTest test,
+    Arg1 test ~ outer,
+    Arg2 test ~ inner
+  ) =>
+  String ->
+  test ->
+  TestDefM (outer ': otherOuters) inner ()
+xspecifyWithBoth = xitWithBoth
+
 -- | Declare a test that uses all outer resources
 --
 -- You will most likely never need this function, but in case you do:
@@ -313,7 +425,15 @@
 -- >     itWithAll "example" $
 -- >         \(HCons c (HCons i HNil) :: HList '[Char, Int]) () ->
 -- >             (c, i) `shouldeBe` ('a', 5)
-itWithAll :: (HasCallStack, IsTest test, Arg1 test ~ HList l) => String -> test -> TestDefM l (Arg2 test) ()
+itWithAll ::
+  ( HasCallStack,
+    IsTest test,
+    Arg1 test ~ HList outers,
+    Arg2 test ~ inner
+  ) =>
+  String ->
+  test ->
+  TestDefM outers inner ()
 itWithAll s t = do
   sets <- ask
   let testDef =
@@ -327,41 +447,45 @@
           }
   tell [DefSpecifyNode (T.pack s) testDef ()]
 
-xitWithAll :: (HasCallStack, IsTest test, Arg1 test ~ HList l) => String -> test -> TestDefM l (Arg2 test) ()
+xitWithAll ::
+  ( HasCallStack,
+    IsTest test,
+    Arg1 test ~ HList outers,
+    Arg2 test ~ inner
+  ) =>
+  String ->
+  test ->
+  TestDefM outers inner ()
 xitWithAll s _ = pending s
 
--- | A synonym for 'it'
-specify :: forall outers test. (HasCallStack, IsTest test, Arg1 test ~ ()) => String -> test -> TestDefM outers (Arg2 test) ()
-specify = it
-
-xspecify :: forall outers test. (HasCallStack, IsTest test, Arg1 test ~ ()) => String -> test -> TestDefM outers (Arg2 test) ()
-xspecify = xit
-
--- | A synonym for 'itWithOuter'
-specifyWithOuter :: (HasCallStack, IsTest test) => String -> test -> TestDefM (Arg2 test ': l) (Arg1 test) ()
-specifyWithOuter = itWithOuter
-
-xspecifyWithOuter :: (HasCallStack, IsTest test) => String -> test -> TestDefM (Arg2 test ': l) (Arg1 test) ()
-xspecifyWithOuter = xitWithOuter
-
--- | A synonym for 'itWithBoth'
-specifyWithBoth :: (HasCallStack, IsTest test) => String -> test -> TestDefM (Arg1 test ': l) (Arg2 test) ()
-specifyWithBoth = itWithBoth
-
-xspecifyWithBoth :: (HasCallStack, IsTest test) => String -> test -> TestDefM (Arg1 test ': l) (Arg2 test) ()
-xspecifyWithBoth = xitWithBoth
-
 -- | A synonym for 'itWithAll'
-specifyWithAll :: (HasCallStack, IsTest test, Arg1 test ~ HList l) => String -> test -> TestDefM l (Arg2 test) ()
+specifyWithAll ::
+  ( HasCallStack,
+    IsTest test,
+    Arg1 test ~ HList outers,
+    Arg2 test ~ inner
+  ) =>
+  String ->
+  test ->
+  TestDefM outers inner ()
 specifyWithAll = itWithAll
 
-xspecifyWithAll :: (HasCallStack, IsTest test, Arg1 test ~ HList l) => String -> test -> TestDefM l (Arg2 test) ()
+-- | A synonym for 'xitWithAll'
+xspecifyWithAll ::
+  ( HasCallStack,
+    IsTest test,
+    Arg1 test ~ HList outers,
+    Arg2 test ~ inner
+  ) =>
+  String ->
+  test ->
+  TestDefM outers inner ()
 xspecifyWithAll = xitWithAll
 
 -- | Declare a test that has not been written yet.
-pending :: String -> TestDefM a b ()
+pending :: String -> TestDefM outers inner ()
 pending s = tell [DefPendingNode (T.pack s) Nothing]
 
 -- | Declare a test that has not been written yet for the given reason.
-pendingWith :: String -> String -> TestDefM a b ()
+pendingWith :: String -> String -> TestDefM outers inner ()
 pendingWith s reason = tell [DefPendingNode (T.pack s) (Just (T.pack reason))]
diff --git a/src/Test/Syd/Def/TestDefM.hs b/src/Test/Syd/Def/TestDefM.hs
--- a/src/Test/Syd/Def/TestDefM.hs
+++ b/src/Test/Syd/Def/TestDefM.hs
@@ -18,6 +18,7 @@
 import Control.Monad.Random
 import Data.DList (DList)
 import qualified Data.DList as DList
+import Data.Kind
 import Data.Maybe
 import Data.Text (Text)
 import qualified Data.Text as T
@@ -31,32 +32,32 @@
 type Spec = SpecWith ()
 
 -- | A synonym for easy migration from hspec
-type SpecWith a = SpecM a ()
+type SpecWith inner = SpecM inner ()
 
 -- | A synonym for easy migration from hspec
-type SpecM a b = TestDefM '[] a b
+type SpecM inner result = TestDefM '[] inner result
 
 -- | A synonym for a test suite definition
-type TestDef a b = TestDefM a b ()
+type TestDef outer inner = TestDefM outer inner ()
 
 -- | The test definition monad
 --
 -- This type has three parameters:
 --
--- * @a@: The type of the result of `aroundAll`
--- * @b@: The type of the result of `around`
--- * @c@: The result
+-- * @outers@: A type-level list of the outer resources. These are resources that are prived once, around a group of tests. (This is the type of the results of `aroundAll`.)
+-- * @inner@: The inner resource. This is a resource that is set up around every test, and even every example of a property test. (This is the type of the result of `around`.)
+-- * @result@: The result ('TestDefM' is a monad.)
 --
 -- In practice, all of these three parameters should be '()' at the top level.
-newtype TestDefM a b c = TestDefM
-  { unTestDefM :: RWST TestRunSettings (TestForest a b) () IO c
+newtype TestDefM (outers :: [Type]) inner result = TestDefM
+  { unTestDefM :: RWST TestRunSettings (TestForest outers inner) () IO result
   }
-  deriving (Functor, Applicative, Monad, MonadIO, MonadReader TestRunSettings, MonadWriter (TestForest a b), MonadState ())
+  deriving (Functor, Applicative, Monad, MonadIO, MonadReader TestRunSettings, MonadWriter (TestForest outers inner), MonadState ())
 
-execTestDefM :: Settings -> TestDefM a b c -> IO (TestForest a b)
+execTestDefM :: Settings -> TestDefM outers inner result -> IO (TestForest outers inner)
 execTestDefM sets = fmap snd . runTestDefM sets
 
-runTestDefM :: Settings -> TestDefM a b c -> IO (c, TestForest a b)
+runTestDefM :: Settings -> TestDefM outers inner result -> IO (result, TestForest outers inner)
 runTestDefM sets defFunc = do
   let func = unTestDefM defFunc
   (a, _, testForest) <- runRWST func (toTestRunSettings sets) () -- TODO allow passing in settings from the command-line
@@ -79,7 +80,7 @@
       testRunSettingGoldenReset = settingGoldenReset
     }
 
-filterTestForest :: Maybe Text -> SpecDefForest a b c -> SpecDefForest a b c
+filterTestForest :: Maybe Text -> SpecDefForest outers inner result -> SpecDefForest outers inner result
 filterTestForest mf = fromMaybe [] . goForest DList.empty
   where
     goForest :: DList Text -> SpecDefForest a b c -> Maybe (SpecDefForest a b c)
@@ -112,7 +113,7 @@
       DefParallelismNode func sdf -> DefParallelismNode func <$> goForest dl sdf
       DefRandomisationNode func sdf -> DefRandomisationNode func <$> goForest dl sdf
 
-randomiseTestForest :: MonadRandom m => SpecDefForest a b c -> m (SpecDefForest a b c)
+randomiseTestForest :: MonadRandom m => SpecDefForest outers inner result -> m (SpecDefForest outers inner result)
 randomiseTestForest = goForest
   where
     goForest :: MonadRandom m => SpecDefForest a b c -> m (SpecDefForest a b c)
diff --git a/src/Test/Syd/Output.hs b/src/Test/Syd/Output.hs
--- a/src/Test/Syd/Output.hs
+++ b/src/Test/Syd/Output.hs
@@ -9,8 +9,8 @@
 
 import Control.Monad.Reader
 import Data.Algorithm.Diff
-import Data.ByteString (ByteString)
-import qualified Data.ByteString as SB
+import Data.ByteString.Builder (Builder)
+import qualified Data.ByteString.Builder as SBB
 import qualified Data.ByteString.Char8 as SB8
 import Data.List
 import Data.List.Split (splitWhen)
@@ -20,28 +20,25 @@
 import Data.Text (Text)
 import qualified Data.Text as T
 import GHC.Stack
-import Rainbow
-import Rainbow.Types (Chunk (..))
 import Safe
 import Test.QuickCheck.IO ()
 import Test.Syd.Run
 import Test.Syd.SpecDef
 import Test.Syd.SpecForest
+import Text.Colour
 import Text.Printf
 
-printOutputSpecForest :: Maybe Bool -> Timed ResultForest -> IO ()
-printOutputSpecForest mColour results = do
-  byteStringMaker <- case mColour of
-    Just False -> pure toByteStringsColors0
-    Just True -> pure toByteStringsColors256
-    Nothing -> liftIO byteStringMakerFromEnvironment
-  let bytestrings = outputSpecForestByteString byteStringMaker results
-  forM_ bytestrings $ \bs -> do
-    mapM_ SB.putStr bs
+printOutputSpecForest :: TerminalCapabilities -> Timed ResultForest -> IO ()
+printOutputSpecForest tc results = do
+  forM_ (outputResultReport results) $ \chunks -> do
+    putChunksWith tc chunks
     SB8.putStrLn ""
 
-outputSpecForestByteString :: (Chunk -> [ByteString] -> [ByteString]) -> Timed ResultForest -> [[ByteString]]
-outputSpecForestByteString byteStringMaker results = map (chunksToByteStrings byteStringMaker) (outputResultReport results)
+renderResultReport :: TerminalCapabilities -> Timed ResultForest -> Builder
+renderResultReport tc rf =
+  mconcat $
+    intersperse (SBB.char7 '\n') $
+      map (renderChunks tc) (outputResultReport rf)
 
 outputResultReport :: Timed ResultForest -> [[Chunk]]
 outputResultReport trf@(Timed rf _) =
@@ -158,7 +155,10 @@
               ]
             ],
             [ pad
-                [chunk (T.pack (printf "passed for all of %d inputs" w))]
+                [ chunk "passed for all of ",
+                  fore green $ chunk (T.pack (printf "%d" w)),
+                  " inputs."
+                ]
               | testRunResultStatus == TestPassed,
                 w <- maybeToList testRunResultNumTests
             ],
@@ -350,7 +350,7 @@
 outputEqualityAssertionFailed :: String -> String -> [[Chunk]]
 outputEqualityAssertionFailed actual expected =
   let diff = getDiff actual expected -- TODO use 'getGroupedDiff' instead, but then we need to fix the 'splitWhen' below
-      splitLines = splitWhen ((== "\n") . _yarn)
+      splitLines = splitWhen ((== "\n") . chunkText)
       chunksLinesWithHeader :: Chunk -> [[Chunk]] -> [[Chunk]]
       chunksLinesWithHeader header = \case
         [cs] -> [header : cs]
@@ -405,7 +405,7 @@
     [ [ [chunk "Predicate failed, but should have succeeded, on this value:"],
         [chunk (T.pack actual)]
       ],
-      concat $ [[chunk "Predicate: "]] : [stringChunks name | name <- maybeToList mName]
+      concat [map (chunk "Predicate: " :) (stringChunks name) | name <- maybeToList mName]
     ]
 
 outputPredicateFailAssertionFailed :: String -> Maybe String -> [[Chunk]]
@@ -414,7 +414,7 @@
     [ [ [chunk "Predicate succeeded, but should have failed, on this value:"],
         [chunk (T.pack actual)]
       ],
-      concat $ [[chunk "Predicate: "]] : [stringChunks name | name <- maybeToList mName]
+      concat [map (chunk "Predicate: " :) (stringChunks name) | name <- maybeToList mName]
     ]
 
 mContextChunks :: Maybe String -> [[Chunk]]
@@ -433,7 +433,7 @@
   TestPassed -> Nothing
   TestFailed -> Just [[chunk "Failure"]]
 
-statusColour :: TestStatus -> Radiant
+statusColour :: TestStatus -> Colour
 statusColour = \case
   TestPassed -> green
   TestFailed -> red
@@ -481,8 +481,8 @@
 paddingSize :: Int
 paddingSize = 2
 
-orange :: Radiant
-orange = color256 166
+orange :: Colour
+orange = colour256 166
 
-darkRed :: Radiant
-darkRed = color256 160
+darkRed :: Colour
+darkRed = colour256 160
diff --git a/src/Test/Syd/Runner.hs b/src/Test/Syd/Runner.hs
--- a/src/Test/Syd/Runner.hs
+++ b/src/Test/Syd/Runner.hs
@@ -13,6 +13,9 @@
 where
 
 import Control.Concurrent (getNumCapabilities)
+import Control.Monad
+import Control.Monad.IO.Class
+import qualified Data.ByteString.Char8 as SB8
 import System.Environment
 import System.Mem (performGC)
 import Test.Syd.Def
@@ -22,6 +25,7 @@
 import Test.Syd.Runner.Asynchronous
 import Test.Syd.Runner.Synchronous
 import Test.Syd.SpecDef
+import Text.Colour
 import Text.Printf
 
 sydTestResult :: Settings -> TestDefM '[] () r -> IO (Timed ResultForest)
@@ -37,13 +41,32 @@
 sydTestOnce :: Settings -> TestDefM '[] () r -> IO (Timed ResultForest)
 sydTestOnce sets spec = do
   specForest <- execTestDefM sets spec
+  tc <- case settingColour sets of
+    Just False -> pure WithoutColours
+    Just True -> pure With8BitColours
+    Nothing -> getTerminalCapabilitiesFromEnv
   withArgs [] $ case settingThreads sets of
-    Synchronous -> runSpecForestInterleavedWithOutputSynchronously (settingColour sets) (settingFailFast sets) specForest
+    Synchronous -> runSpecForestInterleavedWithOutputSynchronously tc (settingFailFast sets) specForest
     ByCapabilities -> do
       i <- getNumCapabilities
-      runSpecForestInterleavedWithOutputAsynchronously (settingColour sets) (settingFailFast sets) i specForest
+
+      when (i == 1) $ do
+        let outputLine :: [Chunk] -> IO ()
+            outputLine lineChunks = liftIO $ do
+              putChunksWith tc lineChunks
+              SB8.putStrLn ""
+        mapM_
+          ( outputLine
+              . (: [])
+              . fore red
+          )
+          [ chunk "WARNING: Only one CPU core detected, make sure to compile your test suite with these ghc options:",
+            chunk "         -threaded -rtsopts -with-rtsopts=-N",
+            chunk "         (This is important for correctness as well as speed, as a parallell test suite can find thread safety problems.)"
+          ]
+      runSpecForestInterleavedWithOutputAsynchronously tc (settingFailFast sets) i specForest
     Asynchronous i ->
-      runSpecForestInterleavedWithOutputAsynchronously (settingColour sets) (settingFailFast sets) i specForest
+      runSpecForestInterleavedWithOutputAsynchronously tc (settingFailFast sets) i specForest
 
 sydTestIterations :: Maybe Int -> Settings -> TestDefM '[] () r -> IO (Timed ResultForest)
 sydTestIterations totalIterations sets spec =
@@ -72,5 +95,9 @@
                 | otherwise -> go $ succ iteration
 
     rf <- go 0
-    printOutputSpecForest (settingColour sets) rf
+    tc <- case settingColour sets of
+      Just False -> pure WithoutColours
+      Just True -> pure With8BitColours
+      Nothing -> getTerminalCapabilitiesFromEnv
+    printOutputSpecForest tc rf
     pure rf
diff --git a/src/Test/Syd/Runner/Asynchronous.hs b/src/Test/Syd/Runner/Asynchronous.hs
--- a/src/Test/Syd/Runner/Asynchronous.hs
+++ b/src/Test/Syd/Runner/Asynchronous.hs
@@ -12,20 +12,19 @@
 import Control.Concurrent.Async
 import Control.Exception
 import Control.Monad.Reader
-import qualified Data.ByteString as SB
 import qualified Data.ByteString.Char8 as SB8
 import Data.IORef
 import Data.Maybe
 import Data.Set (Set)
 import qualified Data.Set as S
 import qualified Data.Text as T
-import Rainbow
 import Test.QuickCheck.IO ()
 import Test.Syd.HList
 import Test.Syd.Output
 import Test.Syd.Run
 import Test.Syd.SpecDef
 import Test.Syd.SpecForest
+import Text.Colour
 
 runSpecForestAsynchronously :: Bool -> Int -> TestForest '[] () -> IO ResultForest
 runSpecForestAsynchronously failFast nbThreads testForest = do
@@ -36,12 +35,12 @@
   ((), resultForest) <- concurrently runRunner runPrinter
   pure resultForest
 
-runSpecForestInterleavedWithOutputAsynchronously :: Maybe Bool -> Bool -> Int -> TestForest '[] () -> IO (Timed ResultForest)
-runSpecForestInterleavedWithOutputAsynchronously mColour failFast nbThreads testForest = do
+runSpecForestInterleavedWithOutputAsynchronously :: TerminalCapabilities -> Bool -> Int -> TestForest '[] () -> IO (Timed ResultForest)
+runSpecForestInterleavedWithOutputAsynchronously tc failFast nbThreads testForest = do
   handleForest <- makeHandleForest testForest
   failFastVar <- newEmptyMVar
   let runRunner = runner failFast nbThreads failFastVar handleForest
-      runPrinter = liftIO $ printer mColour failFastVar handleForest
+      runPrinter = liftIO $ printer tc failFastVar handleForest
   ((), resultForest) <- concurrently runRunner runPrinter
   pure resultForest
 
@@ -110,17 +109,13 @@
         DefRandomisationNode _ sdf -> goForest p a sdf
   goForest Parallel HNil handleForest
 
-printer :: Maybe Bool -> MVar () -> HandleForest '[] () -> IO (Timed ResultForest)
-printer mColour failFastVar handleForest = do
-  byteStringMaker <- case mColour of
-    Just False -> pure toByteStringsColors0
-    Just True -> pure toByteStringsColors256
-    Nothing -> liftIO byteStringMakerFromEnvironment
+printer :: TerminalCapabilities -> MVar () -> HandleForest '[] () -> IO (Timed ResultForest)
+printer tc failFastVar handleForest = do
   let outputLine :: [Chunk] -> IO ()
-      outputLine lineChunks = do
-        let bss = chunksToByteStrings byteStringMaker lineChunks
-        mapM_ SB.putStr bss
+      outputLine lineChunks = liftIO $ do
+        putChunksWith tc lineChunks
         SB8.putStrLn ""
+
       treeWidth :: Int
       treeWidth = specForestWidth handleForest
 
diff --git a/src/Test/Syd/Runner/Synchronous.hs b/src/Test/Syd/Runner/Synchronous.hs
--- a/src/Test/Syd/Runner/Synchronous.hs
+++ b/src/Test/Syd/Runner/Synchronous.hs
@@ -10,16 +10,15 @@
 
 import Control.Exception
 import Control.Monad.IO.Class
-import qualified Data.ByteString as SB
 import qualified Data.ByteString.Char8 as SB8
 import qualified Data.Text as T
-import Rainbow
 import Test.Syd.HList
 import Test.Syd.Output
 import Test.Syd.Run
 import Test.Syd.Runner.Wrappers
 import Test.Syd.SpecDef
 import Test.Syd.SpecForest
+import Text.Colour
 
 runSpecForestSynchronously :: Bool -> TestForest '[] () -> IO ResultForest
 runSpecForestSynchronously failFast = fmap extractNext . goForest HNil
@@ -59,18 +58,12 @@
       DefParallelismNode _ sdf -> fmap SubForestNode <$> goForest l sdf -- Ignore, it's synchronous anyway
       DefRandomisationNode _ sdf -> fmap SubForestNode <$> goForest l sdf
 
-runSpecForestInterleavedWithOutputSynchronously :: Maybe Bool -> Bool -> TestForest '[] () -> IO (Timed ResultForest)
-runSpecForestInterleavedWithOutputSynchronously mColour failFast testForest = do
-  byteStringMaker <- case mColour of
-    Just False -> pure toByteStringsColors0
-    Just True -> pure toByteStringsColors256
-    Nothing -> liftIO byteStringMakerFromEnvironment
+runSpecForestInterleavedWithOutputSynchronously :: TerminalCapabilities -> Bool -> TestForest '[] () -> IO (Timed ResultForest)
+runSpecForestInterleavedWithOutputSynchronously tc failFast testForest = do
   let outputLine :: [Chunk] -> IO ()
-      outputLine lineChunks = do
-        let bss = chunksToByteStrings byteStringMaker lineChunks
-        liftIO $ do
-          mapM_ SB.putStr bss
-          SB8.putStrLn ""
+      outputLine lineChunks = liftIO $ do
+        putChunksWith tc lineChunks
+        SB8.putStrLn ""
       treeWidth :: Int
       treeWidth = specForestWidth testForest
   let pad :: Int -> [Chunk] -> [Chunk]
diff --git a/src/Test/Syd/SpecDef.hs b/src/Test/Syd/SpecDef.hs
--- a/src/Test/Syd/SpecDef.hs
+++ b/src/Test/Syd/SpecDef.hs
@@ -24,36 +24,85 @@
 import Test.Syd.Run
 import Test.Syd.SpecForest
 
-data TDef v = TDef {testDefVal :: v, testDefCallStack :: CallStack}
+data TDef value = TDef {testDefVal :: value, testDefCallStack :: CallStack}
   deriving (Functor, Foldable, Traversable)
 
-type TestForest a c = SpecDefForest a c ()
+type TestForest outers inner = SpecDefForest outers inner ()
 
-type TestTree a c = SpecDefTree a c ()
+type TestTree outers inner = SpecDefTree outers inner ()
 
-type SpecDefForest (a :: [Type]) c e = [SpecDefTree a c e]
+type SpecDefForest (outers :: [Type]) inner extra = [SpecDefTree outers inner extra]
 
-data SpecDefTree (a :: [Type]) c e where -- a: input from 'aroundAll', c: input from 'around', e: extra
+-- | A tree of tests
+--
+-- This type has three parameters:
+--
+-- * @outers@: A type-level list of the outer resources. These are resources that are prived once, around a group of tests. (This is the type of the results of `aroundAll`.)
+-- * @inner@: The inner resource. This is a resource that is set up around every test, and even every example of a property test. (This is the type of the result of `around`.)
+-- * @result@: The result ('TestDefM' is a monad.)
+--
+-- In practice, all of these three parameters should be '()' at the top level.
+--
+-- When you're just using sydtest and not writing a library for sydtest, you probably don't even want to concern yourself with this type.
+data SpecDefTree (outers :: [Type]) inner extra where
+  -- | Define a test
   DefSpecifyNode ::
+    -- | The description of the test
     Text ->
-    TDef (((HList a -> c -> IO ()) -> IO ()) -> IO TestRunResult) ->
-    e ->
-    SpecDefTree a c e -- A test with its description
-  DefPendingNode :: Text -> Maybe Text -> SpecDefTree a c e
-  DefDescribeNode :: Text -> SpecDefForest a c e -> SpecDefTree a c e -- A description
-  DefWrapNode :: (IO () -> IO ()) -> SpecDefForest a c e -> SpecDefTree a c e
-  DefBeforeAllNode :: IO a -> SpecDefForest (a ': l) c e -> SpecDefTree l c e
+    -- | How the test can be run given a function that provides the resources
+    TDef (((HList outers -> inner -> IO ()) -> IO ()) -> IO TestRunResult) ->
+    extra ->
+    SpecDefTree outers inner extra
+  -- | Define a pending test
+  DefPendingNode ::
+    -- | The description of the test
+    Text ->
+    -- | The reason why the test is pending
+    Maybe Text ->
+    SpecDefTree outers inner extra
+  -- | Group tests using a description
+  DefDescribeNode ::
+    -- | The description
+    Text ->
+    SpecDefForest outers inner extra ->
+    SpecDefTree outers inner extra
+  DefWrapNode ::
+    -- | The function that wraps running the tests.
+    (IO () -> IO ()) ->
+    SpecDefForest outers inner extra ->
+    SpecDefTree outers inner extra
+  DefBeforeAllNode ::
+    -- | The function to run (once), beforehand, to produce the outer resource.
+    IO outer ->
+    SpecDefForest (outer ': otherOuters) inner extra ->
+    SpecDefTree otherOuters inner extra
   DefAroundAllNode ::
-    ((a -> IO ()) -> IO ()) ->
-    SpecDefForest (a ': l) c e ->
-    SpecDefTree l c e
+    -- | The function that provides the outer resource (once), around the tests.
+    ((outer -> IO ()) -> IO ()) ->
+    SpecDefForest (outer ': otherOuters) inner extra ->
+    SpecDefTree otherOuters inner extra
   DefAroundAllWithNode ::
-    ((b -> IO ()) -> (a -> IO ())) ->
-    SpecDefForest (b ': a ': l) c e ->
-    SpecDefTree (a ': l) c e
-  DefAfterAllNode :: (HList a -> IO ()) -> SpecDefForest a c e -> SpecDefTree a c e
-  DefParallelismNode :: Parallelism -> SpecDefForest a c e -> SpecDefTree a c e
-  DefRandomisationNode :: ExecutionOrderRandomisation -> SpecDefForest a c e -> SpecDefTree a c e
+    -- | The function that provides the new outer resource (once), using the old outer resource.
+    ((newOuter -> IO ()) -> (oldOuter -> IO ())) ->
+    SpecDefForest (newOuter ': oldOuter ': otherOuters) inner extra ->
+    SpecDefTree (oldOuter ': otherOuters) inner extra
+  DefAfterAllNode ::
+    -- | The function to run (once), afterwards, using all outer resources.
+    (HList outers -> IO ()) ->
+    SpecDefForest outers inner extra ->
+    SpecDefTree outers inner extra
+  -- | Control the level of parallelism for a given group of tests
+  DefParallelismNode ::
+    -- | The level of parallelism
+    Parallelism ->
+    SpecDefForest outers inner extra ->
+    SpecDefTree outers inner extra
+  -- | Control the execution order randomisation for a given group of tests
+  DefRandomisationNode ::
+    -- | The execution order randomisation
+    ExecutionOrderRandomisation ->
+    SpecDefForest outers inner extra ->
+    SpecDefTree outers inner extra
 
 instance Functor (SpecDefTree a c) where
   fmap :: forall e f. (e -> f) -> SpecDefTree a c e -> SpecDefTree a c f
diff --git a/sydtest.cabal b/sydtest.cabal
--- a/sydtest.cabal
+++ b/sydtest.cabal
@@ -1,21 +1,21 @@
 cabal-version: 1.12
 
--- This file has been generated from package.yaml by hpack version 0.34.2.
+-- This file has been generated from package.yaml by hpack version 0.34.4.
 --
 -- see: https://github.com/sol/hpack
 
 name:           sydtest
-version:        0.0.0.0
-synopsis:       An advanced modern testing framework for Haskell with good defaults and advanced testing features.
-description:    An advanced modern testing framework for Haskell with good defaults and advanced testing features. Sydtest aims to make the common easy and the hard possible. See https://github.com/NorfairKing/sydtest#readme for more information.
+version:        0.1.0.0
+synopsis:       A modern testing framework for Haskell with good defaults and advanced testing features.
+description:    A modern testing framework for Haskell with good defaults and advanced testing features. Sydtest aims to make the common easy and the hard possible. See https://github.com/NorfairKing/sydtest#readme for more information.
 category:       Testing
 homepage:       https://github.com/NorfairKing/sydtest#readme
 bug-reports:    https://github.com/NorfairKing/sydtest/issues
 author:         Tom Sydney Kerckhove
 maintainer:     syd@cs-syd.eu
-copyright:      Copyright (c) 2020 Tom Sydney Kerckhove
+copyright:      Copyright (c) 2020-2021 Tom Sydney Kerckhove
 license:        OtherLicense
-license-file:   LICENSE
+license-file:   LICENSE.md
 build-type:     Simple
 
 source-repository head
@@ -65,9 +65,9 @@
     , path-io
     , pretty-show
     , quickcheck-io
-    , rainbow
     , random-shuffle
     , safe
+    , safe-coloured-text
     , split
     , text
     , yaml
@@ -88,7 +88,7 @@
     , bytestring
     , path
     , path-io
-    , rainbow
+    , safe-coloured-text
     , sydtest
     , text
   default-language: Haskell2010
@@ -117,7 +117,7 @@
     , bytestring
     , path
     , path-io
-    , rainbow
+    , safe-coloured-text
     , stm
     , sydtest
   default-language: Haskell2010
diff --git a/test/Test/Syd/GoldenSpec.hs b/test/Test/Syd/GoldenSpec.hs
--- a/test/Test/Syd/GoldenSpec.hs
+++ b/test/Test/Syd/GoldenSpec.hs
@@ -2,11 +2,11 @@
 
 module Test.Syd.GoldenSpec (spec) where
 
-import qualified Data.ByteString as SB
-import qualified Data.ByteString.Char8 as SB8
-import Rainbow
+import qualified Data.ByteString.Builder as SBB
+import qualified Data.ByteString.Lazy as LB
 import Test.Syd
 import Test.Syd.OptParse
+import Text.Colour
 
 spec :: Spec
 spec = do
@@ -14,6 +14,6 @@
     it "outputs the same as last time" $ do
       pureGoldenByteStringFile
         "test_resources/output.golden"
-        (SB8.intercalate "\n" $ map SB.concat $ outputSpecForestByteString toByteStringsColors256 (Timed [] 0))
+        (LB.toStrict $ SBB.toLazyByteString $ renderResultReport With24BitColours (Timed [] 0))
   describe "defaultSettings" $ do
     it "is the same thing as last time" $ goldenPrettyShowInstance "test_resources/defaultSettings-show.golden" defaultSettings
