diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,5 +1,18 @@
 # Changelog for Shakebook
 
+## (v0.2.2.0)
+
+* Depend on new experimental library
+  [shake-plus](https://hackage.haskell.org/package/shake-plus), that includes
+re-exports of the Shake API based on the
+[path](https://hackage.haskell.org/package/path) library for well-typed paths
+and the [within](https://hackage.haskell.org/package/within) library which
+introduces the `Within` type for representing a `Path` within a `Path`.
+* Zipper functionality moved to external library
+  [zipper-extra](https://hackage.haskell.org/package/zipper-extra).
+* `Shakebook` and `ShakebookA` dropped in favour of `ShakePlus` and `RAction`
+   from `shake-plus`.
+
 ## (v0.2.0.3)
 
 * Add logging to Shakebook's monads via RIO's logging methods.
diff --git a/app/Main.hs b/app/Main.hs
--- a/app/Main.hs
+++ b/app/Main.hs
@@ -1,8 +1,9 @@
+{-# LANGUAGE TemplateHaskell #-}
 module Main where
 
-import           Development.Shake
-import           Development.Shake.FilePath
+import           Development.Shake.Plus
 import           Options.Applicative
+import           Path
 import           RIO
 import qualified RIO.Text                   as T
 import           Shakebook
@@ -31,11 +32,15 @@
    <> progDesc "Creates a simple blog from source with default settings."
    <> header "shakebook-simple-blog - A simple blog using standard shakebook conventions." )
 
+indexHTML :: Path Rel File
+indexHTML = $(mkRelFile "index.html")
+
 main :: IO ()
 main = do
   (x :: SimpleOpts) <- execParser opts
-
-  app $ SbConfig (srcDir x) (outDir x) (T.pack $ baseUrl x) defaultMarkdownReaderOptions defaultHtml5WriterOptions (ppp x)
+  s' <- parseRelDir (srcDir x)
+  o' <- parseRelDir (outDir x)
+  app $ SbConfig s' o' (T.pack $ baseUrl x) defaultMarkdownReaderOptions defaultHtml5WriterOptions (ppp x)
 
 app :: SbConfig -> IO ()
 app sbc =  do
@@ -43,19 +48,19 @@
     lf <- newLogFunc logOptions'
     let f = ShakebookEnv (fst lf) sbc
 
-    shake (shakeOptions { shakeVerbosity = Chatty, shakeLintInside = ["\\"] }) $ do
+    shake shakeOptions $ do
 
       want ["all"]
 
-      runShakebook f $ view sbConfigL >>= \SbConfig {..} -> do
+      runShakePlus f $ view sbConfigL >>= \SbConfig {..} -> do
 
         defaultCleanPhony
 
         defaultSinglePagePattern "index.html" "templates/index.html"
                                  (affixRecentPosts ["posts/md"] 5 defaultEnrichPost)
 
-        liftRules $ phony "index" $ need [sbOutDir </> "index.html"]
+        phony "index" $ needP [sbOutDir </> indexHTML]
 
-      phony "all" $ need ["index"]
+        phony "all" $ need ["index"]
 
     snd lf
diff --git a/shakebook.cabal b/shakebook.cabal
--- a/shakebook.cabal
+++ b/shakebook.cabal
@@ -4,10 +4,10 @@
 --
 -- see: https://github.com/sol/hpack
 --
--- hash: f07d9fe04c2a84023b693534da2123af9c52934fba5c71935b6a93bb57ea1005
+-- hash: 81875743197319be0e2b8ba86e21804725439f8864b08a8528d09354faeb63de
 
 name:           shakebook
-version:        0.2.0.4
+version:        0.2.2.0
 synopsis:       Shake-based technical documentation generator; HTML & PDF
 description:    Shakebook is a documentation generator aimed at covering all the bases for mathematical, technical and scientific diagrams and typesetting. Shakebook provides combinators for taking markdown files and combining them into documents, but allowing the user to control how. Shakebook provides general combinators for templating single pages, cofree comonads for representing tables of contents, and zipper comonads for representing pagers.
 category:       Web
@@ -29,8 +29,8 @@
       Shakebook.Data
       Shakebook.Defaults
       Shakebook.Feed
+      Shakebook.Mustache
       Shakebook.Rules
-      Shakebook.Zipper
   other-modules:
       Paths_shakebook
   hs-source-dirs:
@@ -41,21 +41,25 @@
       aeson
     , base >=4.7 && <5
     , comonad
-    , comonad-extras
     , doctemplates
     , extra
     , feed
     , free
     , lens
     , lens-aeson
+    , mustache
     , pandoc
     , pandoc-types
+    , path
     , relude
     , rio
     , shake
+    , shake-plus
     , slick
     , split
     , text-time
+    , within
+    , zipper-extra
   default-language: Haskell2010
 
 executable shakebook-simple-blog
@@ -70,23 +74,27 @@
       aeson
     , base >=4.7 && <5
     , comonad
-    , comonad-extras
     , doctemplates
     , extra
     , feed
     , free
     , lens
     , lens-aeson
+    , mustache
     , optparse-applicative
     , pandoc
     , pandoc-types
+    , path
     , relude
     , rio
     , shake
+    , shake-plus
     , shakebook
     , slick
     , split
     , text-time
+    , within
+    , zipper-extra
   default-language: Haskell2010
 
 test-suite shakebook-test
@@ -102,22 +110,26 @@
       aeson
     , base >=4.7 && <5
     , comonad
-    , comonad-extras
     , doctemplates
     , extra
     , feed
     , free
     , lens
     , lens-aeson
+    , mustache
     , pandoc
     , pandoc-types
+    , path
     , relude
     , rio
     , shake
+    , shake-plus
     , shakebook
     , slick
     , split
     , tasty
     , tasty-golden
     , text-time
+    , within
+    , zipper-extra
   default-language: Haskell2010
diff --git a/src/Shakebook/Conventions.hs b/src/Shakebook/Conventions.hs
--- a/src/Shakebook/Conventions.hs
+++ b/src/Shakebook/Conventions.hs
@@ -51,7 +51,7 @@
 
 import           Control.Comonad.Cofree
 import           Control.Comonad.Store
-import           Control.Comonad.Store.Zipper
+import           Control.Comonad.Zipper.Extra
 import           Control.Lens                 hiding ((:<))
 import           Control.Monad.Extra
 import           Data.Aeson                   as A
@@ -66,7 +66,6 @@
 import qualified RIO.Vector                   as V
 import           Shakebook.Aeson
 import           Shakebook.Data
-import           Shakebook.Zipper
 import           Text.Pandoc.Highlighting
 
 
@@ -230,10 +229,11 @@
                    . withPosts (extract xs) $ Object mempty
 
 
-genIndexPageData :: [Value]
+genIndexPageData :: MonadThrow m
+                 => [Value]
                  -> Text
                  -> (Text -> Text)
                  -> Int
-                 -> Maybe (Zipper [] Value)
-genIndexPageData xs g h n = extend (genPageData g h) <$> paginate n xs
+                 -> m (Zipper [] Value)
+genIndexPageData xs g h n = extend (genPageData g h) <$> paginate' n xs
 
diff --git a/src/Shakebook/Data.hs b/src/Shakebook/Data.hs
--- a/src/Shakebook/Data.hs
+++ b/src/Shakebook/Data.hs
@@ -1,27 +1,54 @@
+{-# LANGUAGE TemplateHaskell #-}
 module Shakebook.Data where
 
 import           Control.Comonad.Cofree
 import           Control.Comonad.Store
+import           Control.Comonad.Zipper.Extra
 import           Control.Lens               hiding ((:<))
 import           Control.Monad.Extra
 import           Data.Aeson                 as A
 import           Data.Aeson.Lens
-import           Development.Shake          as S
-import           Development.Shake.FilePath
+import           Development.Shake.Plus
+import qualified Development.Shake.FilePath
+import           Path                       as P
 import           RIO                        hiding (Lens', lens, view)
-import           RIO.List
-import           RIO.Partial
 import qualified RIO.Text                   as T
 import           Shakebook.Aeson
-import           Slick
 import           Slick.Pandoc
 import           Text.Pandoc.Options
+import           Within
 
+needLocalOut :: (MonadAction m, MonadReader r m, HasLocalOut r) => [Path Rel File] -> m ()
+needLocalOut ys = view localOutL >>= \r -> needIn r ys
+
+(%->) :: (MonadReader r m, MonadRules m, HasLocalOut r) => FilePattern -> (Within Rel File -> RAction r ()) -> m ()
+(%->) pat f = view localOutL >>= \o -> (toFilePath o Development.Shake.FilePath.</> pat) %> \x -> (x `asWithin` o) >>= f
+
+class HasLocalOut r where
+  localOutL :: Lens' r (Path Rel Dir)
+
+class HasLocalSrc r where
+  localSrcL :: Lens' r (Path Rel Dir)
+
+newtype PathDisplay a t = PathDisplay (Path a t)
+
+instance Display (PathDisplay a t) where
+  display (PathDisplay f) = displayBytesUtf8 . fromString . toFilePath $ f
+
+newtype WithinDisplay a t = WithinDisplay (Within a t)
+
+instance Display (WithinDisplay a t) where
+  display (WithinDisplay (Within (x,y))) = display (PathDisplay x) <> "[" <> display (PathDisplay y) <> "]"
+
+instance Display [WithinDisplay a t] where
+  display [] = ""
+  display (x : xs) = display x <> " : " <> display xs
+
 type ToC = Cofree [] String
 
 data SbConfig = SbConfig
-    { sbSrcDir  :: FilePath
-    , sbOutDir  :: FilePath
+    { sbSrcDir  :: Path Rel Dir
+    , sbOutDir  :: Path Rel Dir
     , sbBaseUrl :: Text
     , sbMdRead  :: ReaderOptions
     , sbHTWrite :: WriterOptions
@@ -32,48 +59,20 @@
 class HasSbConfig a where
   sbConfigL :: Lens' a SbConfig
 
-newtype Shakebook r a = Shakebook ( ReaderT r Rules a )
-  deriving (Functor, Applicative, Monad, MonadReader r, MonadIO)
-
-newtype ShakebookA r a = ShakebookA ( ReaderT r Action a )
-  deriving (Functor, Applicative, Monad, MonadReader r, MonadIO)
-
-runShakebook :: r -> Shakebook r a -> Rules a
-runShakebook c (Shakebook f) = runReaderT f c
-
-runShakebookA :: r -> ShakebookA r a -> Action a
-runShakebookA c (ShakebookA f) = runReaderT f c
-
-class MonadAction m where
-  liftAction :: Action a -> m a
-
-class MonadRules m where
-  liftRules :: Rules a -> m a
-
-instance MonadAction (ShakebookA r) where
-  liftAction = ShakebookA . lift
-
-instance MonadRules (Shakebook r) where
-  liftRules = Shakebook . lift
-
 data ShakebookEnv = ShakebookEnv
     { logFunc  :: LogFunc
     , sbConfig :: SbConfig
     }
 
+instance HasLocalOut ShakebookEnv where
+  localOutL = lens (sbOutDir . sbConfig) undefined
+
 instance HasSbConfig ShakebookEnv where
   sbConfigL = lens sbConfig undefined
 
 instance HasLogFunc ShakebookEnv where
   logFuncL = lens logFunc undefined
 
-type MonadShakebook r m = (MonadReader r m, HasSbConfig r, HasLogFunc r, MonadIO m)
-type MonadShakebookAction r m = (MonadShakebook r m, MonadAction m)
-type MonadShakebookRules r m = (MonadShakebook r m, MonadRules m)
-
-
-
-
 -- | View the "srcPath" field of a JSON Value.
 viewSrcPath :: Value -> Text
 viewSrcPath = view (key "srcPath" . _String)
@@ -106,70 +105,57 @@
 enrichUrl :: (Text -> Text) -> Value -> Value
 enrichUrl f v = withUrl (f (viewSrcPath v)) v
 
--- | Filepath/URL calculators - these work but don't try to do the wrong thing or it will explode.
-typicalFullOutToSrcPath :: MonadShakebook r m => m (String -> String)
-typicalFullOutToSrcPath = view sbConfigL >>= \SbConfig{..} -> pure $
-   drop 1 . fromJust . stripPrefix sbOutDir
-
-typicalFullOutToFullSrcPath :: MonadShakebook r m => m (String -> String)
-typicalFullOutToFullSrcPath = view sbConfigL >>= \SbConfig{..} -> 
-  liftA2 (.) (pure (sbSrcDir </>)) typicalFullOutToSrcPath
+leadingSlash :: Path Abs Dir
+leadingSlash = $(mkAbsDir "/")
 
-typicalFullOutHTMLToMdSrcPath :: MonadShakebook r m => m (String -> String)
-typicalFullOutHTMLToMdSrcPath = liftA2 (.) (pure (-<.> "md")) typicalFullOutToSrcPath
+withHtmlExtension :: MonadThrow m => Path Rel File -> m (Path Rel File)
+withHtmlExtension = replaceExtension ".html"
 
-typicalMdSrcPathToHTMLFullOut :: MonadShakebook r m => m (String -> String)
-typicalMdSrcPathToHTMLFullOut = view sbConfigL >>= \SbConfig{..} -> pure $
-  (-<.> "html") . (sbOutDir </>) . drop 1 . fromJust . stripPrefix sbSrcDir
+withMarkdownExtension :: MonadThrow m => Path Rel File -> m (Path Rel File)
+withMarkdownExtension = replaceExtension ".md"
 
-typicalSrcPathToUrl :: Text -> Text
-typicalSrcPathToUrl = ("/" <>) . T.pack . (-<.> "html") . T.unpack
+generateSupposedUrl :: MonadThrow m => Path Rel File -> m (Path Abs File)
+generateSupposedUrl srcPath = (leadingSlash </>) <$> withHtmlExtension srcPath
 
-enrichTypicalUrl :: Value -> Value
-enrichTypicalUrl v = withUrl (typicalSrcPathToUrl . viewSrcPath $ v) v
+enrichSupposedUrl :: (MonadReader r m, HasSbConfig r, MonadThrow m) => Value -> m Value
+enrichSupposedUrl v = view sbConfigL >>= \SbConfig{..} -> do
+  x <- parseRelFile $ T.unpack $ viewSrcPath v
+  y <- generateSupposedUrl x
+  return $ withUrl (T.pack . toFilePath $ y) v
 
 {-|
   Get a JSON Value of Markdown Data with markdown body as "contents" field
   and the srcPath as "srcPath" field.
 -}
-readMarkdownFile' :: MonadShakebookAction r m => String -> m Value
-readMarkdownFile' srcPath = view sbConfigL >>= \SbConfig{..} -> do
-  logInfo $ displayShow $ "Reading source: " <> srcPath
-  liftAction $ do
-    docContent <- readFile' (sbSrcDir </> srcPath)
-    docData <- markdownToHTMLWithOpts sbMdRead sbHTWrite . T.pack $ docContent
-    return $ withSrcPath (T.pack srcPath) docData
-
-loadIfExists :: (FilePath -> Action Value) -> FilePath -> Action Value
-loadIfExists f src = ifM (S.doesFileExist src) (f src) (return (Object mempty))
+readMarkdownFile' :: (MonadReader r m, HasSbConfig r, MonadAction m)
+                  => Within Rel File
+                  -> m Value
+readMarkdownFile' srcPath = view sbConfigL >>= \SbConfig{..} -> liftAction $ do
+  docContent <- readFile' (fromWithin srcPath)
+  docData <- markdownToHTMLWithOpts sbMdRead sbHTWrite docContent
+  supposedUrl <- liftIO $ (leadingSlash </>) <$> withHtmlExtension (whatLiesWithin srcPath)
+  return $ withSrcPath (T.pack . toFilePath $ whatLiesWithin srcPath)
+         . withUrl (T.pack . toFilePath $ supposedUrl) $ docData
 
-getMarkdown :: MonadShakebookAction r m => [FilePattern] -> m [Value]
-getMarkdown pat = view sbConfigL >>= \SbConfig{..} ->
-  liftAction (getDirectoryFiles sbSrcDir pat) >>= mapM readMarkdownFile'
+data PaginationException = EmptyContentsError
+  deriving (Show, Eq, Typeable)
 
-{-| 
-  Build a single page straight from a template, a loaded Value, and a pure enrichment.
--}
-genBuildPageAction :: (MonadShakebookAction r m)
-                   => FilePath -- ^ The HTML template
-                   -> (FilePath -> m Value) -- ^ How to get from FilePath to Value, can use Actions.
-                   -> (Value -> Value) -- ^ Additional modifiers for the value.
-                   -> FilePath -- ^ The out filepath
-                   -> m Value
-genBuildPageAction template getData withData out = view sbConfigL >>= \SbConfig{..} -> do
-  logInfo $ displayShow $ "Generating page with fullpath " <> out
-  pageT <- liftAction $ compileTemplate' (sbSrcDir </> template)
-  dataT <- withData . enrichTypicalUrl <$> getData out
-  logDebug $ displayShow dataT
-  writeFile' out . T.unpack $ substitute pageT dataT
-  return dataT
+instance Exception PaginationException where
+  displayException EmptyContentsError = "Can not create a Zipper of length zero."
 
-traverseToSnd :: Functor f => (a -> f b) -> a -> f (a, b)
-traverseToSnd f a = (a,) <$> f a
+paginate' :: MonadThrow m => Int -> [a] -> m (Zipper [] [a])
+paginate' n xs =  case paginate n xs of
+                    Just x -> return x
+                    Nothing -> throwM EmptyContentsError
 
 lower :: Cofree [] Value -> [Value]
 lower (_ :< xs) = extract <$> xs
 
+type MonadShakebook r m = (MonadReader r m, HasSbConfig r, HasLogFunc r, MonadIO m, MonadThrow m, HasLocalOut r)
+type MonadShakebookAction r m = (MonadShakebook r m, MonadAction m)
+type MonadShakebookRules r m = (MonadShakebook r m, MonadRules m)
+
+
 {-|
   Multi-markdown loader. Allows you to load a filepattern of markdown as a list of JSON
   values ready to pass to an HTML template. You will probably want to add additional
@@ -180,16 +166,14 @@
                      -> (Value -> b)     -- ^ A value to sortOn e.g (Down . viewPostTime)
                      -> (Value -> Bool)  -- ^ A filtering predicate e.g (elem tag . viewTags)
                      -> (Value -> Value) -- ^ An initial enrichment. This is pure so can only be data derived from the initial markdown.
-                     -> m [(FilePath, Value)] -- ^ A list of Values indexed by their srcPath.
-loadSortFilterEnrich pat s f e = view sbConfigL >>= \SbConfig {..} -> do
-    allPosts <- liftAction $ getDirectoryFiles sbSrcDir $ map (-<.> ".md") pat
-    readPosts <- sequence $ traverseToSnd readMarkdownFile' <$> allPosts
-    return $ fmap (second e) $ sortOn (s . snd) $ filter (f . snd) readPosts
+                     -> m [(Within Rel File, Value)] -- ^ A list of Values indexed by their srcPath.
+loadSortFilterEnrich pat s f e = view sbConfigL >>= \SbConfig {..} ->
+    loadSortFilterApplyW readMarkdownFile' sbSrcDir pat s f e
 
 -- | The same as `loadSortFilterEnrich` but without filtering.
 loadSortEnrich :: (MonadShakebookAction r m, Ord b)
                => [FilePattern]    -- ^ A Shake filepattern to load.
                -> (Value -> b)     -- ^ A value to sortOn e.g (Down . viewPostTime).
                -> (Value -> Value) -- ^ An initial pure enrichment.
-               -> m [(String, Value)] -- ^ A list of Values index by their srcPath.
+               -> m [(Within Rel File, Value)] -- ^ A list of Values index by their srcPath.
 loadSortEnrich pat s = loadSortFilterEnrich pat s (const True)
diff --git a/src/Shakebook/Defaults.hs b/src/Shakebook/Defaults.hs
--- a/src/Shakebook/Defaults.hs
+++ b/src/Shakebook/Defaults.hs
@@ -1,18 +1,17 @@
 {-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE NoMonomorphismRestriction  #-}
+{-# LANGUAGE TemplateHaskell #-}
 module Shakebook.Defaults where
 
 import           Control.Comonad
 import           Control.Comonad.Cofree
 import           Control.Comonad.Store.Class
-import           Control.Comonad.Store.Zipper
+import           Control.Comonad.Zipper.Extra
 import           Control.Monad.Extra
 import           Data.Aeson                   as A
 import           Data.List.Split
 import           Data.Text.Time
-import           Development.Shake            as S
-import           Development.Shake.Classes
-import           Development.Shake.FilePath
+import           Development.Shake.Plus
+import qualified Development.Shake.FilePath   as S
 import           RIO
 import qualified RIO.ByteString.Lazy          as LBS
 import           RIO.List
@@ -21,11 +20,11 @@
 import           RIO.Partial
 import qualified RIO.Text                     as T
 import           RIO.Time
+import           Path                         as P
 import           Shakebook.Aeson
 import           Shakebook.Conventions
 import           Shakebook.Data
-import           Shakebook.Rules
-import           Shakebook.Zipper
+import           Shakebook.Mustache
 import           Text.DocTemplates
 import           Text.Pandoc.Class
 import           Text.Pandoc.Definition
@@ -35,9 +34,10 @@
 import           Text.Pandoc.Templates
 import           Text.Pandoc.Walk
 import           Text.Pandoc.Writers
+import           Within
 
-defaultMonthURLFormat :: UTCTime -> String
-defaultMonthURLFormat = formatTime defaultTimeLocale "%Y-%m"
+defaultMonthUrlFormat :: UTCTime -> String
+defaultMonthUrlFormat = formatTime defaultTimeLocale "%Y-%m"
 
 defaultPrettyMonthFormat :: UTCTime -> String
 defaultPrettyMonthFormat = formatTime defaultTimeLocale "%B, %Y"
@@ -45,14 +45,22 @@
 defaultPrettyTimeFormat :: UTCTime -> String
 defaultPrettyTimeFormat = formatTime defaultTimeLocale "%A, %B %d, %Y"
 
-defaultMonthIndexUrlFormat :: UTCTime -> String
-defaultMonthIndexUrlFormat t = "/posts/months" </> defaultMonthURLFormat t
+defaultIndexFileFragment :: Path Rel File
+defaultIndexFileFragment = $(mkRelFile "index.html")
 
+defaultMonthDirFragment :: MonadThrow m => UTCTime -> m (Path Rel Dir)
+defaultMonthDirFragment t = do
+  k <- parseRelDir $ defaultMonthUrlFormat t
+  return $ $(mkRelDir "posts/months") </> k
+
+defaultMonthUrlFragment :: UTCTime -> Text
+defaultMonthUrlFragment t = T.pack $ "/posts/months/" <> defaultMonthUrlFormat t
+
 defaultEnrichPost :: Value -> Value
 defaultEnrichPost = enrichTeaser "<!--more-->"
                   . enrichTagLinks ("/posts/tags/" <>)
                   . enrichPrettyDate defaultPrettyTimeFormat
-                  . enrichTypicalUrl
+--                  . enrichTypicalUrl
 
 defaultMarkdownReaderOptions :: ReaderOptions
 defaultMarkdownReaderOptions = def { readerExtensions = pandocExtensions }
@@ -68,9 +76,18 @@
                                              , ("linkcolor",SimpleVal "blue")]
                          }
 
+defaultSbSrcDir :: Path Rel Dir
+defaultSbSrcDir = $(mkRelDir "site")
+
+defaultSbOutDir :: Path Rel Dir
+defaultSbOutDir = $(mkRelDir "public")
+
+defaultPostsPerPage :: Int
+defaultPostsPerPage = 5
+
 defaultSbConfig :: Text -- ^ BaseURL
                 -> SbConfig
-defaultSbConfig x = SbConfig "site" "public" x defaultMarkdownReaderOptions defaultHtml5WriterOptions 5
+defaultSbConfig x = SbConfig defaultSbSrcDir defaultSbOutDir x defaultMarkdownReaderOptions defaultHtml5WriterOptions defaultPostsPerPage
 
 affixBlogNavbar :: MonadShakebookAction r m
                 => [FilePattern]
@@ -95,24 +112,22 @@
 
 
 
-defaultDocsPatterns :: MonadShakebookRules r m
+defaultDocsPatterns :: (MonadShakebookRules r m)
                     => Cofree [] FilePath -- Rosetree Table of Contents.
                     -> FilePath
                     -> (Value -> Value) -- Extra data modifiers.
                     -> m ()
-defaultDocsPatterns toc tmpl withData = ask >>= \r -> view sbConfigL >>= \SbConfig {..} -> do
-  let e = enrichTypicalUrl
-  m <- typicalFullOutHTMLToMdSrcPath
-  liftRules $ cofreeRuleGen toc ((sbOutDir </>) . (-<.> ".html")) (
-         \xs -> \out -> runShakebookA r $ do
-             ys <- mapM readMarkdownFile' toc
-             zs <- mapM readMarkdownFile' xs
-             void $ genBuildPageAction tmpl
-                      (readMarkdownFile' . m)
-                      (withData
-                     . withJSON (genTocNavbarData (e <$> ys))
-                     . withSubsections (lower (e <$> zs)))
-                      out)
+defaultDocsPatterns toc tmpl withData = view sbConfigL >>= \SbConfig{..} -> do
+  tmpl' <- parseRelFile tmpl
+  o <- view localOutL
+  toc'  <- mapM (parseRelFile >=> pure . (`within` o) >=> mapWithinT withHtmlExtension) toc
+  let e = blinkAndMapT sbSrcDir withMarkdownExtension >=> readMarkdownFile' >=> enrichSupposedUrl
+  void . sequence . flip extend toc' $ \xs -> (toFilePath . whatLiesWithin $ extract xs) %->  \out -> do
+             ys <- mapM e toc'
+             zs <- mapM e xs
+             v  <- e out
+             let v' = withData . withJSON (genTocNavbarData ys) . withSubsections (lower (zs)) $ v
+             buildPageActionWithin (tmpl' `within` sbSrcDir) v' out
 
 defaultPostIndexData :: MonadShakebookAction r m
                      => [FilePattern]
@@ -123,25 +138,29 @@
                      -> m (Zipper [] Value) -- A pager of index pages.
 defaultPostIndexData pat f t l a = view sbConfigL >>= \SbConfig {..} -> do
   xs <- loadSortFilterEnrich pat (Down . viewPostTime) (f a) defaultEnrichPost
-  let ys = genIndexPageData (snd <$> xs) (t a) (l a) sbPPP
-  return $ fromJust $ ys
+  ys <- genIndexPageData (snd <$> xs) (t a) (l a) sbPPP
+  return ys
 
-defaultPagerPattern :: MonadShakebookRules r m
+defaultPagerPattern :: (MonadShakebookRules r m)
                     => FilePattern
                     -> FilePath
                     -> (FilePattern -> Int) -- ^ How to extract a page number from the Filepattern.
                     -> (FilePattern -> a) -- ^ How to extract an id from the FilePattern
-                    -> (a -> ShakebookA r (Zipper [] Value))
-                    -> (Zipper [] Value -> ShakebookA r (Zipper [] Value))
+                    -> (a -> RAction r (Zipper [] Value))
+                    -> (Zipper [] Value -> RAction r (Zipper [] Value))
                     -> m ()
-defaultPagerPattern fp tmpl f g h w = ask >>= \r -> view sbConfigL >>= \SbConfig{..} -> liftRules $
-  comonadStoreRuleGen (sbOutDir </> fp) (f . drop 1 . fromJust . stripPrefix sbOutDir) (g . drop 1 . fromJust . stripPrefix sbOutDir) (runShakebookA r . (w <=< h))
-  (\a -> void <$> runShakebookA r . genBuildPageAction tmpl (const $ return a) id)
+defaultPagerPattern fp tmpl f g h w = view sbConfigL >>= \SbConfig{..} -> do
+  tmpl' <- parseRelFile tmpl
+  fp %-> \x -> do
+    let x' = toFilePath $ whatLiesWithin x
+    xs <- (w <=< h) $ g (x')
+    let b = extract (seek (f x') xs)
+    buildPageActionWithin (tmpl' `within` sbSrcDir) b x
 
 defaultPostIndexPatterns :: MonadShakebookRules r m
                          => [FilePattern]
                          -> FilePath
-                         -> (Zipper [] Value -> ShakebookA r (Zipper [] Value)) -- ^ Pager extension.
+                         -> (Zipper [] Value -> RAction r (Zipper [] Value)) -- ^ Pager extension.
                          -> m ()
 defaultPostIndexPatterns pat tmpl extData = do
    defaultPagerPattern "posts/index.html" tmpl
@@ -162,7 +181,7 @@
 defaultTagIndexPatterns :: MonadShakebookRules r m
                         => [FilePattern]
                         -> FilePath
-                        -> (Zipper [] Value -> ShakebookA r (Zipper [] Value)) -- ^ Pager extension.
+                        -> (Zipper [] Value -> RAction r (Zipper [] Value)) -- ^ Pager extension.
                         -> m ()
 defaultTagIndexPatterns pat tmpl extData = do
  defaultPagerPattern ("posts/tags/*/index.html") tmpl
@@ -183,7 +202,7 @@
 defaultMonthIndexPatterns :: MonadShakebookRules r m
                           => [FilePattern]
                           -> FilePath
-                          -> (Zipper [] Value -> ShakebookA r (Zipper [] Value)) -- ^ Pager extension.
+                          -> (Zipper [] Value -> RAction r (Zipper [] Value)) -- ^ Pager extension.
                           -> m ()
 defaultMonthIndexPatterns pat tmpl extData = do
  defaultPagerPattern "posts/months/*/index.html" tmpl
@@ -192,7 +211,7 @@
                      (defaultPostIndexData pat
                         (\x y -> sameMonth x (viewPostTime y))
                         (("Posts from " <>) . T.pack . defaultPrettyMonthFormat)
-                        (\x y -> ("/posts/months/" <> T.pack (defaultMonthURLFormat x) <> "/pages" <> y)))
+                        (\x y -> ("/posts/months/" <> T.pack (defaultMonthUrlFormat x) <> "/pages" <> y)))
                      extData
  defaultPagerPattern "posts/months/*/pages/*/index.html" tmpl
                       ((+ (-1)) . read . (!! 4) . splitOn "/")
@@ -200,7 +219,7 @@
                        (defaultPostIndexData pat
                            (\x y -> sameMonth x (viewPostTime y))
                            (("Posts from " <>) . T.pack . defaultPrettyMonthFormat)
-                           (\x y -> ("/posts/months/" <> T.pack (defaultMonthURLFormat x) <> "/pages" <> y)))
+                           (\x y -> ("/posts/months/" <> T.pack (defaultMonthUrlFormat x) <> "/pages" <> y)))
                        extData
 
 {-|
@@ -209,31 +228,35 @@
 defaultPostsPatterns :: MonadShakebookRules r m
                      => FilePattern
                      -> FilePath
-                     -> (Value -> ShakebookA r Value) -- ^ A post loader function.
-                     -> (Zipper [] Value -> ShakebookA r (Zipper [] Value)) -- ^ A transformation on the entire post zipper.
+                     -> (Value -> RAction r Value) -- ^ A post loader function.
+                     -> (Zipper [] Value -> RAction r (Zipper [] Value)) -- ^ A transformation on the entire post zipper.
                      -> m ()
-defaultPostsPatterns pat tmpl e extData = ask >>= \r -> view sbConfigL >>= \SbConfig {..} ->
-  liftRules $ sbOutDir </> pat %> \out -> do
-      sortedPosts <- runShakebookA r $ do
-        xs <- loadSortEnrich [pat] (Down . viewPostTime) defaultEnrichPost
-        mapM (\(s,x) -> e x >>= \e' -> return (s, e')) xs
-      let i = (-<.> ".md") . drop 1 . fromJust . stripPrefix sbOutDir $ out
-      let k = fromJust $ elemIndex i (fst <$> sortedPosts)
-      let z = fromJust $ seek k <$> zipper (snd <$> sortedPosts)
-      void $ runShakebookA r $ genBuildPageAction tmpl
-                                (const $ extract <$> extData z)
-                                id out
+defaultPostsPatterns pat tmpl e extData = view sbConfigL >>= \SbConfig {..} ->
+   pat %-> \out -> do
+    logInfo $ display $ "Caught pattern: " <> display (WithinDisplay out)
+    tmpl' <- parseRelFile tmpl
+    logInfo $ display $ "Using template " <> display (PathDisplay tmpl')
+    let pat' = pat S.-<.> ".md"
+    xs    <- loadSortEnrich [pat'] (Down . viewPostTime) defaultEnrichPost
+    xs'   <- mapM (\(s,x) -> e x >>= \e' -> return (s, e')) xs
+    i     <- blinkAndMapT sbSrcDir withMarkdownExtension out
+    logInfo $ display $ WithinDisplay i
+    logInfo $ display $ WithinDisplay . fst <$> xs'
+    let k = fromJust $ elemIndex i (fst <$> xs')
+    let z = fromJust $ seek k <$> zipper (snd <$> xs')
+    z' <- extData z
+    buildPageActionWithin (tmpl' `within` sbSrcDir) (extract z') out
 
 makePDFLaTeX :: Pandoc -> PandocIO (Either LBS.ByteString LBS.ByteString)
 makePDFLaTeX p = do
   t <- compileDefaultTemplate "latex"
   makePDF "pdflatex" [] writeLaTeX defaultLatexWriterOptions { writerTemplate = Just t } p
 
-handleImages :: (Text -> Text) -> Inline -> Inline
-handleImages f (Image attr ins (src,txt)) =
+handleImages :: Text -> (Text -> Text) -> Inline -> Inline
+handleImages prefix f (Image attr ins (src,txt)) =
   if T.takeEnd 4 src == ".mp4" then Str (f src)
-  else Image attr ins ("public/" <> src, txt)
-handleImages _ x = x
+  else Image attr ins (prefix <> "/" <> src, txt)
+handleImages _ _ x = x
 
 handleHeaders :: Int -> Block -> Block
 handleHeaders i (Header a as xs) = Header (max 1 (a + i)) as xs
@@ -243,14 +266,14 @@
 pushHeaders i (x :< xs) = walk (handleHeaders i) x :< map (pushHeaders (i+1)) xs
 
 -- | Build a PDF from a Cofree table of contents.
-buildPDF :: MonadShakebookAction r m => Cofree [] String -> String -> FilePath -> m ()
-buildPDF toc meta out = view sbConfigL >>= \SbConfig {..} -> liftAction $ do
-  y <- mapM readFile' ((sbSrcDir </>) <$> toc)
-  m <- readFile' $  sbSrcDir </> meta
+buildPDF :: (MonadShakebookAction r m, MonadFail m) => Cofree [] String -> Path Rel File -> FilePath -> m ()
+buildPDF toc meta out = view sbConfigL >>= \SbConfig {..} -> do
+  y <- mapM (readFileIn' sbSrcDir <=< parseRelFile) toc
+  m <- readFileIn' sbSrcDir meta
   Right f <- liftIO . runIOorExplode $ do
-    k <- mapM (readMarkdown sbMdRead . T.pack) y
-    a <- readMarkdown sbMdRead . T.pack $ m
-    let z = walk (handleImages (\x -> "[Video available at " <> sbBaseUrl <> x <> "]")) $ foldr (<>) a $ pushHeaders (-1) k
+    k <- mapM (readMarkdown sbMdRead ) y
+    a <- readMarkdown sbMdRead $ m
+    let z = walk (handleImages (T.pack $ toFilePath sbOutDir) (\x -> "[Video available at " <> sbBaseUrl <> x <> "]")) $ foldr (<>) a $ pushHeaders (-1) k
     makePDFLaTeX z
   LBS.writeFile out f
 
@@ -259,72 +282,68 @@
   called singlePagePattern, as there's no hardcoded strings here, but it would need to
   run entirely within the monad to translate filepaths.
 -}
-defaultSinglePagePattern :: MonadShakebookRules r m
+defaultSinglePagePattern :: (MonadRules m, MonadReader r m, HasSbConfig r, HasLocalOut r)
                          => FilePath -- ^ The output filename e.g "index.html".
                          -> FilePath -- ^ A tmpl file.
-                         -> (Value -> ShakebookA r Value) -- ^ Last minute enrichment.
+                         -> (Value -> RAction r Value) -- ^ Last minute enrichment.
                          -> m ()
-defaultSinglePagePattern out tmpl withDataM = ask >>= \r -> view sbConfigL >>= \SbConfig {..} -> do
-  m <- typicalFullOutHTMLToMdSrcPath
-  liftRules $ sbOutDir </> out %> void . runShakebookA r . genBuildPageAction
-                 tmpl
-                 (\fp -> do
-                    x <- readMarkdownFile' . m $ fp
-                    withDataM $ x)
-                 id
+defaultSinglePagePattern out tmpl withDataM = view sbConfigL >>= \SbConfig {..} -> do
+  out %-> \x -> do
+    tmpl' <- parseRelFile tmpl
+    x'    <- blinkAndMapT sbSrcDir withMarkdownExtension $ x
+    v     <- withDataM =<< readMarkdownFile' x'
+    buildPageActionWithin (tmpl' `within` sbSrcDir) v x
 
 {-|
   Default statics patterns. Takes a list of filepatterns and adds a rule that copies everything
   verbatim
 -}
 defaultStaticsPatterns :: MonadShakebookRules r m => [FilePattern] -> m ()
-defaultStaticsPatterns xs = view sbConfigL >>= \SbConfig {..} -> do
-  f <- typicalFullOutToFullSrcPath
-  liftRules $ mconcat $ map (\x -> sbOutDir </> x %> \y -> copyFileChanged (f y) y) xs
+defaultStaticsPatterns xs =  view sbConfigL >>= \SbConfig {..} -> do
+  foldr (>>) (return ()) $ flip map xs $ flip (%->) $ \y -> do
+       let y' = blinkWithin sbSrcDir y
+       copyFileChanged (fromWithin y') (fromWithin y)
 
 -- | Default "shake clean" phony, cleans your output directory.
 defaultCleanPhony :: MonadShakebookRules r m => m ()
-defaultCleanPhony = view sbConfigL >>= \SbConfig {..} -> liftRules $
+defaultCleanPhony = view sbConfigL >>= \SbConfig {..} -> 
   phony "clean" $ do
-      putInfo $ "Cleaning files in " ++ sbOutDir
+      logInfo $ "Cleaning files in " <> display (PathDisplay sbOutDir)
       removeFilesAfter sbOutDir ["//*"]
 
 defaultSinglePagePhony :: MonadShakebookRules r m => String -> FilePath -> m ()
-defaultSinglePagePhony x y = view sbConfigL >>= \SbConfig {..} -> liftRules $ 
-  phony x $ need [sbOutDir </> y]
+defaultSinglePagePhony x y = phony x $ parseRelFile y >>= needLocalOut . pure
 
 {-|
   Default "shake statics" phony rule. automatically runs need on "\<out\>\/thing\/\*" for every
   thing found in "images\/", "css\/", "js\/" and "webfonts\/"
 -}
-defaultStaticsPhony :: MonadShakebookRules r m => m ()
-defaultStaticsPhony = view sbConfigL >>= \SbConfig {..} -> liftRules $
-  phony "statics" $ do
-    fp <- getDirectoryFiles sbSrcDir ["images//*", "css//*", "js//*", "webfonts//*"]
-    need $ [sbOutDir </> x | x <- fp]
+defaultStaticsPhony :: MonadShakebookRules r m => [FilePattern] -> m ()
+defaultStaticsPhony pattern = view sbConfigL >>= \SbConfig{..} -> 
+  phony "statics" $ 
+    getDirectoryFiles sbSrcDir pattern >>= needIn sbOutDir
 
 {-|
   Default "shake posts" phony rule. takes a [FilePattern] pointing to the posts and
   and calls need on "\<out\>\/posts\/\<filename\>.html" for each markdown post found.
 -}  
 defaultPostsPhony :: MonadShakebookRules r m => [FilePattern] -> m ()
-defaultPostsPhony pattern = view sbConfigL >>= \SbConfig {..} -> liftRules $
-  phony "posts" $ do
-    fp <- getDirectoryFiles sbSrcDir pattern
-    need [sbOutDir </> x -<.> ".html" | x <- fp]
+defaultPostsPhony pattern = view sbConfigL >>= \SbConfig{..} -> 
+  phony "posts" $ 
+    getDirectoryFilesWithin sbSrcDir pattern >>= mapM (blinkAndMapT sbOutDir withHtmlExtension) >>= needWithin
 
+
 {-|
   Default "shake posts-index" phony rule. Takes a [FilePattern] of posts to
   discover and calls need on "\<out\>\/posts\/index.html" and
   "\<out\>\/posts\/pages\/\<n\>\/index.html" for each page required.
 -}
 defaultPostIndexPhony :: MonadShakebookRules r m => [FilePattern] -> m ()
-defaultPostIndexPhony pattern = ask >>= \r -> view sbConfigL >>= \SbConfig {..} -> liftRules $
+defaultPostIndexPhony pattern = view sbConfigL >>= \SbConfig{..} ->
     phony "posts-index" $ do
-      fp <- runShakebookA r $ getMarkdown pattern
-      need [sbOutDir </> "posts/index.html"]
-      need [sbOutDir </> "posts/pages/" ++ show x ++ "/index.html"
-           | x <- [1..size (fromJust $ paginate sbPPP fp)]]
+      fp  <- getDirectoryFilesWithin sbSrcDir pattern >>= mapM readMarkdownFile'
+      needIn sbOutDir [dirPosts </> fileIndexHTML]
+      paginate' sbPPP fp >>= defaultPagePaths dirPosts >>= needIn sbOutDir
 
 {-|
   Default "shake tag-index" phony rule. Takes a [FilePattern] of posts to
@@ -333,38 +352,56 @@
   each page required per tag filter.
 -}
 defaultTagIndexPhony :: MonadShakebookRules r m => [FilePattern] -> m ()
-defaultTagIndexPhony pattern = ask >>= \r -> view sbConfigL >>= \SbConfig {..} -> liftRules $
-  phony "tag-index" $ do
-    fp <- runShakebookA r $ getMarkdown pattern
-    let tags = viewAllPostTags fp
-    need [sbOutDir </> "posts/tags" </> T.unpack x </> "index.html" | x <- tags]
-    need [sbOutDir </> "posts/tags" </> T.unpack x </> "pages" </> show p </> "index.html"
-         |  x <- tags
-         ,  p <- [1..size (fromJust $ paginate sbPPP $ tagFilterPosts x fp)]
-         ]
+defaultTagIndexPhony pattern = view sbConfigL >>= \SbConfig{..} ->
+   phony "tag-index" $ do
+      fp  <- getDirectoryFilesWithin sbSrcDir pattern >>= mapM readMarkdownFile'
+      forM_ (viewAllPostTags fp) $ \t -> do
+        u <- parseRelDir $ T.unpack t
+        needIn sbOutDir [dirPosts </> dirTags </> u </> fileIndexHTML]
+        paginate' sbPPP (tagFilterPosts t fp)
+         >>= defaultPagePaths (dirPosts </> dirTags </> u)
+           >>= needIn sbOutDir
 
+defaultPagePaths :: MonadThrow m => Path Rel Dir -> Zipper [] [a] -> m [Path Rel File]
+defaultPagePaths a xs = forM [1..size xs] $ parseRelDir . show >=> \p -> return $ a </> dirPages </> p </> fileIndexHTML
+
+fileIndexHTML :: Path Rel File
+fileIndexHTML = $(mkRelFile "index.html")
+
+dirPosts :: Path Rel Dir
+dirPosts = $(mkRelDir "posts")
+
+dirMonths :: Path Rel Dir
+dirMonths = $(mkRelDir "months")
+
+dirPages :: Path Rel Dir
+dirPages = $(mkRelDir "pages")
+
+dirTags :: Path Rel Dir
+dirTags = $(mkRelDir "tags")
+
 {-|
   Default "shake month-index" phony rule. Takes a [FilePattern] of posts to
   discover and calls need on "\<out\>\/posts\/months\/\<yyyy-md\>\/index.html" and
   "\<out\>\/posts\/months\/\<yyyy-md\>\/pages\/\<n\>\/index.html" for each month
   discovered that contains a post and for each page required per month filter.
 -}
-defaultMonthIndexPhony :: MonadShakebookRules r m => [FilePattern] -> m ()
-defaultMonthIndexPhony pattern = ask >>= \r -> view sbConfigL >>= \SbConfig {..} -> liftRules $
-   phony "month-index" $ do
-      fp <- runShakebookA r $ getMarkdown pattern
-      let times = viewAllPostTimes fp
-      need [sbOutDir </> "posts/months" </> defaultMonthURLFormat t </> "index.html" | t <- times]
-      need [sbOutDir </> "posts/months" </> defaultMonthURLFormat t </> "pages" </> show p </> "index.html"
-           | t <- times
-           , p <- [1..length (fromJust $ paginate sbPPP $ monthFilterPosts t fp)]
-           ]
+defaultMonthIndexPhony :: (MonadRules m, MonadReader r m, HasSbConfig r, HasLocalOut r) => [FilePattern] -> m ()
+defaultMonthIndexPhony pattern = phony "month-index" $ do
+  SbConfig{..} <- view sbConfigL
+  fp  <- getDirectoryFilesWithin sbSrcDir pattern >>= mapM readMarkdownFile'
+  forM_ (viewAllPostTimes fp) $ \t -> do
+    u <- parseRelDir $ defaultMonthUrlFormat t
+    needLocalOut [dirPosts </> dirMonths </> u </> fileIndexHTML]
+    paginate' sbPPP (monthFilterPosts t fp)
+      >>= defaultPagePaths (dirPosts </> dirMonths </> u)
+        >>= needLocalOut
 
 -- | Default "shake docs" phony rule, takes a Cofree [] String as a table of contents.
 defaultDocsPhony :: MonadShakebookRules r m
-                 => Cofree [] String
+                 => Cofree [] String 
                  -> m ()
-defaultDocsPhony toc = view sbConfigL >>= \SbConfig {..} -> liftRules $
+defaultDocsPhony toc = view sbConfigL >>= \SbConfig{..} ->
     phony "docs" $ do
-      let xs =  (foldr ((<>) . pure) [] toc)
-      need $ [ (sbOutDir </>) . (-<.> ".html") $ x | x <- xs]
+      let xs = foldr ((<>) . pure) [] $ toc
+      pure xs >>= mapM (parseRelFile >=> withHtmlExtension) >>= needIn sbOutDir
diff --git a/src/Shakebook/Mustache.hs b/src/Shakebook/Mustache.hs
new file mode 100644
--- /dev/null
+++ b/src/Shakebook/Mustache.hs
@@ -0,0 +1,43 @@
+{-|
+Module      : Shakebook.Mustache
+Description : Slick mustache utilities re-exported to use `Path`
+Copyright   : (c) Daniel Firth 2020
+License     : MIT
+-}
+
+module Shakebook.Mustache (
+  Text.Mustache.Template
+, buildPageAction
+, buildPageActionWithin
+, compileTemplate'
+) where
+
+import Data.Aeson
+import Path
+import RIO
+import qualified Slick.Mustache
+import Development.Shake.Plus
+import Text.Mustache
+import Within
+
+compileTemplate' :: MonadAction m => Path Rel File -> m Template
+compileTemplate' = liftAction . Slick.Mustache.compileTemplate' . toFilePath
+
+{-| 
+  Build a single page straight from a template.
+-}
+buildPageAction :: MonadAction m
+                => Path Rel File -- ^ The HTML templatate.
+                -> Value -- ^ A JSON value.
+                -> Path Rel File -- ^ The out filepath.
+                -> m ()
+buildPageAction template value out = do
+  pageT <- compileTemplate' template
+  writeFile' out $ substitute pageT value
+
+buildPageActionWithin :: MonadAction m
+                      => Within Rel File
+                      -> Value
+                      -> Within Rel File
+                      -> m ()
+buildPageActionWithin template value out = buildPageAction (fromWithin template) value (fromWithin out)
diff --git a/src/Shakebook/Rules.hs b/src/Shakebook/Rules.hs
--- a/src/Shakebook/Rules.hs
+++ b/src/Shakebook/Rules.hs
@@ -25,7 +25,7 @@
 -}
 cofreeRuleGen :: (Traversable w, ComonadCofree f w)
               => w FilePath -- ^ A cofree comonad of FilePaths
-              -> (FilePath -> FilePath) -- ^ How to find the source for each out FilePath.
+              -> (FilePath -> FilePath) -- ^ How to find the out path for each source FilePath.
               -> (w FilePath -> FilePath -> Action ()) -- ^ How to generate a write Action for the target of a comonad. This is extended over the whole comonad.
               -> Rules ()
 cofreeRuleGen xs h k = do
diff --git a/src/Shakebook/Zipper.hs b/src/Shakebook/Zipper.hs
deleted file mode 100644
--- a/src/Shakebook/Zipper.hs
+++ /dev/null
@@ -1,29 +0,0 @@
-{-| Zipper utils that weren't in Control.Comonad.Store.Zipper -}
-
-module Shakebook.Zipper (
-  paginate
-, zipperNextMaybe
-, zipperPreviousMaybe
-, zipperWithin
-) where
-
-import           Control.Comonad.Store
-import           Control.Comonad.Store.Zipper
-import           Data.List.Split
-import           RIO
-
--- Turn a list into a zipper of chunks of length n
-paginate :: Int -> [a] -> Maybe (Zipper [] [a])
-paginate n = zipper . chunksOf n
-
--- Return the peek of the next element if it exists.
-zipperNextMaybe :: Zipper [] a -> Maybe a
-zipperNextMaybe xs = if pos xs < size xs-1 then Just (peeks (+1) xs) else Nothing
-
--- Return the peek of the previous element if it exists.
-zipperPreviousMaybe :: Zipper [] a -> Maybe a
-zipperPreviousMaybe xs = if pos xs > 0 then Just (peeks (+ (-1)) xs) else Nothing
-
--- Return a list of elements within 'r' hops either side of the zipper target.
-zipperWithin :: Int -> Zipper [] a -> [a]
-zipperWithin r xs = (`peek` xs) <$>  [(max 0 (pos xs - r)) .. (min (size xs -1) (pos xs + r))]
diff --git a/test/Spec.hs b/test/Spec.hs
--- a/test/Spec.hs
+++ b/test/Spec.hs
@@ -1,8 +1,10 @@
+{-# LANGUAGE TemplateHaskell #-}
 import           Control.Comonad.Cofree
-import           Control.Comonad.Store.Zipper
+import           Control.Comonad.Zipper.Extra
 import           Data.Aeson
 import           Data.List.Split
-import           Development.Shake
+import           Development.Shake.Plus
+import           Path
 import           RIO
 import           RIO.List
 import qualified RIO.Text                     as T
@@ -14,11 +16,11 @@
 import           Test.Tasty.Golden
 import           Text.Pandoc.Highlighting
 
-srcDir :: String
-srcDir = "test/site"
+srcDir :: Path Rel Dir
+srcDir = $(mkRelDir "test/site")
 
-outDir :: String
-outDir = "test/public"
+outDir :: Path Rel Dir
+outDir = $(mkRelDir "test/public")
 
 baseUrl :: Text
 baseUrl = "http://blanky.test"
@@ -32,7 +34,7 @@
       ]
 
 myBlogNavbar :: [Value] -> Value
-myBlogNavbar = genBlogNavbarData "Blog" "/posts/" (T.pack . defaultPrettyMonthFormat) (T.pack . defaultMonthIndexUrlFormat)
+myBlogNavbar = genBlogNavbarData "Blog" "/posts/" (T.pack . defaultPrettyMonthFormat) (defaultMonthUrlFragment)
 
 numRecentPosts :: Int
 numRecentPosts = 3
@@ -58,7 +60,7 @@
          (affixRecentPosts ["posts/*.md"] numRecentPosts defaultEnrichPost)
 
   defaultPostsPatterns     "posts/*.html" "templates/post.html"
-             (affixBlogNavbar ["posts/*.md"] "Blog" "/posts/" (T.pack . defaultPrettyMonthFormat) (T.pack . defaultMonthIndexUrlFormat) defaultEnrichPost
+             (affixBlogNavbar ["posts/*.md"] "Blog" "/posts/" (T.pack . defaultPrettyMonthFormat) (defaultMonthUrlFragment) defaultEnrichPost
          <=< affixRecentPosts ["posts/*.md"] numRecentPosts defaultEnrichPost
          . defaultEnrichPost . withHighlighting pygments)
               extendPostsZipper
@@ -75,7 +77,7 @@
   defaultMonthIndexPatterns ["posts/*.md"] "templates/post-list.html"
                                 (enrichPostIndexPage ["posts/*.md"])
   defaultStaticsPatterns   ["css//*", "images//*", "js//*", "webfonts//*"]
-  defaultStaticsPhony
+  defaultStaticsPhony   ["css//*", "images//*", "js//*", "webfonts//*"]
 
   defaultSinglePagePhony    "index" "index.html"
   defaultPostsPhony         ["posts/*.md"]
@@ -101,7 +103,7 @@
    logOptions' <- logOptionsHandle stdout True
    lf <- newLogFunc (setLogMinLevel LevelInfo logOptions')
    let f = ShakebookEnv (fst lf) sbc
-   shake shakeOptions $ want ["clean"] >> runShakebook f rules
-   shake shakeOptions $ want ["index", "docs", "month-index", "posts-index", "tag-index", "posts"]  >> runShakebook f rules
+   shake shakeOptions $ want ["clean"] >> runShakePlus f rules
+   shake shakeOptions $ want ["index", "docs", "month-index", "posts-index", "tag-index", "posts"]  >> runShakePlus f rules
    defaultMain $ tests xs
    snd lf
