diff --git a/LICENSE b/LICENSE
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2020 Torsten Schmits
+Copyright (c) 2022 Torsten Schmits
 
 Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
 following conditions are met:
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/chiasma.cabal b/chiasma.cabal
--- a/chiasma.cabal
+++ b/chiasma.cabal
@@ -1,37 +1,36 @@
 cabal-version: 2.2
 
--- This file has been generated from package.yaml by hpack version 0.34.4.
+-- This file has been generated from package.yaml by hpack version 0.34.7.
 --
 -- see: https://github.com/sol/hpack
 
 name:           chiasma
-version:        0.2.0.0
-synopsis:       tmux api
-description:    Please see the README on GitHub at <https://github.com/tek/chiasma>
+version:        0.10.0.0
+synopsis:       A tmux client for Polysemy
+description:    See https://hackage.haskell.org/package/chiasma/docs/Chiasma.html
 category:       Terminal
-homepage:       https://github.com/tek/chiasma-hs#readme
-bug-reports:    https://github.com/tek/chiasma-hs/issues
+homepage:       https://github.com/tek/chiasma#readme
+bug-reports:    https://github.com/tek/chiasma/issues
 author:         Torsten Schmits
-maintainer:     tek@tryp.io
-copyright:      2021 Torsten Schmits
+maintainer:     hackage@tryp.io
+copyright:      2022 Torsten Schmits
 license:        BSD-2-Clause-Patent
 license-file:   LICENSE
 build-type:     Simple
-extra-source-files:
-    readme.md
 
 source-repository head
   type: git
-  location: https://github.com/tek/chiasma-hs
+  location: https://github.com/tek/chiasma
 
 library
   exposed-modules:
-      Chiasma.Api.Class
+      Chiasma
+      Chiasma.Class.CmdArgs
       Chiasma.Codec
       Chiasma.Codec.Data
+      Chiasma.Codec.Data.Client
       Chiasma.Codec.Data.Pane
       Chiasma.Codec.Data.PaneCoords
-      Chiasma.Codec.Data.PaneDetail
       Chiasma.Codec.Data.PaneMode
       Chiasma.Codec.Data.PanePid
       Chiasma.Codec.Data.Session
@@ -42,36 +41,71 @@
       Chiasma.Command.Session
       Chiasma.Command.Window
       Chiasma.Control.IO.Unsafe
+      Chiasma.Data.Axis
+      Chiasma.Data.CapturePaneParams
       Chiasma.Data.Cmd
-      Chiasma.Data.Conduit
+      Chiasma.Data.CodecError
+      Chiasma.Data.CopyModeParams
+      Chiasma.Data.DecodeError
+      Chiasma.Data.Direction
+      Chiasma.Data.Event
       Chiasma.Data.Ident
+      Chiasma.Data.KillPaneParams
+      Chiasma.Data.LayoutError
       Chiasma.Data.List
-      Chiasma.Data.Maybe
+      Chiasma.Data.Panes
+      Chiasma.Data.PaneSelection
+      Chiasma.Data.PipePaneParams
+      Chiasma.Data.ProcessError
+      Chiasma.Data.ReceiveEvent
       Chiasma.Data.RenderError
+      Chiasma.Data.ResizePaneParams
+      Chiasma.Data.SelectParams
+      Chiasma.Data.SelectWindowParams
+      Chiasma.Data.SendKeysParams
+      Chiasma.Data.SessionGroupId
+      Chiasma.Data.SessionParams
+      Chiasma.Data.SplitParams
+      Chiasma.Data.Target
+      Chiasma.Data.TmuxCommand
       Chiasma.Data.TmuxError
       Chiasma.Data.TmuxId
+      Chiasma.Data.TmuxNative
+      Chiasma.Data.TmuxOutputBlock
+      Chiasma.Data.TmuxQuery
+      Chiasma.Data.TmuxRequest
+      Chiasma.Data.TmuxResponse
       Chiasma.Data.TmuxThunk
       Chiasma.Data.View
       Chiasma.Data.Views
+      Chiasma.Data.WindowParams
+      Chiasma.Data.WindowSelection
       Chiasma.Data.WindowState
+      Chiasma.Data.WithPaneId
+      Chiasma.Effect.Codec
+      Chiasma.Effect.TmuxApi
+      Chiasma.Effect.TmuxClient
+      Chiasma.Event
+      Chiasma.Function
+      Chiasma.Interpreter.Codec
+      Chiasma.Interpreter.ProcessOutput
+      Chiasma.Interpreter.Pure
+      Chiasma.Interpreter.TmuxApi
+      Chiasma.Interpreter.TmuxClient
       Chiasma.Lens.Tree
       Chiasma.Lens.Where
       Chiasma.Monad.EvalFreeT
-      Chiasma.Monad.IndividualProcess
-      Chiasma.Monad.Stream
       Chiasma.Monad.Tmux
-      Chiasma.Native.Api
       Chiasma.Native.Parse
-      Chiasma.Native.Process
-      Chiasma.Native.StreamParse
+      Chiasma.Native.TmuxOutputBlock
       Chiasma.Pack
       Chiasma.Pane
-      Chiasma.Prelude
+      Chiasma.Path
       Chiasma.Render
       Chiasma.Session
-      Chiasma.Test.File
-      Chiasma.Test.Screenshot
-      Chiasma.Test.Tmux
+      Chiasma.Tmux
+      Chiasma.TmuxApi
+      Chiasma.TmuxNative
       Chiasma.Ui.Data.Measure
       Chiasma.Ui.Data.RenderableTree
       Chiasma.Ui.Data.Tree
@@ -89,14 +123,10 @@
       Chiasma.View
       Chiasma.View.State
       Chiasma.Window
-  other-modules:
-      Prelude
-      Paths_chiasma
-  autogen-modules:
-      Paths_chiasma
   hs-source-dirs:
       lib
   default-extensions:
+      ImpredicativeTypes
       AllowAmbiguousTypes
       ApplicativeDo
       BangPatterns
@@ -110,11 +140,14 @@
       DeriveFoldable
       DeriveFunctor
       DeriveGeneric
+      DeriveLift
       DeriveTraversable
       DerivingStrategies
+      DerivingVia
       DisambiguateRecordFields
       DoAndIfThenElse
       DuplicateRecordFields
+      EmptyCase
       EmptyDataDecls
       ExistentialQuantification
       FlexibleContexts
@@ -129,120 +162,9 @@
       MultiParamTypeClasses
       MultiWayIf
       NamedFieldPuns
-      OverloadedStrings
+      OverloadedLabels
       OverloadedLists
-      PackageImports
-      PartialTypeSignatures
-      PatternGuards
-      PatternSynonyms
-      PolyKinds
-      QuantifiedConstraints
-      QuasiQuotes
-      RankNTypes
-      RecordWildCards
-      RecursiveDo
-      ScopedTypeVariables
-      StandaloneDeriving
-      TemplateHaskell
-      TupleSections
-      TypeApplications
-      TypeFamilies
-      TypeFamilyDependencies
-      TypeOperators
-      TypeSynonymInstances
-      UndecidableInstances
-      UnicodeSyntax
-      ViewPatterns
-  build-depends:
-      aeson >=1.4 && <1.6
-    , attoparsec ==0.13.*
-    , base ==4.*
-    , bytestring
-    , composition ==1.0.*
-    , composition-extra ==2.0.*
-    , conduit ==1.3.*
-    , conduit-extra ==1.3.*
-    , containers
-    , cornea ==0.4.*
-    , data-default ==0.7.*
-    , deepseq
-    , directory
-    , either >=5.0.1 && <5.1
-    , exceptions
-    , filepath
-    , free ==5.1.*
-    , lens ==4.*
-    , mtl
-    , parsec
-    , parsers ==0.12.*
-    , posix-pty ==0.2.*
-    , prettyprinter >=1.6 && <1.8
-    , prettyprinter-ansi-terminal ==1.1.*
-    , process ==1.6.*
-    , random ==1.1.*
-    , relude >=0.7 && <1.2
-    , resourcet ==1.2.*
-    , split ==0.2.*
-    , stm-chans ==3.0.*
-    , stm-conduit ==4.0.*
-    , text
-    , transformers
-    , typed-process ==0.2.*
-    , unix
-    , unliftio ==0.2.*
-    , unliftio-core >=0.1 && <0.3
-    , uuid ==1.3.*
-  mixins:
-      base hiding (Prelude)
-  default-language: Haskell2010
-
-test-suite chiasma-integration
-  type: exitcode-stdio-1.0
-  main-is: Main.hs
-  other-modules:
-      Chiasma.Test.IndividualProcessTest
-      Chiasma.Test.ProcessTest
-      Chiasma.Test.RenderTest
-      Chiasma.Test.TmuxStreamTest
-      Chiasma.Test.Util
-      Paths_chiasma
-  hs-source-dirs:
-      integration
-  default-extensions:
-      AllowAmbiguousTypes
-      ApplicativeDo
-      BangPatterns
-      BinaryLiterals
-      BlockArguments
-      ConstraintKinds
-      DataKinds
-      DefaultSignatures
-      DeriveAnyClass
-      DeriveDataTypeable
-      DeriveFoldable
-      DeriveFunctor
-      DeriveGeneric
-      DeriveTraversable
-      DerivingStrategies
-      DisambiguateRecordFields
-      DoAndIfThenElse
-      DuplicateRecordFields
-      EmptyDataDecls
-      ExistentialQuantification
-      FlexibleContexts
-      FlexibleInstances
-      FunctionalDependencies
-      GADTs
-      GeneralizedNewtypeDeriving
-      InstanceSigs
-      KindSignatures
-      LambdaCase
-      LiberalTypeSynonyms
-      MultiParamTypeClasses
-      MultiWayIf
-      NamedFieldPuns
       OverloadedStrings
-      OverloadedLists
       PackageImports
       PartialTypeSignatures
       PatternGuards
@@ -253,126 +175,7 @@
       RankNTypes
       RecordWildCards
       RecursiveDo
-      ScopedTypeVariables
-      StandaloneDeriving
-      TemplateHaskell
-      TupleSections
-      TypeApplications
-      TypeFamilies
-      TypeFamilyDependencies
-      TypeOperators
-      TypeSynonymInstances
-      UndecidableInstances
-      UnicodeSyntax
-      ViewPatterns
-  ghc-options: -threaded -rtsopts -with-rtsopts=-N
-  build-depends:
-      aeson >=1.4 && <1.6
-    , attoparsec ==0.13.*
-    , base ==4.*
-    , bytestring
-    , chiasma
-    , composition ==1.0.*
-    , composition-extra ==2.0.*
-    , conduit ==1.3.*
-    , conduit-extra ==1.3.*
-    , containers
-    , cornea ==0.4.*
-    , data-default ==0.7.*
-    , deepseq
-    , directory
-    , either >=5.0.1 && <5.1
-    , exceptions
-    , filepath
-    , free ==5.1.*
-    , hedgehog
-    , lens ==4.*
-    , mtl
-    , parsec
-    , parsers ==0.12.*
-    , posix-pty ==0.2.*
-    , prettyprinter >=1.6 && <1.8
-    , prettyprinter-ansi-terminal ==1.1.*
-    , process ==1.6.*
-    , random ==1.1.*
-    , relude >=0.7 && <1.2
-    , resourcet ==1.2.*
-    , split ==0.2.*
-    , stm-chans ==3.0.*
-    , stm-conduit ==4.0.*
-    , tasty
-    , tasty-hedgehog
-    , text
-    , transformers
-    , typed-process ==0.2.*
-    , unix
-    , unliftio ==0.2.*
-    , unliftio-core >=0.1 && <0.3
-    , uuid ==1.3.*
-  mixins:
-      base hiding (Prelude)
-    , chiasma (Chiasma.Prelude as Prelude)
-    , chiasma hiding (Chiasma.Prelude)
-  default-language: Haskell2010
-
-test-suite chiasma-unit
-  type: exitcode-stdio-1.0
-  main-is: Main.hs
-  other-modules:
-      Chiasma.Test.CodecTest
-      Chiasma.Test.LensTest
-      Chiasma.Test.OutputParseTest
-      Chiasma.Test.PinTest
-      Chiasma.Test.StreamParseTest
-      Chiasma.Test.Util
-      Paths_chiasma
-  hs-source-dirs:
-      test
-  default-extensions:
-      AllowAmbiguousTypes
-      ApplicativeDo
-      BangPatterns
-      BinaryLiterals
-      BlockArguments
-      ConstraintKinds
-      DataKinds
-      DefaultSignatures
-      DeriveAnyClass
-      DeriveDataTypeable
-      DeriveFoldable
-      DeriveFunctor
-      DeriveGeneric
-      DeriveTraversable
-      DerivingStrategies
-      DisambiguateRecordFields
-      DoAndIfThenElse
-      DuplicateRecordFields
-      EmptyDataDecls
-      ExistentialQuantification
-      FlexibleContexts
-      FlexibleInstances
-      FunctionalDependencies
-      GADTs
-      GeneralizedNewtypeDeriving
-      InstanceSigs
-      KindSignatures
-      LambdaCase
-      LiberalTypeSynonyms
-      MultiParamTypeClasses
-      MultiWayIf
-      NamedFieldPuns
-      OverloadedStrings
-      OverloadedLists
-      PackageImports
-      PartialTypeSignatures
-      PatternGuards
-      PatternSynonyms
-      PolyKinds
-      QuantifiedConstraints
-      QuasiQuotes
-      RankNTypes
-      RecordWildCards
-      RecursiveDo
+      RoleAnnotations
       ScopedTypeVariables
       StandaloneDeriving
       TemplateHaskell
@@ -385,52 +188,36 @@
       UndecidableInstances
       UnicodeSyntax
       ViewPatterns
-  ghc-options: -threaded -rtsopts -with-rtsopts=-N
+  ghc-options: -Wall -Wredundant-constraints -Wincomplete-uni-patterns -Wmissing-deriving-strategies -Widentities -Wunused-packages -fplugin=Polysemy.Plugin
   build-depends:
-      aeson >=1.4 && <1.6
-    , attoparsec ==0.13.*
-    , base ==4.*
+      attoparsec >=0.13
+    , base >=4.12 && <5
     , bytestring
-    , chiasma
-    , composition ==1.0.*
-    , composition-extra ==2.0.*
-    , conduit ==1.3.*
-    , conduit-extra ==1.3.*
+    , composition >=1.0
     , containers
-    , cornea ==0.4.*
-    , data-default ==0.7.*
-    , deepseq
-    , directory
-    , either >=5.0.1 && <5.1
-    , exceptions
-    , filepath
-    , free ==5.1.*
-    , hedgehog
-    , lens ==4.*
-    , mtl
+    , exon
+    , extra
+    , first-class-families
+    , lens >=4
     , parsec
-    , parsers ==0.12.*
-    , posix-pty ==0.2.*
-    , prettyprinter >=1.6 && <1.8
-    , prettyprinter-ansi-terminal ==1.1.*
-    , process ==1.6.*
-    , random ==1.1.*
-    , relude >=0.7 && <1.2
-    , resourcet ==1.2.*
-    , split ==0.2.*
-    , stm-chans ==3.0.*
-    , stm-conduit ==4.0.*
-    , tasty
-    , tasty-hedgehog
+    , parsers >=0.12
+    , path
+    , polysemy
+    , polysemy-conc >=0.9
+    , polysemy-log >=0.7
+    , polysemy-plugin
+    , polysemy-process >=0.9
+    , polysemy-time >=0.5
+    , prelate >=0.2
+    , prettyprinter >=1.6
+    , prettyprinter-ansi-terminal >=1.1
+    , random >=1.1
     , text
     , transformers
-    , typed-process ==0.2.*
-    , unix
-    , unliftio ==0.2.*
-    , unliftio-core >=0.1 && <0.3
-    , uuid ==1.3.*
+    , typed-process >=0.2
+    , uuid >=1.3
   mixins:
       base hiding (Prelude)
-    , chiasma (Chiasma.Prelude as Prelude)
-    , chiasma hiding (Chiasma.Prelude)
+    , prelate (Prelate as Prelude)
+    , prelate hiding (Prelate)
   default-language: Haskell2010
diff --git a/integration/Chiasma/Test/IndividualProcessTest.hs b/integration/Chiasma/Test/IndividualProcessTest.hs
deleted file mode 100644
--- a/integration/Chiasma/Test/IndividualProcessTest.hs
+++ /dev/null
@@ -1,42 +0,0 @@
-module Chiasma.Test.IndividualProcessTest where
-
-import Chiasma.Codec.Data (Pane(Pane), Window(Window))
-import Chiasma.Data.TmuxError (TmuxError)
-import Chiasma.Data.TmuxId (PaneId(..), WindowId(..))
-import Chiasma.Monad.IndividualProcess (TmuxProg, runTmux)
-import qualified Chiasma.Monad.Tmux as Tmux (read, write)
-import Chiasma.Native.Api (TmuxNative(..))
-import Hedgehog ((===))
-
-import Chiasma.Test.Tmux (tmuxSpec)
-import Chiasma.Test.Util (UnitTest)
-
-prog :: TmuxProg ([Pane], [Pane], [Window])
-prog = do
-  panes1 <- Tmux.read "list-panes" ["-t", "%0"]
-  Tmux.write "new-window" []
-  Tmux.write "new-window" []
-  wins <- Tmux.read "list-windows" []
-  panes <- Tmux.read "list-panes" ["-a"]
-  return (panes1, panes, wins)
-
-p :: Int -> Pane
-p i = Pane (PaneId i) 240 60
-
-w :: Int -> Window
-w i = Window (WindowId i) 240 60
-
-runProg :: TmuxNative -> IO (Either TmuxError ([Pane], [Pane], [Window]))
-runProg api = runExceptT $ runTmux api prog
-
-test_individual :: UnitTest
-test_individual = do
-  result :: Either TmuxError ([Pane], [Pane], [Window]) <- liftIO (tmuxSpec runProg)
-  Right ([p0], [p0, p1, p2], [w0, w1, w2]) === result
-  where
-    p0 = p 0
-    p1 = p 1
-    p2 = p 2
-    w0 = w 0
-    w1 = w 1
-    w2 = w 2
diff --git a/integration/Chiasma/Test/ProcessTest.hs b/integration/Chiasma/Test/ProcessTest.hs
deleted file mode 100644
--- a/integration/Chiasma/Test/ProcessTest.hs
+++ /dev/null
@@ -1,42 +0,0 @@
-module Chiasma.Test.ProcessTest where
-
-import Chiasma.Data.Conduit (createSinkFlush)
-import Chiasma.Native.Api (TmuxNative(..))
-import Chiasma.Native.Process (socketArg)
-import Conduit (ConduitT, Flush(..), mapC, runConduit, sinkList, yieldMany, (.|))
-import Data.ByteString.Internal (packChars)
-import qualified Data.Conduit.Combinators as Conduit (concatMap, take)
-import Data.Conduit.Process.Typed (
-  Process,
-  createSource,
-  getStdin,
-  getStdout,
-  proc,
-  setStdin,
-  setStdout,
-  withProcessTerm,
-  )
-import qualified Data.Text as Text (splitOn)
-import Hedgehog (assert)
-
-import Chiasma.Test.Tmux (tmuxSpec)
-import Chiasma.Test.Util (UnitTest)
-
-run :: Process (ConduitT (Flush ByteString) Void IO ()) (ConduitT () ByteString IO ()) () -> IO [Text]
-run prc = do
-  let stdin' = mapC (fmap packChars) .| getStdin prc
-  let stdout' = getStdout prc
-  runConduit $ yieldMany ([Chunk "list-panes\n"] :: [Flush String]) .| stdin'
-  runConduit $ yieldMany ([Flush] :: [Flush String]) .| stdin'
-  runConduit $ stdout' .| Conduit.take 2 .| mapC decodeUtf8 .| Conduit.concatMap (Text.splitOn "\n") .| sinkList
-
-test_process :: UnitTest
-test_process = do
-  out <- liftIO $ tmuxSpec $ \(TmuxNative sock) -> do
-    withProcessTerm (pc (args sock)) run
-  assert (length out > 3)
-    where
-      pc =
-        setStdin createSinkFlush . setStdout createSource . proc "tmux"
-      args sock =
-        toString <$> socketArg sock ++ ["-C", "attach"]
diff --git a/integration/Chiasma/Test/RenderTest.hs b/integration/Chiasma/Test/RenderTest.hs
deleted file mode 100644
--- a/integration/Chiasma/Test/RenderTest.hs
+++ /dev/null
@@ -1,216 +0,0 @@
-module Chiasma.Test.RenderTest where
-
-import Chiasma.Codec.Data.PaneDetail (PaneDetail(..))
-import Chiasma.Command.Pane (panesAs)
-import Chiasma.Command.Session (activateSession)
-import Chiasma.Data.Ident (Ident(Str))
-import Chiasma.Data.TmuxError (TmuxError)
-import Chiasma.Data.TmuxId (PaneId(PaneId), SessionId(SessionId), WindowId(WindowId))
-import Chiasma.Data.View (viewIdent)
-import qualified Chiasma.Data.View as Tmux (View(View))
-import Chiasma.Data.Views (Views(Views))
-import Chiasma.Monad.Stream (runTmux)
-import Chiasma.Native.Api (TmuxNative(..))
-import Chiasma.Render (render)
-import Chiasma.Ui.Data.View (Tree(..), TreeSub(..), ViewTree, consLayout, consLayoutVertical)
-import qualified Chiasma.Ui.Data.View as Ui (Pane(Pane), View(View))
-import Chiasma.Ui.Data.ViewGeometry (ViewGeometry(..))
-import Chiasma.Ui.Data.ViewState (ViewState(ViewState))
-import Chiasma.Ui.ViewTree (togglePane)
-import qualified Chiasma.Ui.ViewTree as ToggleResult (ToggleResult(Success))
-import Hedgehog (evalEither, (===))
-import Test.Tasty (TestTree, testGroup)
-import UnliftIO (throwString)
-import UnliftIO.Directory (getCurrentDirectory)
-
-import Chiasma.Test.Tmux (sleep, tmuxSpec)
-import Chiasma.Test.Util (UnitTest, integrationTest)
-
-id0, id1, id2, id3 :: Ident
-id0 = Str "0"
-id1 = Str "1"
-id2 = Str "2"
-id3 = Str "3"
-
-views :: Views
-views =
-  Views
-    [Tmux.View id0 (Just (SessionId 0))]
-    [Tmux.View id0 (Just (WindowId 0))]
-    [Tmux.View id0 (Just (PaneId 0))]
-    []
-
-renderOnce :: ViewTree -> TmuxNative -> FilePath -> Views -> IO Views
-renderOnce tree api cwd vs = do
-  r <- runExceptT $ runStateT (runTmux api $ render cwd id1 id1 tree) vs
-  either (throwString . show) (return . snd) r
-
-safeRender :: Views -> ViewTree -> TmuxNative -> IO Views
-safeRender vs tree api = do
-  cwd <- getCurrentDirectory
-  vs1 <- renderOnce tree api cwd vs
-  _ <- runExceptT @TmuxError $ runTmux api $ activateSession 0
-  _ <- runExceptT @TmuxError $ runTmux api $ activateSession 1
-  sleep 1
-  renderOnce tree api cwd vs1
-
-runRender :: ViewTree -> TmuxNative -> IO ([Tmux.View PaneId], Either TmuxError [PaneDetail])
-runRender tree api = do
-  (Views _ _ vs2 _) <- safeRender views tree api
-  sleep 1
-  ps <- runExceptT @TmuxError $ runTmux api panesAs
-  return (sortOn viewIdent vs2, sortOn paneLeft . drop 1 <$> ps)
-
-renderSpec :: ViewTree -> [PaneDetail] -> UnitTest
-renderSpec tree target = do
-  (_, pse) <- liftIO $ tmuxSpec $ runRender tree
-  ps <- evalEither pse
-  target === ps
-
-treeImbalanced :: ViewTree
-treeImbalanced =
-  Tree (consLayout id0) [
-    TreeLeaf (openPane id0 def),
-    TreeLeaf (openPane id1 (ViewGeometry Nothing Nothing (Just 150) Nothing Nothing Nothing))
-    ]
-  where
-    openPane i geo = Ui.View i (ViewState False) geo (Ui.Pane True False Nothing)
-
-imbalancedTarget :: [PaneDetail]
-imbalancedTarget =
-  [
-    PaneDetail { paneId = PaneId 1, paneWidth = 89, paneHeight = 60, paneTop = 0, paneLeft = 0 },
-    PaneDetail { paneId = PaneId 2, paneWidth = 150, paneHeight = 60, paneTop = 0, paneLeft = 90 }
-    ]
-
-test_imbalanced :: UnitTest
-test_imbalanced =
-  renderSpec treeImbalanced imbalancedTarget
-
-treeNested :: ViewTree
-treeNested =
-  Tree (consLayoutVertical id0) [
-    TreeNode $
-      Tree (consLayout id1) [
-        TreeLeaf (openPane id0 def),
-        TreeNode $
-          Tree (consLayoutVertical id2) [
-            TreeLeaf (openPane id1 def),
-            TreeLeaf (openPane id2 def)
-            ]
-        ]
-    ]
-  where
-    openPane i geo = Ui.View i (ViewState False) geo (Ui.Pane True False Nothing)
-
-nestedTarget :: [PaneDetail]
-nestedTarget =
-  [
-    PaneDetail { paneId = PaneId 1, paneWidth = 120, paneHeight = 60, paneTop = 0, paneLeft = 0 },
-    PaneDetail { paneId = PaneId 2, paneWidth = 119, paneHeight = 30, paneTop = 0, paneLeft = 121 },
-    PaneDetail { paneId = PaneId 3, paneWidth = 119, paneHeight = 29, paneTop = 31, paneLeft = 121 }
-  ]
-
-test_nested :: UnitTest
-test_nested =
-  renderSpec treeNested nestedTarget
-
-treeTwoLayouts :: ViewTree
-treeTwoLayouts =
-  Tree (consLayout id0) [
-    TreeNode $
-      Tree (consLayoutVertical id1) [
-        TreeLeaf (openPane id0 def)
-        ],
-    TreeNode $
-      Tree (consLayoutVertical id2) [
-        TreeLeaf (openPane id1 def)
-        ]
-    ]
-  where
-    openPane i geo = Ui.View i (ViewState False) geo (Ui.Pane True False Nothing)
-
-twoLayoutsTarget :: [PaneDetail]
-twoLayoutsTarget =
-  [
-    PaneDetail { paneId = PaneId 1, paneWidth = 120, paneHeight = 60, paneTop = 0, paneLeft = 0 },
-    PaneDetail { paneId = PaneId 2, paneWidth = 119, paneHeight = 60, paneTop = 0, paneLeft = 121 }
-    ]
-
-test_twoLayouts :: UnitTest
-test_twoLayouts =
-  renderSpec treeTwoLayouts twoLayoutsTarget
-
-treePosition :: ViewTree
-treePosition =
-  Tree (consLayout id0) [
-    TreeLeaf (openPane id0 1 (Just 10)),
-    TreeLeaf (openPane id1 4 (Just 20)),
-    TreeLeaf (openPane id2 2 (Just 30)),
-    TreeLeaf (openPane id3 3 Nothing)
-    ]
-  where
-    openPane i pos size =
-      Ui.View i (ViewState False) def { position = Just pos, fixedSize = size } (Ui.Pane True False Nothing)
-
-positionTarget :: [PaneDetail]
-positionTarget =
-  [
-    PaneDetail { paneId = PaneId 1, paneWidth = 10, paneHeight = 60, paneTop = 0, paneLeft = 0 },
-    PaneDetail { paneId = PaneId 2, paneWidth = 30, paneHeight = 60, paneTop = 0, paneLeft = 11 },
-    PaneDetail { paneId = PaneId 3, paneWidth = 177, paneHeight = 60, paneTop = 0, paneLeft = 42 },
-    PaneDetail { paneId = PaneId 4, paneWidth = 20, paneHeight = 60, paneTop = 0, paneLeft = 220 }
-    ]
-
-test_position :: UnitTest
-test_position =
-  renderSpec treePosition positionTarget
-
-treeSuccessiveOpen :: ViewTree
-treeSuccessiveOpen =
-  Tree (consLayout id0) [
-    TreeNode $ Tree (consLayoutVertical id1) [
-      TreeLeaf (pane id0 True False)
-      ],
-    TreeNode $ Tree (consLayoutVertical id2) [
-      TreeLeaf (pane id1 False True),
-      TreeLeaf (pane id2 False False)
-      ]
-    ]
-  where
-    pane i open pin =
-      Ui.View i (ViewState False) def (Ui.Pane open pin Nothing)
-
-successiveOpenTarget :: [PaneDetail]
-successiveOpenTarget =
-  [
-    PaneDetail { paneId = PaneId 1, paneWidth = 120, paneHeight = 60, paneTop = 0, paneLeft = 0 },
-    PaneDetail { paneId = PaneId 2, paneWidth = 119, paneHeight = 30, paneTop = 0, paneLeft = 121 },
-    PaneDetail { paneId = PaneId 3, paneWidth = 119, paneHeight = 29, paneTop = 31, paneLeft = 121 }
-    ]
-
-test_successiveOpen :: UnitTest
-test_successiveOpen = do
-  ps <- liftIO $ tmuxSpec \ api -> do
-    vs1 <- safeRender views treeSuccessiveOpen api
-    sleep 0.2
-    vs2 <- safeRender vs1 tree1 api
-    sleep 0.2
-    _ <- safeRender vs2 tree2 api
-    sleep 0.2
-    runExceptT @TmuxError $ runTmux api panesAs
-  ps1 <- evalEither (sortOn paneId . drop 1 <$> ps)
-  successiveOpenTarget === ps1
-  where
-    (ToggleResult.Success tree2) = togglePane id2 tree1
-    (ToggleResult.Success tree1) = togglePane id1 treeSuccessiveOpen
-
-test_render :: TestTree
-test_render =
-  testGroup "render" [
-    integrationTest "imbalanced tree" test_imbalanced,
-    integrationTest "nested panes" test_nested,
-    integrationTest "two layouts" test_twoLayouts,
-    integrationTest "fixed pane position" test_position,
-    integrationTest "open multiple panes" test_successiveOpen
-  ]
diff --git a/integration/Chiasma/Test/TmuxStreamTest.hs b/integration/Chiasma/Test/TmuxStreamTest.hs
deleted file mode 100644
--- a/integration/Chiasma/Test/TmuxStreamTest.hs
+++ /dev/null
@@ -1,44 +0,0 @@
-module Chiasma.Test.TmuxStreamTest where
-
-import Chiasma.Codec.Data (Pane(Pane), Window(Window))
-import Chiasma.Data.TmuxError (TmuxError)
-import Chiasma.Data.TmuxId (PaneId(..), WindowId(..))
-import Chiasma.Data.TmuxThunk (TmuxThunk)
-import Chiasma.Monad.Stream (runTmux)
-import qualified Chiasma.Monad.Tmux as Tmux (read, write)
-import Chiasma.Native.Api (TmuxNative(..))
-import Control.Monad.Free.Class (MonadFree)
-import Hedgehog ((===))
-
-import Chiasma.Test.Tmux (tmuxSpec)
-import Chiasma.Test.Util (UnitTest)
-
-prog :: MonadFree TmuxThunk m => m ([Pane], [Pane], [Window])
-prog = do
-  panes1 <- Tmux.read "list-panes" ["-t", "%0"]
-  Tmux.write "new-window" []
-  Tmux.write "new-window" []
-  wins <- Tmux.read "list-windows" []
-  panes <- Tmux.read "list-panes" ["-a"]
-  return (panes1, panes, wins)
-
-p :: Int -> Pane
-p i = Pane (PaneId i) 240 60
-
-w :: Int -> Window
-w i = Window (WindowId i) 240 60
-
-runProg :: TmuxNative -> IO (Either TmuxError ([Pane], [Pane], [Window]))
-runProg api = runExceptT @TmuxError $ runTmux api prog
-
-test_streamed :: UnitTest
-test_streamed = do
-  result <- liftIO (tmuxSpec runProg)
-  (Right ([p0], [p0, p1, p2], [w0, w1, w2])) === result
-  where
-    p0 = p 0
-    p1 = p 1
-    p2 = p 2
-    w0 = w 0
-    w1 = w 1
-    w2 = w 2
diff --git a/integration/Chiasma/Test/Util.hs b/integration/Chiasma/Test/Util.hs
deleted file mode 100644
--- a/integration/Chiasma/Test/Util.hs
+++ /dev/null
@@ -1,19 +0,0 @@
-module Chiasma.Test.Util where
-
-import Hedgehog (TestT, withTests, property, test)
-import Test.Tasty (TestName, TestTree)
-import Test.Tasty.Hedgehog (testProperty)
-import qualified System.Environment as Environment
-
-type UnitTest = TestT IO ()
-
-checkEnv :: UnitTest -> UnitTest
-checkEnv t =
-  maybe unit (const t) =<< liftIO (Environment.lookupEnv "DISPLAY")
-
-integrationTest ::
-  TestName ->
-  UnitTest ->
-  TestTree
-integrationTest desc =
-  testProperty desc . withTests 1 . property . test . checkEnv
diff --git a/integration/Main.hs b/integration/Main.hs
deleted file mode 100644
--- a/integration/Main.hs
+++ /dev/null
@@ -1,21 +0,0 @@
-module Main where
-
-import Chiasma.Test.IndividualProcessTest (test_individual)
-import Chiasma.Test.ProcessTest (test_process)
-import Chiasma.Test.RenderTest (test_render)
-import Chiasma.Test.TmuxStreamTest (test_streamed)
-import Chiasma.Test.Util (integrationTest)
-import Test.Tasty (TestTree, defaultMain, testGroup)
-
-tests :: TestTree
-tests =
-  testGroup "all" [
-    integrationTest "use individual subprocesses for queries" test_individual,
-    integrationTest "tmux control process" test_process,
-    test_render,
-    integrationTest "streamed tmux commands" test_streamed
-  ]
-
-main :: IO ()
-main =
-  defaultMain tests
diff --git a/lib/Chiasma.hs b/lib/Chiasma.hs
new file mode 100644
--- /dev/null
+++ b/lib/Chiasma.hs
@@ -0,0 +1,12 @@
+module Chiasma (
+  TmuxApi,
+  Tmux,
+  Codec,
+  NativeCodec,
+  NativeCodecE,
+  NativeCommandCodec,
+  NativeCommandCodecE,
+) where
+
+import Chiasma.Effect.Codec (Codec, NativeCodec, NativeCodecE, NativeCommandCodec, NativeCommandCodecE)
+import Chiasma.Effect.TmuxApi (Tmux, TmuxApi)
diff --git a/lib/Chiasma/Api/Class.hs b/lib/Chiasma/Api/Class.hs
deleted file mode 100644
--- a/lib/Chiasma/Api/Class.hs
+++ /dev/null
@@ -1,19 +0,0 @@
-module Chiasma.Api.Class where
-
-import Conduit (ConduitT, Flush)
-
-import Chiasma.Codec.Decode (TmuxDecodeError)
-import Chiasma.Data.Cmd (Cmd, Cmds)
-import Chiasma.Native.StreamParse (TmuxOutputBlock)
-
-class TmuxApi m a where
-  runCommands ::
-    a ->
-    (Text -> Either TmuxDecodeError b) ->
-    Cmds ->
-    m [b]
-
-  withTmux ::
-    a ->
-    (ConduitT (Flush Cmd) Void m () -> ConduitT () TmuxOutputBlock m () -> m b) ->
-    m b
diff --git a/lib/Chiasma/Class/CmdArgs.hs b/lib/Chiasma/Class/CmdArgs.hs
new file mode 100644
--- /dev/null
+++ b/lib/Chiasma/Class/CmdArgs.hs
@@ -0,0 +1,55 @@
+module Chiasma.Class.CmdArgs where
+
+import qualified Data.Map.Strict as Map
+import Exon (exon)
+
+import Chiasma.Data.Ident (Ident, identText)
+
+class CmdArgs a where
+  cmdArgs :: a -> [Text]
+
+flag :: [Text] -> Bool -> [Text]
+flag =
+  bool []
+
+flag1 :: Text -> Bool -> [Text]
+flag1 =
+  flag . pure
+
+optionWith :: Text -> (a -> Text) -> Maybe a -> [Text]
+optionWith flg f =
+  foldMap \ a -> [flg, f a]
+
+option :: Text -> Maybe Text -> [Text]
+option flg =
+  optionWith flg id
+
+identOption :: Text -> Maybe Ident -> [Text]
+identOption flg =
+  optionWith flg identText
+
+arg :: Maybe Text -> [Text]
+arg =
+  maybeToList
+
+envVars :: Map Text Text -> [Text]
+envVars =
+  concatMap \case
+    (k, v) -> ["-e", [exon|#{k}=#{v}|]]
+  .
+  Map.toList
+
+optionArgs ::
+  CmdArgs a =>
+  Maybe a ->
+  [Text]
+optionArgs =
+  foldMap cmdArgs
+
+optionArgsWith ::
+  CmdArgs a =>
+  Text ->
+  Maybe a ->
+  [Text]
+optionArgsWith flg =
+  foldMap \ a -> flg : cmdArgs a
diff --git a/lib/Chiasma/Codec.hs b/lib/Chiasma/Codec.hs
--- a/lib/Chiasma/Codec.hs
+++ b/lib/Chiasma/Codec.hs
@@ -1,37 +1,74 @@
 module Chiasma.Codec where
 
-import Chiasma.Data.TmuxId (PaneId, SessionId, WindowId)
-import qualified Data.Text as Text (drop, dropEnd, splitOn, take, takeEnd, unwords)
+import qualified Data.Text as Text
 import GHC.Generics (Rep, to)
+import Prelude hiding (to)
 
-import Chiasma.Codec.Decode (TmuxDataDecode(..), TmuxDecodeError(TooManyFields))
-import Chiasma.Codec.Query (TmuxDataQuery(..))
+import Chiasma.Codec.Decode (TmuxDataDecode (..))
+import Chiasma.Codec.Query (TmuxDataQuery (..))
+import qualified Chiasma.Data.DecodeError as DecodeFailure (DecodeFailure (..))
+import Chiasma.Data.DecodeError (DecodeError (DecodeError), DecodeFailure)
+import Chiasma.Data.TmuxId (PaneId, SessionId, WindowId)
+import Chiasma.Data.TmuxQuery (TmuxQuery (TmuxQuery))
 
-newtype TmuxQuery =
-  TmuxQuery { unQ :: Text }
-  deriving (Eq, Show)
+-- |Remove one leading and trailing space from tmux output if both are present.
+tryTrim :: Text -> Maybe Text
+tryTrim text = do
+  (prefix, lastChar) <- Text.unsnoc text
+  guard (lastChar == ' ')
+  (firstChar, payload) <- Text.uncons prefix
+  guard (firstChar == ' ')
+  pure payload
 
-genDecode :: (Generic a, TmuxDataDecode (Rep a)) => Text -> Either TmuxDecodeError a
+trim :: Text -> Text
+trim text =
+  fromMaybe text (tryTrim text)
+
+checkRemainder :: ([Text], b) -> Either DecodeFailure b
+checkRemainder = \case
+  ([], result) -> Right result
+  (a, _) -> Left (DecodeFailure.TooManyFields a)
+
+genDecode ::
+  Generic a =>
+  TmuxDataDecode (Rep a) =>
+  Text ->
+  Either DecodeError a
 genDecode fields = do
-  (rest, result) <- decode' (Text.splitOn " " . trim $ fields)
-  case rest of
-    [] -> return $ to result
-    a -> Left $ TooManyFields a
-  where
-    trim text =
-      if Text.take 1 text == " " && Text.takeEnd 1 text == " "
-      then Text.drop 1 . Text.dropEnd 1 $ text
-      else text
+  bimap (DecodeError [fields]) to do
+    checkRemainder =<< dataDecode (Text.splitOn " " (trim fields))
 
 class TmuxCodec a where
-    decode :: Text -> Either TmuxDecodeError a
-    default decode :: (Generic a, TmuxDataDecode (Rep a)) => Text -> Either TmuxDecodeError a
-    decode = genDecode
+  decode :: Text -> Either DecodeError a
+  default decode ::
+    Generic a =>
+    TmuxDataDecode (Rep a) =>
+    Text ->
+    Either DecodeError a
+  decode =
+    genDecode
 
-    query :: TmuxQuery
-    default query :: TmuxDataQuery (Rep a) => TmuxQuery
-    query = TmuxQuery $ Text.unwords $ query' @(Rep a)
+  query :: TmuxQuery
+  default query :: TmuxDataQuery (Rep a) => TmuxQuery
+  query =
+    TmuxQuery (Text.unwords (dataQuery @(Rep a)))
 
 instance TmuxCodec SessionId
 instance TmuxCodec WindowId
 instance TmuxCodec PaneId
+
+multi ::
+  TmuxCodec a =>
+  [Text] ->
+  Either DecodeError [a]
+multi =
+  traverse decode
+
+single ::
+  TmuxCodec a =>
+  [Text] ->
+  Either DecodeError a
+single = \case
+  [out] -> decode out
+  [] -> Left (DecodeError [] DecodeFailure.TargetMissing)
+  out -> Left (DecodeError out (DecodeFailure.TooManyRecords out))
diff --git a/lib/Chiasma/Codec/Data/Client.hs b/lib/Chiasma/Codec/Data/Client.hs
new file mode 100644
--- /dev/null
+++ b/lib/Chiasma/Codec/Data/Client.hs
@@ -0,0 +1,13 @@
+module Chiasma.Codec.Data.Client where
+
+import Chiasma.Codec (TmuxCodec)
+import Chiasma.Data.TmuxId (ClientId, SessionId)
+
+data Client =
+  Client {
+    clientName :: ClientId,
+    clientControlMode :: Bool,
+    sessionId :: SessionId
+  }
+  deriving stock (Eq, Show, Generic)
+  deriving anyclass (TmuxCodec)
diff --git a/lib/Chiasma/Codec/Data/Pane.hs b/lib/Chiasma/Codec/Data/Pane.hs
--- a/lib/Chiasma/Codec/Data/Pane.hs
+++ b/lib/Chiasma/Codec/Data/Pane.hs
@@ -2,15 +2,18 @@
 
 import Chiasma.Codec (TmuxCodec)
 import Chiasma.Data.TmuxId (HasPaneId, PaneId)
-import qualified Chiasma.Data.TmuxId as HasPaneId (HasPaneId(..))
+import qualified Chiasma.Data.TmuxId as HasPaneId (HasPaneId (..))
 
 data Pane =
   Pane {
     paneId :: PaneId,
     paneWidth :: Int,
-    paneHeight :: Int
+    paneHeight :: Int,
+    paneTop :: Int,
+    paneLeft :: Int
   }
-  deriving (Eq, Show, Generic, TmuxCodec)
+  deriving stock (Eq, Show, Generic)
+  deriving anyclass (TmuxCodec)
 
 instance HasPaneId Pane where
   paneId = paneId
diff --git a/lib/Chiasma/Codec/Data/PaneCoords.hs b/lib/Chiasma/Codec/Data/PaneCoords.hs
--- a/lib/Chiasma/Codec/Data/PaneCoords.hs
+++ b/lib/Chiasma/Codec/Data/PaneCoords.hs
@@ -1,9 +1,8 @@
 module Chiasma.Codec.Data.PaneCoords where
 
-import Chiasma.Data.TmuxId (HasPaneId, PaneId, SessionId, WindowId)
-import qualified Chiasma.Data.TmuxId as HasPaneId (HasPaneId(..))
-
 import Chiasma.Codec (TmuxCodec)
+import Chiasma.Data.TmuxId (HasPaneId, PaneId, SessionId, WindowId)
+import qualified Chiasma.Data.TmuxId as HasPaneId (HasPaneId (..))
 
 data PaneCoords =
   PaneCoords {
@@ -11,7 +10,8 @@
     windowId :: WindowId,
     paneId :: PaneId
   }
-  deriving (Eq, Show, Generic, TmuxCodec)
+  deriving stock (Eq, Show, Generic)
+  deriving anyclass (TmuxCodec)
 
 instance HasPaneId PaneCoords where
   paneId = paneId
diff --git a/lib/Chiasma/Codec/Data/PaneDetail.hs b/lib/Chiasma/Codec/Data/PaneDetail.hs
deleted file mode 100644
--- a/lib/Chiasma/Codec/Data/PaneDetail.hs
+++ /dev/null
@@ -1,18 +0,0 @@
-module Chiasma.Codec.Data.PaneDetail where
-
-import Chiasma.Codec (TmuxCodec)
-import Chiasma.Data.TmuxId (HasPaneId, PaneId)
-import qualified Chiasma.Data.TmuxId as HasPaneId (HasPaneId(..))
-
-data PaneDetail =
-  PaneDetail {
-    paneId :: PaneId,
-    paneWidth :: Int,
-    paneHeight :: Int,
-    paneTop :: Int,
-    paneLeft :: Int
-  }
-  deriving (Eq, Show, Generic, TmuxCodec)
-
-instance HasPaneId PaneDetail where
-  paneId = paneId
diff --git a/lib/Chiasma/Codec/Data/PaneMode.hs b/lib/Chiasma/Codec/Data/PaneMode.hs
--- a/lib/Chiasma/Codec/Data/PaneMode.hs
+++ b/lib/Chiasma/Codec/Data/PaneMode.hs
@@ -2,14 +2,15 @@
 
 import Chiasma.Codec (TmuxCodec)
 import Chiasma.Data.TmuxId (HasPaneId, PaneId)
-import qualified Chiasma.Data.TmuxId as HasPaneId (HasPaneId(paneId))
+import qualified Chiasma.Data.TmuxId as HasPaneId (HasPaneId (paneId))
 
 data PaneMode =
   PaneMode {
     paneId :: PaneId,
     paneMode :: Text
   }
-  deriving (Eq, Show, Generic, TmuxCodec)
+  deriving stock (Eq, Show, Generic)
+  deriving anyclass (TmuxCodec)
 
 instance HasPaneId PaneMode where
   paneId = paneId
diff --git a/lib/Chiasma/Codec/Data/PanePid.hs b/lib/Chiasma/Codec/Data/PanePid.hs
--- a/lib/Chiasma/Codec/Data/PanePid.hs
+++ b/lib/Chiasma/Codec/Data/PanePid.hs
@@ -2,14 +2,15 @@
 
 import Chiasma.Codec (TmuxCodec)
 import Chiasma.Data.TmuxId (HasPaneId, PaneId)
-import qualified Chiasma.Data.TmuxId as HasPaneId (HasPaneId(paneId))
+import qualified Chiasma.Data.TmuxId as HasPaneId (HasPaneId (paneId))
 
 data PanePid =
   PanePid {
     paneId :: PaneId,
     panePid :: Int
   }
-  deriving (Eq, Show, Generic, TmuxCodec)
+  deriving stock (Eq, Show, Generic)
+  deriving anyclass (TmuxCodec)
 
 instance HasPaneId PanePid where
   paneId = paneId
diff --git a/lib/Chiasma/Codec/Data/Session.hs b/lib/Chiasma/Codec/Data/Session.hs
--- a/lib/Chiasma/Codec/Data/Session.hs
+++ b/lib/Chiasma/Codec/Data/Session.hs
@@ -6,6 +6,8 @@
 -- must be data for now because of how derivation of `TmuxCodec` is implemented
 data Session =
   Session {
-    sessionId :: SessionId
+    sessionId :: SessionId,
+    sessionName :: Text
   }
-  deriving (Eq, Show, Generic, TmuxCodec)
+  deriving stock (Eq, Show, Generic)
+  deriving anyclass (TmuxCodec)
diff --git a/lib/Chiasma/Codec/Data/Window.hs b/lib/Chiasma/Codec/Data/Window.hs
--- a/lib/Chiasma/Codec/Data/Window.hs
+++ b/lib/Chiasma/Codec/Data/Window.hs
@@ -9,4 +9,5 @@
     windowWidth :: Int,
     windowHeight :: Int
   }
-  deriving (Eq, Show, Generic, TmuxCodec)
+  deriving stock (Eq, Show, Generic)
+  deriving anyclass (TmuxCodec)
diff --git a/lib/Chiasma/Codec/Decode.hs b/lib/Chiasma/Codec/Decode.hs
--- a/lib/Chiasma/Codec/Decode.hs
+++ b/lib/Chiasma/Codec/Decode.hs
@@ -1,58 +1,48 @@
-{-# LANGUAGE TypeOperators #-}
-
 module Chiasma.Codec.Decode where
 
 import qualified Data.Text as Text (null, unpack)
 import Data.Text.Read (decimal)
-import GHC.Generics (K1(..), M1(..), (:*:)(..))
+import GHC.Generics (K1 (..), M1 (..), (:*:) (..))
 import Prelude hiding (many)
-import Text.Parsec.Char (char, digit)
-import Text.ParserCombinators.Parsec (
-  GenParser,
-  ParseError,
-  many,
-  parse,
-  )
-
-import Chiasma.Data.TmuxId (PaneId(..), SessionId(..), WindowId(..), panePrefix, sessionPrefix, windowPrefix)
+import Text.Parsec.Char (digit, string)
+import Text.ParserCombinators.Parsec (GenParser, many, parse)
 
-data TmuxDecodeError =
-  ParseFailure Text ParseError
-  |
-  IntParsingFailure Text
-  |
-  BoolParsingFailure Text
-  |
-  TooFewFields
-  |
-  TooManyFields [Text]
-  deriving (Eq, Show)
+import Chiasma.Data.DecodeError (DecodeFailure (BoolParsingFailure, IntParsingFailure, ParseFailure, TooFewFields))
+import Chiasma.Data.TmuxId (
+  ClientId (ClientId),
+  PaneId (..),
+  SessionId (..),
+  WindowId (..),
+  panePrefix,
+  sessionPrefix,
+  windowPrefix,
+  )
 
 class TmuxPrimDecode a where
-  primDecode :: Text -> Either TmuxDecodeError a
+  primDecode :: Text -> Either DecodeFailure a
 
 class TmuxDataDecode f where
-  decode' :: [Text] -> Either TmuxDecodeError ([Text], f a)
+  dataDecode :: [Text] -> Either DecodeFailure ([Text], f a)
 
 instance (TmuxDataDecode f, TmuxDataDecode g) => TmuxDataDecode (f :*: g) where
-  decode' fields = do
-    (rest, left) <- decode' fields
-    (rest1, right) <- decode' rest
-    return (rest1, left :*: right)
+  dataDecode fields = do
+    (rest, left) <- dataDecode fields
+    (rest1, right) <- dataDecode rest
+    pure (rest1, left :*: right)
 
 instance TmuxDataDecode f => (TmuxDataDecode (M1 i c f)) where
-  decode' fields =
-    second M1 <$> decode' fields
+  dataDecode fields =
+    second M1 <$> dataDecode fields
 
 instance TmuxPrimDecode a => (TmuxDataDecode (K1 c a)) where
-  decode' (a:as) = do
+  dataDecode (a:as') = do
     prim <- primDecode a
-    return (as, K1 prim)
-  decode' [] = Left TooFewFields
+    pure (as', K1 prim)
+  dataDecode [] = Left TooFewFields
 
-readInt :: Text -> Text -> Either TmuxDecodeError Int
-readInt input num =
-  first (const $ IntParsingFailure input) parsed
+readInt :: Text -> Text -> Either DecodeFailure Int
+readInt text num =
+  first (const $ IntParsingFailure text) parsed
   where
     parsed = do
       (num', rest) <- decimal num
@@ -72,15 +62,18 @@
       convert _ =
         Left (BoolParsingFailure $ "got non-bool `" <> show field <> "`")
 
-idParser :: Char -> GenParser Char st Text
+idParser :: Text -> GenParser Char st Text
 idParser sym =
-  char sym *> (toText <$> many digit)
+  string (toString sym) *> (toText <$> many digit)
 
-parseId :: (Int -> a) -> Char -> Text -> Either TmuxDecodeError a
-parseId cons sym input = do
-  num <- first (ParseFailure "id") $ parse (idParser sym) "none" (Text.unpack input)
-  i <- readInt input num
-  return $ cons i
+parseId :: (Int -> a) -> Text -> Text -> Either DecodeFailure a
+parseId cons sym text = do
+  num <- first (ParseFailure "id") $ parse (idParser sym) "none" (Text.unpack text)
+  i <- readInt text num
+  pure (cons i)
+
+instance TmuxPrimDecode ClientId where
+  primDecode = pure . ClientId
 
 instance TmuxPrimDecode SessionId where
   primDecode = parseId SessionId sessionPrefix
diff --git a/lib/Chiasma/Codec/Query.hs b/lib/Chiasma/Codec/Query.hs
--- a/lib/Chiasma/Codec/Query.hs
+++ b/lib/Chiasma/Codec/Query.hs
@@ -1,6 +1,3 @@
-{-# LANGUAGE AllowAmbiguousTypes #-}
-{-# LANGUAGE TypeOperators #-}
-
 module Chiasma.Codec.Query where
 
 import Data.Char (isUpper, toLower)
@@ -8,16 +5,16 @@
 import GHC.Generics (C1, D1, S1, Selector, selName, (:*:))
 
 class TmuxDataQuery f where
-  query' :: [Text]
+  dataQuery :: [Text]
 
 instance TmuxDataQuery f => (TmuxDataQuery (D1 c f)) where
-  query' = query' @f
+  dataQuery = dataQuery @f
 
 instance TmuxDataQuery f => (TmuxDataQuery (C1 c f)) where
-  query' = query' @f
+  dataQuery = dataQuery @f
 
 instance (TmuxDataQuery f, TmuxDataQuery g) => TmuxDataQuery (f :*: g) where
-  query' = query' @f <> query' @g
+  dataQuery = dataQuery @f <> dataQuery @g
 
 trans :: Char -> Text
 trans a | isUpper a = toText @String ['_', toLower a]
@@ -31,7 +28,7 @@
 formatQuery q = "#{" <> snakeCase q <> "}"
 
 instance Selector s => (TmuxDataQuery (S1 s f)) where
-  query' =
+  dataQuery =
     [formatQuery (toText query)]
     where
       query = selName (undefined :: t s f p)
diff --git a/lib/Chiasma/Command/Pane.hs b/lib/Chiasma/Command/Pane.hs
--- a/lib/Chiasma/Command/Pane.hs
+++ b/lib/Chiasma/Command/Pane.hs
@@ -1,173 +1,160 @@
 module Chiasma.Command.Pane where
 
-import Data.List (dropWhileEnd)
-import qualified Data.Text as Text (intercalate, singleton, splitOn)
-
-import Chiasma.Codec (TmuxCodec)
-import qualified Chiasma.Codec.Data as Codec (Pane)
-import qualified Chiasma.Codec.Data.PaneCoords as Codec (PaneCoords)
-import qualified Chiasma.Codec.Data.PaneMode as Codec (PaneMode(PaneMode))
-import qualified Chiasma.Codec.Data.PanePid as Codec (PanePid)
-import Chiasma.Data.TmuxId (HasPaneId, PaneId, WindowId, formatId)
-import qualified Chiasma.Data.TmuxId as HasPaneId (paneId)
-import Chiasma.Data.TmuxThunk (TmuxThunk)
-import Chiasma.Data.View (View(View))
-import qualified Chiasma.Monad.Tmux as Tmux (read, readRaw, unsafeReadFirst, write)
-import Control.Monad.Free.Class (MonadFree)
-
-paneTarget :: PaneId -> [Text]
-paneTarget paneId =
-  ["-t", formatId paneId]
-
-sameId :: HasPaneId a => PaneId -> a -> Bool
-sameId target candidate = target == HasPaneId.paneId candidate
+import Prelude hiding (output)
 
-panesAs :: (MonadFree TmuxThunk m, TmuxCodec a) => m [a]
-panesAs =
-  Tmux.read "list-panes" ["-a"]
+import qualified Chiasma.Codec.Data.Pane as Codec (Pane)
+import qualified Chiasma.Codec.Data.PaneMode as Codec (PaneMode (PaneMode))
+import Chiasma.Data.Axis (Axis)
+import Chiasma.Data.CapturePaneParams (CaptureOutput (Stdout), escapeSequences, output, target)
+import Chiasma.Data.CopyModeParams (target)
+import Chiasma.Data.KillPaneParams (target)
+import qualified Chiasma.Data.PaneSelection as PaneSelection
+import qualified Chiasma.Data.Panes as Panes
+import Chiasma.Data.Panes (TmuxPanes)
+import Chiasma.Data.PipePaneParams (command, target)
+import Chiasma.Data.ResizePaneParams (ResizeAbsolute (ResizeAbsolute), absolute, target)
+import Chiasma.Data.SelectParams (target)
+import qualified Chiasma.Data.SendKeysParams as SendKeysParams
+import Chiasma.Data.SendKeysParams (Key, keys, target)
+import Chiasma.Data.SplitParams (JoinPaneParams (axis, detach, source, target))
+import qualified Chiasma.Data.Target as Target
+import Chiasma.Data.TmuxCommand (
+  TmuxCommand (CapturePane, CopyMode, KillPane, MovePane, PipePane, ResizePane, SelectPane, SendKeys),
+  )
+import Chiasma.Data.TmuxId (PaneId, WindowId)
+import Chiasma.Data.View (View (View))
+import qualified Chiasma.Effect.TmuxApi as Tmux
+import Chiasma.Effect.TmuxApi (Tmux)
 
-panes :: MonadFree TmuxThunk m => m [Codec.Pane]
+panes ::
+  Member (TmuxPanes a) r =>
+  Sem r [a]
 panes =
-  panesAs
+  Tmux.send (Panes.List PaneSelection.All)
 
-pane :: (MonadFree TmuxThunk m, TmuxCodec a, HasPaneId a) => PaneId -> m (Maybe a)
+pane ::
+  Member (TmuxPanes a) r =>
+  PaneId ->
+  Sem r (Maybe a)
 pane paneId =
-  find (sameId paneId) <$> Tmux.read "list-panes" ["-t", formatId paneId]
-
-windowPanesAs :: (MonadFree TmuxThunk m, TmuxCodec a) => WindowId -> m [a]
-windowPanesAs windowId =
-  Tmux.read "list-panes" ["-t", formatId windowId]
+  Tmux.send (Panes.Find paneId)
 
-windowPanes :: MonadFree TmuxThunk m => WindowId -> m [Codec.Pane]
-windowPanes =
-  windowPanesAs
+windowPanes ::
+  Member (TmuxPanes a) r =>
+  WindowId ->
+  Sem r [a]
+windowPanes windowId =
+  Tmux.send (Panes.List (PaneSelection.InWindow (Target.Window windowId)))
 
-firstWindowPane :: MonadFree TmuxThunk m => WindowId -> m Codec.Pane
+firstWindowPane ::
+  Member (TmuxPanes a) r =>
+  WindowId ->
+  Sem r a
 firstWindowPane windowId =
-  Tmux.unsafeReadFirst "list-panes" ["-t", formatId windowId]
+  Tmux.send (Panes.First (PaneSelection.InWindow (Target.Window windowId)))
 
-closePane :: MonadFree TmuxThunk m => PaneId -> m ()
+closePane ::
+  Member Tmux r =>
+  PaneId ->
+  Sem r ()
 closePane paneId =
-  Tmux.write "kill-pane" (paneTarget paneId)
+  Tmux.send (KillPane def { target = Target.Pane paneId })
 
 isPaneIdOpen ::
-  MonadFree TmuxThunk m =>
+  Member (TmuxPanes Codec.Pane) r =>
   PaneId ->
-  m Bool
+  Sem r Bool
 isPaneIdOpen paneId =
-  any (sameId paneId) <$> panes
+  isJust <$> pane paneId
 
 isPaneOpen ::
-  MonadFree TmuxThunk m =>
+  Member (TmuxPanes Codec.Pane) r =>
   View PaneId ->
-  m Bool
+  Sem r Bool
 isPaneOpen (View _ (Just paneId)) =
   isPaneIdOpen paneId
-isPaneOpen _ = return False
+isPaneOpen _ =
+  pure False
 
 movePane ::
-  MonadFree TmuxThunk m =>
+  Member Tmux r =>
   PaneId ->
   PaneId ->
-  Bool ->
-  m ()
-movePane paneId refId vertical =
-  Tmux.write "move-pane" ["-d", "-s", formatId paneId, "-t", formatId refId, direction]
-  where
-    direction = if vertical then "-v" else "-h"
+  Axis ->
+  Sem r ()
+movePane paneId refId axis =
+  Tmux.send (MovePane def {
+    detach = True,
+    source = Just (Target.Pane paneId),
+    target = Target.Pane refId,
+    axis = Just axis
+  })
 
 resizePane ::
-  MonadFree TmuxThunk m =>
+  Member Tmux r =>
   PaneId ->
-  Bool ->
+  Axis ->
   Int ->
-  m ()
-resizePane paneId vertical size =
-  Tmux.write "resize-pane" ["-t", formatId paneId, direction, show size]
-  where
-    direction = if vertical then "-y" else "-x"
-
-formatLine :: Text -> [Text]
-formatLine line =
-  ["\"" <> replace "\"" "\\\"" line <> Text.singleton '"', "enter"]
-  where
-    replace from to =
-      Text.intercalate to . Text.splitOn from
+  Sem r ()
+resizePane paneId axis size =
+  Tmux.send (ResizePane def {
+    target = Target.Pane paneId,
+    absolute = Just (ResizeAbsolute axis size)
+  })
 
 sendKeys ::
-  MonadFree TmuxThunk m =>
+  Member Tmux r =>
   PaneId ->
-  [Text] ->
-  m ()
+  [Key] ->
+  Sem r ()
 sendKeys paneId lines' =
-  traverse_ send formatted
-  where
-    formatted = lines' >>= formatLine
-    send line = Tmux.write "send-keys" (paneTarget paneId <> [line])
+  for_ lines' \ line ->
+    Tmux.send (SendKeys def { target = Target.Pane paneId, keys = [line] })
 
 pipePane ::
-  MonadFree TmuxThunk m =>
+  Member Tmux r =>
   PaneId ->
   Text ->
-  m ()
+  Sem r ()
 pipePane paneId cmd =
-  Tmux.write "pipe-pane" (paneTarget paneId <> [cmd])
+  Tmux.schedule (PipePane def { target = Target.Pane paneId, command = Just cmd })
 
 capturePane ::
-  MonadFree TmuxThunk m =>
-  PaneId ->
-  m [Text]
-capturePane paneId = do
-  lines' <- Tmux.readRaw "capture-pane" (paneTarget paneId <> ["-p", "-e"])
-  return $ dropWhileEnd ("" ==) lines'
-
-panePid ::
-  MonadFree TmuxThunk m =>
-  PaneId ->
-  m (Maybe Codec.PanePid)
-panePid =
-  pane
-
-panePids ::
-  MonadFree TmuxThunk m =>
-  m [Codec.PanePid]
-panePids =
-  panesAs
-
-paneCoords ::
-  MonadFree TmuxThunk m =>
+  Member Tmux r =>
   PaneId ->
-  m (Maybe Codec.PaneCoords)
-paneCoords =
-  pane
+  Sem r [Text]
+capturePane paneId =
+  Tmux.send (CapturePane def { target = Target.Pane paneId, escapeSequences = True, output = Just Stdout})
 
 selectPane ::
-  MonadFree TmuxThunk m =>
+  Member Tmux r =>
   PaneId ->
-  m ()
+  Sem r ()
 selectPane paneId =
-  Tmux.write "select-pane" (paneTarget paneId)
+  Tmux.send (SelectPane def { target = Target.Pane paneId })
 
 copyMode ::
-  MonadFree TmuxThunk m =>
+  Member Tmux r =>
   PaneId ->
-  m ()
-copyMode =
-  Tmux.write "copy-mode" . paneTarget
+  Sem r ()
+copyMode paneId =
+  Tmux.send (CopyMode def { target = Target.Pane paneId })
 
 paneMode ::
-  MonadFree TmuxThunk m =>
+  Member (TmuxPanes Codec.PaneMode) r =>
   PaneId ->
-  m (Maybe Codec.PaneMode)
+  Sem r (Maybe Codec.PaneMode)
 paneMode =
   pane
 
 quitCopyMode ::
-  MonadFree TmuxThunk m =>
+  Member Tmux r =>
+  Member (TmuxPanes Codec.PaneMode) r =>
   PaneId ->
-  m ()
+  Sem r ()
 quitCopyMode paneId =
   traverse_ check =<< pane paneId
   where
     check (Codec.PaneMode _ mode) =
-      when (mode == "copy-mode") (sendKeys paneId ["C-c"])
+      when (mode == "copy-mode") do
+        Tmux.send (SendKeys def { target = Target.Pane paneId, SendKeysParams.copyMode = True, keys = ["cancel"] })
diff --git a/lib/Chiasma/Command/Session.hs b/lib/Chiasma/Command/Session.hs
--- a/lib/Chiasma/Command/Session.hs
+++ b/lib/Chiasma/Command/Session.hs
@@ -1,33 +1,61 @@
 module Chiasma.Command.Session where
 
-import Control.Monad.Free.Class (MonadFree)
-
-import Chiasma.Codec.Data (Session(Session))
-import Chiasma.Data.Ident (Ident, identText)
+import Chiasma.Codec.Data.Client (Client (Client))
+import Chiasma.Codec.Data.Session (Session (Session))
+import Chiasma.Data.Ident (Ident)
+import Chiasma.Data.SessionParams (name)
+import qualified Chiasma.Data.Target as Target
+import Chiasma.Data.TmuxCommand (TmuxCommand (ListClients, ListSessions, NewSession, SwitchClient))
+import Chiasma.Data.TmuxError (TmuxError (NoClients))
 import Chiasma.Data.TmuxId (SessionId)
-import Chiasma.Data.TmuxThunk (TmuxThunk)
-import qualified Chiasma.Monad.Tmux as Tmux (read, unsafeReadOne, write)
+import qualified Chiasma.Effect.TmuxApi as Tmux
+import Chiasma.Effect.TmuxApi (Tmux)
 
 sameId :: SessionId -> Session -> Bool
-sameId target (Session i) = target == i
+sameId target (Session i _) = target == i
 
-sessions :: MonadFree TmuxThunk m => m [Session]
+sessions ::
+  Member Tmux r =>
+  Sem r [Session]
 sessions =
-  Tmux.read "list-sessions" []
+  Tmux.send ListSessions
 
-doesSessionExist :: MonadFree TmuxThunk m => SessionId -> m Bool
+doesSessionExist ::
+  Member Tmux r =>
+  SessionId ->
+  Sem r Bool
 doesSessionExist sessionId =
   any (sameId sessionId) <$> sessions
 
-existingSessionId :: MonadFree TmuxThunk m => SessionId -> m (Maybe SessionId)
+existingSessionId ::
+  Member Tmux r =>
+  SessionId ->
+  Sem r (Maybe SessionId)
 existingSessionId sessionId = do
   exists <- doesSessionExist sessionId
-  return $ if exists then Just sessionId else Nothing
+  pure $ if exists then Just sessionId else Nothing
 
-newSession :: MonadFree TmuxThunk m => Ident -> m Session
+newSession ::
+  Member Tmux r =>
+  Ident ->
+  Sem r Session
 newSession name =
-  Tmux.unsafeReadOne "new-session" ["-s", identText name, "-P"]
+  Tmux.send (NewSession def { name = Just name })
 
-activateSession :: MonadFree TmuxThunk m => Int -> m ()
-activateSession sessionId =
-  Tmux.write "send-keys" ["-t", "%1", "'tmux switch-client -t \\$" <> show sessionId <> "'", "enter"]
+clientForSession :: SessionId -> [Client] -> Maybe Client
+clientForSession session =
+  find \case
+    Client _ False sid ->
+      sid == session
+    _ ->
+      False
+
+switchClient ::
+  Members [Tmux, Stop TmuxError] r =>
+  SessionId ->
+  SessionId ->
+  Sem r ()
+switchClient clientSession session = do
+  clients <- Tmux.send ListClients
+  Client client _ _ <- stopNote NoClients (clientForSession clientSession clients)
+  Tmux.send (SwitchClient client (Target.Session session))
diff --git a/lib/Chiasma/Command/Window.hs b/lib/Chiasma/Command/Window.hs
--- a/lib/Chiasma/Command/Window.hs
+++ b/lib/Chiasma/Command/Window.hs
@@ -1,53 +1,79 @@
 module Chiasma.Command.Window where
 
-import Control.Monad.Free.Class (MonadFree)
+import Exon (exon)
+import Path (Abs, Dir, Path)
 
-import Chiasma.Codec (TmuxCodec)
-import Chiasma.Codec.Data (Pane, Window(Window))
-import Chiasma.Data.Ident (Ident, identText)
-import Chiasma.Data.TmuxId (SessionId, TmuxId(formatId), WindowId)
-import Chiasma.Data.TmuxThunk (TmuxThunk)
-import qualified Chiasma.Monad.Tmux as Tmux (read, unsafeReadFirst, unsafeReadOne)
+import Chiasma.Codec.Data.Pane (Pane)
+import Chiasma.Codec.Data.Window (Window (Window))
+import Chiasma.Data.Ident (Ident)
+import Chiasma.Data.LayoutError (LayoutError (LayoutError))
+import qualified Chiasma.Data.Target as Target
+import Chiasma.Data.TmuxCommand (TmuxCommand (ListWindows, NewWindow, SplitWindow))
+import Chiasma.Data.TmuxId (SessionId, WindowId)
+import Chiasma.Data.WindowParams (cwd, detach, name, target)
+import qualified Chiasma.Data.WindowSelection as WindowSelection
+import qualified Chiasma.Effect.TmuxApi as Tmux
+import Chiasma.Effect.TmuxApi (Tmux)
 
 sameId :: WindowId -> Window -> Bool
 sameId target (Window i _ _) = target == i
 
-windows :: MonadFree TmuxThunk m => m [Window]
+windows ::
+  Member Tmux r =>
+  Sem r [Window]
 windows =
-  Tmux.read "list-windows" ["-a"]
+  Tmux.send (ListWindows WindowSelection.All)
 
-window :: MonadFree TmuxThunk m => WindowId -> m (Maybe Window)
+window ::
+  Member Tmux r =>
+  WindowId ->
+  Sem r (Maybe Window)
 window windowId =
   find (sameId windowId) <$> windows
 
-sessionWindows :: MonadFree TmuxThunk m => SessionId -> m [Window]
+sessionWindows ::
+  Member Tmux r =>
+  SessionId ->
+  Sem r [Window]
 sessionWindows sid =
-  Tmux.read "list-windows" ["-t", formatId sid]
+  Tmux.send (ListWindows (WindowSelection.InSession (Target.Session sid)))
 
-newSessionWindow :: MonadFree TmuxThunk m => SessionId -> m Window
+-- TODO this could be ListWindow
+newSessionWindow ::
+  Members [Tmux, Stop LayoutError] r =>
+  SessionId ->
+  Sem r Window
 newSessionWindow sid =
-  Tmux.unsafeReadOne "list-windows" ["-t", formatId sid]
+  sessionWindows sid >>= \case
+    [w] -> pure w
+    ws -> stop (LayoutError [exon|New session contains multiple windows: #{show ws}|])
 
-doesWindowExist :: MonadFree TmuxThunk m => WindowId -> m Bool
+doesWindowExist ::
+  Member Tmux r =>
+  WindowId ->
+  Sem r Bool
 doesWindowExist windowId =
   any (sameId windowId) <$> windows
 
-newWindow :: MonadFree TmuxThunk m => SessionId -> Ident -> m Window
+newWindow ::
+  Member Tmux r =>
+  SessionId ->
+  Ident ->
+  Sem r Window
 newWindow sid name =
-  Tmux.unsafeReadOne "new-window" ["-t", formatId sid, "-n", identText name, "-P"]
+  Tmux.send (NewWindow def { target = Target.Session sid, name = Just name })
 
-splitWindowAs ::
-  (MonadFree TmuxThunk m, TmuxCodec a) =>
-  FilePath ->
+splitWindowInDir ::
+  Member Tmux r =>
+  Path Abs Dir ->
   WindowId ->
-  m a
-splitWindowAs dir windowId =
-  Tmux.unsafeReadFirst "split-window" ["-t", formatId windowId, "-d", "-P", "-c", toText dir]
+  Sem r Pane
+splitWindowInDir dir windowId =
+  Tmux.send (SplitWindow def { target = Target.Window windowId, detach = True, cwd = Just dir } def)
 
 splitWindow ::
-  (MonadFree TmuxThunk m) =>
-  FilePath ->
+  Member Tmux r =>
   WindowId ->
-  m Pane
-splitWindow =
-  splitWindowAs
+  Sem r Pane
+splitWindow windowId =
+  Tmux.send (SplitWindow def { target = Target.Window windowId, detach = True } def)
diff --git a/lib/Chiasma/Control/IO/Unsafe.hs b/lib/Chiasma/Control/IO/Unsafe.hs
--- a/lib/Chiasma/Control/IO/Unsafe.hs
+++ b/lib/Chiasma/Control/IO/Unsafe.hs
@@ -3,4 +3,4 @@
 import GHC.IO.Unsafe (unsafePerformIO)
 
 unsafeLog :: Show a => a -> b -> b
-unsafeLog a b = unsafePerformIO $ print a >> return b
+unsafeLog a b = unsafePerformIO $ print a >> pure b
diff --git a/lib/Chiasma/Data/Axis.hs b/lib/Chiasma/Data/Axis.hs
new file mode 100644
--- /dev/null
+++ b/lib/Chiasma/Data/Axis.hs
@@ -0,0 +1,31 @@
+module Chiasma.Data.Axis where
+
+import Data.Data (Data)
+import Prettyprinter (Pretty (pretty))
+
+import Chiasma.Class.CmdArgs (CmdArgs (cmdArgs))
+
+data Axis =
+  Horizontal
+  |
+  Vertical
+  deriving stock (Eq, Show, Data)
+
+instance Pretty Axis where
+  pretty = \case
+    Horizontal -> "h"
+    Vertical -> "v"
+
+instance CmdArgs Axis where
+  cmdArgs = \case
+    Horizontal -> ["-h"]
+    Vertical -> ["-v"]
+
+instance Default Axis where
+  def =
+    Vertical
+
+vertical :: Axis -> Bool
+vertical = \case
+  Horizontal -> False
+  Vertical -> True
diff --git a/lib/Chiasma/Data/CapturePaneParams.hs b/lib/Chiasma/Data/CapturePaneParams.hs
new file mode 100644
--- /dev/null
+++ b/lib/Chiasma/Data/CapturePaneParams.hs
@@ -0,0 +1,84 @@
+module Chiasma.Data.CapturePaneParams where
+
+import Chiasma.Class.CmdArgs (CmdArgs (cmdArgs), flag1, optionArgs, optionArgsWith)
+import qualified Chiasma.Data.Target as Target
+import Chiasma.Data.Target (Target)
+import Prelude hiding (output)
+
+data CaptureOutput =
+  Stdout
+  |
+  Buffer Text
+  deriving stock (Eq, Show)
+
+instance CmdArgs CaptureOutput where
+  cmdArgs = \case
+    Stdout -> ["-p"]
+    Buffer name -> ["-b", name]
+
+data CaptureLine =
+  Edge
+  |
+  CaptureLine Int
+  deriving stock (Eq, Show)
+
+instance CmdArgs CaptureLine where
+  cmdArgs = \case
+    Edge -> ["-"]
+    CaptureLine n -> [show n]
+
+data CapturePaneParams =
+  CapturePaneParams {
+    output :: Maybe CaptureOutput,
+    alternate :: Bool,
+    quiet :: Bool,
+    escapeSequences :: Bool,
+    octal :: Bool,
+    joinWrapped :: Bool,
+    incomplete :: Bool,
+    startLine :: Maybe CaptureLine,
+    endLine :: Maybe CaptureLine,
+    target :: Target,
+    stripBlank :: Bool,
+    stripTrailingWs :: Bool
+  }
+  deriving stock (Eq, Show)
+
+instance Default CapturePaneParams where
+  def =
+    CapturePaneParams {
+      output = Nothing,
+      alternate = False,
+      quiet = False,
+      escapeSequences = False,
+      octal = False,
+      joinWrapped = True,
+      incomplete = False,
+      startLine = Nothing,
+      endLine = Nothing,
+      target = Target.Current,
+      stripBlank = True,
+      stripTrailingWs = True
+    }
+
+instance CmdArgs CapturePaneParams where
+  cmdArgs CapturePaneParams {..} =
+    flag1 "-a" alternate
+    <>
+    flag1 "-q" quiet
+    <>
+    flag1 "-e" escapeSequences
+    <>
+    flag1 "-C" octal
+    <>
+    flag1 "-J" joinWrapped
+    <>
+    flag1 "-P" incomplete
+    <>
+    optionArgs output
+    <>
+    optionArgsWith "-S" startLine
+    <>
+    optionArgsWith "-E" endLine
+    <>
+    cmdArgs target
diff --git a/lib/Chiasma/Data/Cmd.hs b/lib/Chiasma/Data/Cmd.hs
--- a/lib/Chiasma/Data/Cmd.hs
+++ b/lib/Chiasma/Data/Cmd.hs
@@ -8,19 +8,19 @@
 
 newtype CmdName =
   CmdName Text
-  deriving (Eq, Show)
+  deriving stock (Eq, Show)
 
 newtype CmdArgs =
   CmdArgs [Text]
-  deriving (Eq, Show)
+  deriving stock (Eq, Show)
 
 data Cmd =
   Cmd CmdName CmdArgs
-  deriving (Eq, Show)
+  deriving stock (Eq, Show)
 
 newtype Cmds =
   Cmds [Cmd]
-  deriving (Eq, Show)
+  deriving stock (Eq, Show)
 
 cmd :: Text -> [Text] -> Cmd
 cmd name args = Cmd (CmdName name) (CmdArgs args)
diff --git a/lib/Chiasma/Data/CodecError.hs b/lib/Chiasma/Data/CodecError.hs
new file mode 100644
--- /dev/null
+++ b/lib/Chiasma/Data/CodecError.hs
@@ -0,0 +1,8 @@
+module Chiasma.Data.CodecError where
+
+import Chiasma.Data.DecodeError (DecodeError)
+import Chiasma.Data.TmuxRequest (TmuxRequest)
+
+data CodecError =
+  CodecError TmuxRequest DecodeError
+  deriving stock (Eq, Show)
diff --git a/lib/Chiasma/Data/Conduit.hs b/lib/Chiasma/Data/Conduit.hs
deleted file mode 100644
--- a/lib/Chiasma/Data/Conduit.hs
+++ /dev/null
@@ -1,10 +0,0 @@
-{-# LANGUAGE DataKinds #-}
-
-module Chiasma.Data.Conduit where
-
-import Conduit (ConduitT, Flush)
-import Data.Conduit.Binary (sinkHandleFlush)
-import Data.Conduit.Process.Typed (StreamSpec, StreamType(STInput), createPipe)
-
-createSinkFlush :: MonadIO m => StreamSpec 'STInput (ConduitT (Flush ByteString) o m ())
-createSinkFlush = sinkHandleFlush <$> createPipe
diff --git a/lib/Chiasma/Data/CopyModeParams.hs b/lib/Chiasma/Data/CopyModeParams.hs
new file mode 100644
--- /dev/null
+++ b/lib/Chiasma/Data/CopyModeParams.hs
@@ -0,0 +1,32 @@
+module Chiasma.Data.CopyModeParams where
+
+import Chiasma.Class.CmdArgs (CmdArgs (cmdArgs), flag1)
+import Chiasma.Data.Target (Target)
+
+data CopyModeParams =
+  CopyModeParams {
+    mouseDrag :: Bool,
+    scrollUp :: Bool,
+    exitBottom :: Bool,
+    target :: Target
+  }
+  deriving stock (Eq, Show)
+
+instance Default CopyModeParams where
+  def =
+    CopyModeParams {
+      mouseDrag = False,
+      scrollUp = False,
+      exitBottom = False,
+      target = def
+    }
+
+instance CmdArgs CopyModeParams where
+  cmdArgs CopyModeParams {..} =
+    flag1 "-M" mouseDrag
+    <>
+    flag1 "-u" scrollUp
+    <>
+    flag1 "-e" exitBottom
+    <>
+    cmdArgs target
diff --git a/lib/Chiasma/Data/DecodeError.hs b/lib/Chiasma/Data/DecodeError.hs
new file mode 100644
--- /dev/null
+++ b/lib/Chiasma/Data/DecodeError.hs
@@ -0,0 +1,26 @@
+module Chiasma.Data.DecodeError where
+
+import Text.ParserCombinators.Parsec (ParseError)
+
+data DecodeFailure =
+  ParseFailure Text ParseError
+  |
+  IntParsingFailure Text
+  |
+  BoolParsingFailure Text
+  |
+  TooFewFields
+  |
+  TooManyFields [Text]
+  |
+  TooManyRecords [Text]
+  |
+  TargetMissing
+  deriving stock (Eq, Show)
+
+data DecodeError =
+  DecodeError {
+    output :: [Text],
+    failure :: DecodeFailure
+  }
+  deriving stock (Eq, Show)
diff --git a/lib/Chiasma/Data/Direction.hs b/lib/Chiasma/Data/Direction.hs
new file mode 100644
--- /dev/null
+++ b/lib/Chiasma/Data/Direction.hs
@@ -0,0 +1,22 @@
+module Chiasma.Data.Direction where
+
+import Prelude hiding (Down, Left, Right)
+
+import Chiasma.Class.CmdArgs (CmdArgs (cmdArgs))
+
+data Direction =
+  Up
+  |
+  Down
+  |
+  Left
+  |
+  Right
+  deriving stock (Eq, Show)
+
+instance CmdArgs Direction where
+  cmdArgs = \case
+    Up -> ["-U"]
+    Down -> ["-D"]
+    Left -> ["-L"]
+    Right -> ["-R"]
diff --git a/lib/Chiasma/Data/Event.hs b/lib/Chiasma/Data/Event.hs
new file mode 100644
--- /dev/null
+++ b/lib/Chiasma/Data/Event.hs
@@ -0,0 +1,7 @@
+module Chiasma.Data.Event where
+
+import Chiasma.Data.TmuxId (WindowId)
+
+data Event =
+  WindowClosed WindowId
+  deriving stock (Eq, Show)
diff --git a/lib/Chiasma/Data/Ident.hs b/lib/Chiasma/Data/Ident.hs
--- a/lib/Chiasma/Data/Ident.hs
+++ b/lib/Chiasma/Data/Ident.hs
@@ -1,21 +1,18 @@
 module Chiasma.Data.Ident where
 
-import Data.Aeson (FromJSON, ToJSON(toEncoding), defaultOptions, genericToEncoding)
 import Data.Data (Data)
-import Data.Text.Prettyprint.Doc (Pretty(..))
 import Data.UUID (UUID)
 import qualified Data.UUID as UUID (fromText, toText)
+import Prettyprinter (Pretty (..))
 import System.Random (randomIO)
-import qualified Text.Show as Show
 
 data Ident =
   Str Text
   |
   Uuid UUID
-  deriving (Eq, Generic, Data, NFData, Ord)
+  deriving stock (Eq, Show, Ord, Generic, Data)
 
-instance Show Ident where
-  show = toString . identText
+json ''Ident
 
 class Identifiable a where
   identify :: a -> Ident
@@ -24,19 +21,18 @@
   identify = id
 
 instance Pretty Ident where
-  pretty (Str s) = pretty s
-  pretty (Uuid u) = pretty . UUID.toText $ u
+  pretty =
+    pretty . \case
+      Str s -> s
+      Uuid u -> UUID.toText u
 
 instance Default Ident where
-  def = Str ""
+  def =
+    Str ""
 
 instance IsString Ident where
-  fromString = Str . toText
-
-instance FromJSON Ident where
-
-instance ToJSON Ident where
-  toEncoding = genericToEncoding defaultOptions
+  fromString =
+    Str . toText
 
 sameIdent ::
   Identifiable a =>
diff --git a/lib/Chiasma/Data/KillPaneParams.hs b/lib/Chiasma/Data/KillPaneParams.hs
new file mode 100644
--- /dev/null
+++ b/lib/Chiasma/Data/KillPaneParams.hs
@@ -0,0 +1,24 @@
+module Chiasma.Data.KillPaneParams where
+
+import Chiasma.Class.CmdArgs (CmdArgs (cmdArgs), flag1)
+import Chiasma.Data.Target (Target)
+
+data KillPaneParams =
+  KillPaneParams {
+    allbut :: Bool,
+    target :: Target
+  }
+  deriving stock (Eq, Show)
+
+instance Default KillPaneParams where
+  def =
+    KillPaneParams {
+      allbut = False,
+      target = def
+    }
+
+instance CmdArgs KillPaneParams where
+  cmdArgs KillPaneParams {..} =
+    flag1 "-a" allbut
+    <>
+    cmdArgs target
diff --git a/lib/Chiasma/Data/LayoutError.hs b/lib/Chiasma/Data/LayoutError.hs
new file mode 100644
--- /dev/null
+++ b/lib/Chiasma/Data/LayoutError.hs
@@ -0,0 +1,6 @@
+module Chiasma.Data.LayoutError where
+
+newtype LayoutError =
+  LayoutError { unLayoutError :: Text }
+  deriving stock (Eq, Show, Generic)
+  deriving newtype (IsString)
diff --git a/lib/Chiasma/Data/Maybe.hs b/lib/Chiasma/Data/Maybe.hs
deleted file mode 100644
--- a/lib/Chiasma/Data/Maybe.hs
+++ /dev/null
@@ -1,17 +0,0 @@
-module Chiasma.Data.Maybe where
-
-import Control.Monad.Error.Class (MonadError(throwError))
-
-maybeExcept :: MonadError e m => e -> Maybe a -> m a
-maybeExcept e Nothing = throwError e
-maybeExcept _ (Just a) = return a
-
-findMaybe :: (a -> Maybe b) -> [a] -> Maybe b
-findMaybe f as =
-  case mapMaybe f as of
-    (a : _) -> Just a
-    _ -> Nothing
-
-orElse :: Maybe a -> Maybe a -> Maybe a
-orElse _ (Just a) = Just a
-orElse fallback Nothing = fallback
diff --git a/lib/Chiasma/Data/PaneSelection.hs b/lib/Chiasma/Data/PaneSelection.hs
new file mode 100644
--- /dev/null
+++ b/lib/Chiasma/Data/PaneSelection.hs
@@ -0,0 +1,22 @@
+module Chiasma.Data.PaneSelection where
+
+import Chiasma.Class.CmdArgs (CmdArgs (cmdArgs))
+import Chiasma.Data.Target (Target (Current))
+
+data PaneSelection =
+  All
+  |
+  InSession Target
+  |
+  InWindow Target
+  deriving stock (Eq, Show)
+
+instance CmdArgs PaneSelection where
+  cmdArgs = \case
+    All -> ["-a"]
+    InSession target -> ["-s"] <> cmdArgs target
+    InWindow target -> cmdArgs target
+
+instance Default PaneSelection where
+  def =
+    InWindow Current
diff --git a/lib/Chiasma/Data/Panes.hs b/lib/Chiasma/Data/Panes.hs
new file mode 100644
--- /dev/null
+++ b/lib/Chiasma/Data/Panes.hs
@@ -0,0 +1,105 @@
+module Chiasma.Data.Panes where
+
+import Chiasma.Class.CmdArgs (cmdArgs)
+import qualified Chiasma.Codec as Codec
+import Chiasma.Codec (TmuxCodec, multi, single)
+import qualified Chiasma.Data.DecodeError as DecodeFailure
+import Chiasma.Data.DecodeError (DecodeError (DecodeError))
+import qualified Chiasma.Data.PaneSelection as PaneSelection
+import Chiasma.Data.PaneSelection (PaneSelection)
+import qualified Chiasma.Data.Target as Target
+import Chiasma.Data.TmuxId (PaneId)
+import Chiasma.Data.TmuxQuery (TmuxQuery)
+import Chiasma.Data.TmuxRequest (TmuxRequest (TmuxRequest))
+import Chiasma.Data.TmuxResponse (TmuxResponse (TmuxResponse))
+import Chiasma.Data.WithPaneId (WithPaneId (WithPaneId))
+import Chiasma.Effect.TmuxApi (TmuxApi)
+
+-- |A 'TmuxApi' command for listing panes, with different query criteria.
+-- The constructors taking a 'PaneSelection' list all panes that are present in the selected scope, but may constrain
+-- the return value.
+-- The constructors 'Get' and 'Find' return only the pane with the requested ID.
+data Panes (p :: Type) (a :: Type) :: Type where
+  -- |Return all panes covered by the selection.
+  List :: PaneSelection -> Panes p [p]
+  -- |Return one pane covered by the selection, fail if there is none.
+  First :: PaneSelection -> Panes p p
+  -- |Return one pane covered by the selection, fail if there is none or more than one.
+  One :: PaneSelection -> Panes p p
+  -- |Return the pane with the specified ID, fail if there is none.
+  Get :: PaneId -> Panes p p
+  -- |Return the pane with the specified ID if it exists.
+  Find :: PaneId -> Panes p (Maybe p)
+
+type TmuxPanes p =
+  TmuxApi (Panes p)
+
+query ::
+  ∀ p a .
+  TmuxCodec p =>
+  Panes p a ->
+  TmuxQuery
+query _ =
+  Codec.query @p
+
+selection :: Panes p a -> PaneSelection
+selection = \case
+  List ps -> ps
+  First ps -> ps
+  One ps -> ps
+  Get i -> PaneSelection.InWindow (Target.Pane i)
+  Find _ -> PaneSelection.All
+
+request ::
+  Panes p a ->
+  TmuxQuery ->
+  TmuxRequest
+request (selection -> s) =
+  TmuxRequest "list-panes" (cmdArgs s) . Just
+
+encode ::
+  ∀ p a .
+  TmuxCodec p =>
+  Panes p a ->
+  TmuxRequest
+encode (Get i) =
+  request (Get i) (Codec.query @(WithPaneId p))
+encode (Find i) =
+  request (Find i) (Codec.query @(WithPaneId p))
+encode cmd =
+  request cmd (query cmd)
+
+sameId :: PaneId -> WithPaneId a -> Maybe a
+sameId target (WithPaneId paneId p)
+  | target == paneId = Just p
+  | otherwise = Nothing
+
+paneById ::
+  TmuxCodec p =>
+  PaneId ->
+  [Text] ->
+  Either DecodeError (Maybe p)
+paneById paneId out = do
+  firstJust (sameId paneId) <$> multi out
+
+decode ::
+  ∀ p a .
+  TmuxCodec p =>
+  TmuxResponse ->
+  Panes p a ->
+  Either DecodeError a
+decode (TmuxResponse out) = \case
+  List _ ->
+    multi out
+  First _ ->
+    multi out >>= \case
+      [] -> Left (DecodeError out DecodeFailure.TooFewFields)
+      a : _ -> Right a
+  One _ ->
+    single out
+  Get paneId -> do
+    ps <- multi @(WithPaneId p) out
+    maybeToRight (DecodeError out DecodeFailure.TargetMissing) (firstJust (sameId paneId) ps)
+  Find paneId -> do
+    ps <- multi @(WithPaneId p) out
+    pure (firstJust (sameId paneId) ps)
diff --git a/lib/Chiasma/Data/PipePaneParams.hs b/lib/Chiasma/Data/PipePaneParams.hs
new file mode 100644
--- /dev/null
+++ b/lib/Chiasma/Data/PipePaneParams.hs
@@ -0,0 +1,38 @@
+module Chiasma.Data.PipePaneParams where
+
+import Prelude hiding (input, output)
+
+import Chiasma.Class.CmdArgs (CmdArgs (cmdArgs), arg, flag1)
+import Chiasma.Data.Target (Target (Current))
+
+data PipePaneParams =
+  PipePaneParams {
+    input :: Bool,
+    output :: Bool,
+    onlyNew :: Bool,
+    target :: Target,
+    command :: Maybe Text
+  }
+  deriving stock (Eq, Show)
+
+instance Default PipePaneParams where
+  def =
+    PipePaneParams {
+      input = False,
+      output = False,
+      onlyNew = False,
+      target = Current,
+      command = Nothing
+    }
+
+instance CmdArgs PipePaneParams where
+  cmdArgs PipePaneParams {..} =
+    flag1 "-I" input
+    <>
+    flag1 "-O" output
+    <>
+    flag1 "-o" onlyNew
+    <>
+    cmdArgs target
+    <>
+    arg command
diff --git a/lib/Chiasma/Data/ProcessError.hs b/lib/Chiasma/Data/ProcessError.hs
new file mode 100644
--- /dev/null
+++ b/lib/Chiasma/Data/ProcessError.hs
@@ -0,0 +1,5 @@
+module Chiasma.Data.ProcessError where
+
+data ProcessError =
+  Terminated Text
+  deriving stock (Eq, Show)
diff --git a/lib/Chiasma/Data/ReceiveEvent.hs b/lib/Chiasma/Data/ReceiveEvent.hs
new file mode 100644
--- /dev/null
+++ b/lib/Chiasma/Data/ReceiveEvent.hs
@@ -0,0 +1,6 @@
+module Chiasma.Data.ReceiveEvent where
+
+import Chiasma.Data.Event (Event)
+
+data ReceiveEvent :: Type -> Type where
+  ReceiveEvent :: ReceiveEvent Event
diff --git a/lib/Chiasma/Data/RenderError.hs b/lib/Chiasma/Data/RenderError.hs
--- a/lib/Chiasma/Data/RenderError.hs
+++ b/lib/Chiasma/Data/RenderError.hs
@@ -1,6 +1,8 @@
 module Chiasma.Data.RenderError where
 
+import Chiasma.Data.CodecError (CodecError)
 import Chiasma.Data.Ident (Ident)
+import Chiasma.Data.LayoutError (LayoutError)
 import Chiasma.Data.TmuxError (TmuxError)
 import Chiasma.Data.Views (ViewsError)
 
@@ -9,9 +11,11 @@
   |
   Views ViewsError
   |
+  Layout LayoutError
+  |
   Pack Text
   |
   Fatal TmuxError
-  deriving (Eq, Show)
-
-deepPrisms ''RenderError
+  |
+  Codec CodecError
+  deriving stock (Eq, Show)
diff --git a/lib/Chiasma/Data/ResizePaneParams.hs b/lib/Chiasma/Data/ResizePaneParams.hs
new file mode 100644
--- /dev/null
+++ b/lib/Chiasma/Data/ResizePaneParams.hs
@@ -0,0 +1,58 @@
+module Chiasma.Data.ResizePaneParams where
+
+import Chiasma.Class.CmdArgs (CmdArgs (cmdArgs), arg, flag1, optionArgs)
+import Chiasma.Data.Axis (Axis (Horizontal, Vertical))
+import Chiasma.Data.Direction (Direction)
+import Chiasma.Data.Target (Target (Current))
+
+data ResizeAbsolute =
+  ResizeAbsolute Axis Int
+  deriving stock (Eq, Show)
+
+instance CmdArgs ResizeAbsolute where
+  cmdArgs (ResizeAbsolute axis amount) =
+    [axisArg axis, show amount]
+    where
+      axisArg = \case
+        Horizontal -> "-x"
+        Vertical -> "-y"
+
+data Adjustment =
+  Adjustment (Maybe Direction) (Maybe Int)
+  deriving stock (Eq, Show)
+
+instance CmdArgs Adjustment where
+  cmdArgs (Adjustment dir amount) =
+    optionArgs dir <> arg (show <$> amount)
+
+data ResizePaneParams =
+  ResizePaneParams {
+    absolute :: Maybe ResizeAbsolute,
+    adjustment :: Maybe Adjustment,
+    zoom :: Bool,
+    mouse :: Bool,
+    target :: Target
+  }
+  deriving stock (Eq, Show)
+
+instance Default ResizePaneParams where
+  def =
+    ResizePaneParams {
+      absolute = Nothing,
+      adjustment = Nothing,
+      zoom = False,
+      mouse = False,
+      target = Current
+    }
+
+instance CmdArgs ResizePaneParams where
+  cmdArgs ResizePaneParams {..} =
+    optionArgs absolute
+    <>
+    flag1 "-Z" zoom
+    <>
+    flag1 "-M" mouse
+    <>
+    optionArgs adjustment
+    <>
+    cmdArgs target
diff --git a/lib/Chiasma/Data/SelectParams.hs b/lib/Chiasma/Data/SelectParams.hs
new file mode 100644
--- /dev/null
+++ b/lib/Chiasma/Data/SelectParams.hs
@@ -0,0 +1,65 @@
+module Chiasma.Data.SelectParams where
+
+import Prelude hiding (input, last)
+
+import Chiasma.Class.CmdArgs (CmdArgs (cmdArgs), flag1, option, optionArgs)
+import Chiasma.Data.Direction (Direction)
+import Chiasma.Data.Target (Target)
+
+data SelectInput =
+  EnableInput
+  |
+  DisableInput
+  deriving stock (Eq, Show)
+
+instance CmdArgs SelectInput where
+  cmdArgs = \case
+    EnableInput -> ["-e"]
+    DisableInput -> ["-d"]
+
+data SelectMark =
+  SelectMark
+  |
+  SelectUnmark
+  deriving stock (Eq, Show)
+
+instance CmdArgs SelectMark where
+  cmdArgs = \case
+    SelectMark -> ["-m"]
+    SelectUnmark -> ["-M"]
+
+data SelectParams =
+  SelectParams {
+    neighbor :: Maybe Direction,
+    last :: Bool,
+    input :: Maybe SelectInput,
+    mark :: Maybe SelectMark,
+    title :: Maybe Text,
+    target :: Target
+  }
+  deriving stock (Eq, Show)
+
+instance Default SelectParams where
+  def =
+    SelectParams {
+      neighbor = Nothing,
+      last = False,
+      input = Nothing,
+      mark = Nothing,
+      title = Nothing,
+      target = def
+    }
+
+instance CmdArgs SelectParams where
+  cmdArgs SelectParams {..} =
+    optionArgs neighbor
+    <>
+    flag1 "-l" last
+    <>
+    optionArgs input
+    <>
+    optionArgs mark
+    <>
+    option "-T" title
+    <>
+    cmdArgs target
diff --git a/lib/Chiasma/Data/SelectWindowParams.hs b/lib/Chiasma/Data/SelectWindowParams.hs
new file mode 100644
--- /dev/null
+++ b/lib/Chiasma/Data/SelectWindowParams.hs
@@ -0,0 +1,38 @@
+module Chiasma.Data.SelectWindowParams where
+
+import Prelude hiding (last)
+
+import Chiasma.Class.CmdArgs (CmdArgs (cmdArgs), flag1)
+import Chiasma.Data.Target (Target)
+
+data SelectWindowParams =
+  SelectWindowParams {
+    last :: Bool,
+    next :: Bool,
+    previous :: Bool,
+    toggle :: Bool,
+    target :: Target
+  }
+  deriving stock (Eq, Show)
+
+instance Default SelectWindowParams where
+  def =
+    SelectWindowParams {
+      last = False,
+      next = False,
+      previous = False,
+      toggle = False,
+      target = def
+    }
+
+instance CmdArgs SelectWindowParams where
+  cmdArgs SelectWindowParams {..} =
+    flag1 "-l" last
+    <>
+    flag1 "-n" next
+    <>
+    flag1 "-p" previous
+    <>
+    flag1 "-T" toggle
+    <>
+    cmdArgs target
diff --git a/lib/Chiasma/Data/SendKeysParams.hs b/lib/Chiasma/Data/SendKeysParams.hs
new file mode 100644
--- /dev/null
+++ b/lib/Chiasma/Data/SendKeysParams.hs
@@ -0,0 +1,78 @@
+module Chiasma.Data.SendKeysParams where
+
+import qualified Data.Text as Text
+import Exon (exon)
+import Prelude hiding (repeat)
+
+import Chiasma.Class.CmdArgs (CmdArgs (cmdArgs), flag1, optionWith)
+import Chiasma.Data.Target (Target)
+
+escape :: Text -> Text
+escape fragment =
+  [exon|"#{escapeQuotes}"|]
+  where
+    escapeQuotes =
+      Text.replace [exon|"|] [exon|\"|] fragment
+
+data Key =
+  Key Text
+  |
+  Lit Text
+  deriving stock (Eq, Show)
+
+instance IsString Key where
+  fromString =
+    Lit . toText
+
+instance CmdArgs Key where
+  cmdArgs = \case
+    Key k -> [k]
+    Lit s -> [escape s]
+
+data SendKeysParams =
+  SendKeysParams {
+    enter :: Bool,
+    literal :: Bool,
+    hex :: Bool,
+    reset :: Bool,
+    mouse :: Bool,
+    copyMode :: Bool,
+    repeat :: Maybe Int,
+    keys :: [Key],
+    target :: Target
+  }
+  deriving stock (Eq, Show)
+
+instance Default SendKeysParams where
+  def =
+    SendKeysParams {
+      enter = True,
+      literal = False,
+      hex = False,
+      reset = False,
+      mouse = False,
+      copyMode = False,
+      repeat = Nothing,
+      keys = mempty,
+      target = def
+    }
+
+instance CmdArgs SendKeysParams where
+  cmdArgs SendKeysParams {..} =
+    flag1 "-l" literal
+    <>
+    flag1 "-H" hex
+    <>
+    flag1 "-R" reset
+    <>
+    flag1 "-M" mouse
+    <>
+    flag1 "-X" copyMode
+    <>
+    optionWith "-N" show repeat
+    <>
+    cmdArgs target
+    <>
+    (cmdArgs =<< keys)
+    <>
+    flag1 "enter" (enter && not copyMode)
diff --git a/lib/Chiasma/Data/SessionGroupId.hs b/lib/Chiasma/Data/SessionGroupId.hs
new file mode 100644
--- /dev/null
+++ b/lib/Chiasma/Data/SessionGroupId.hs
@@ -0,0 +1,6 @@
+module Chiasma.Data.SessionGroupId where
+
+newtype SessionGroupId =
+  SessionGroupId { unSessionGroupId :: Text }
+  deriving stock (Eq, Show, Generic)
+  deriving newtype (IsString)
diff --git a/lib/Chiasma/Data/SessionParams.hs b/lib/Chiasma/Data/SessionParams.hs
new file mode 100644
--- /dev/null
+++ b/lib/Chiasma/Data/SessionParams.hs
@@ -0,0 +1,72 @@
+module Chiasma.Data.SessionParams where
+
+import Path (Abs, Dir, Path)
+
+import Chiasma.Class.CmdArgs (CmdArgs (cmdArgs), arg, envVars, flag1, identOption, optionWith)
+import Chiasma.Data.Ident (Ident)
+import Chiasma.Data.SessionGroupId (SessionGroupId (unSessionGroupId))
+import Chiasma.Path (pathText)
+
+data SessionParams =
+  SessionParams {
+    attach :: Bool,
+    defaultSize :: Bool,
+    detach :: Bool,
+    exitClient :: Bool,
+    printInfo :: Bool,
+    cwd :: Maybe (Path Abs Dir),
+    environment :: Map Text Text,
+    name :: Maybe Ident,
+    windowName :: Maybe Ident,
+    target :: Maybe SessionGroupId,
+    width :: Maybe Int,
+    height :: Maybe Int,
+    command :: Maybe Text
+  }
+  deriving stock (Eq, Show)
+
+instance Default SessionParams where
+  def =
+    SessionParams {
+      attach = False,
+      defaultSize = False,
+      detach = False,
+      exitClient = False,
+      printInfo = True,
+      cwd = Nothing,
+      environment = mempty,
+      name = Nothing,
+      windowName = Nothing,
+      target = Nothing,
+      width = Nothing,
+      height = Nothing,
+      command = Nothing
+    }
+
+instance CmdArgs SessionParams where
+  cmdArgs SessionParams {..} =
+    flag1 "-A" attach
+    <>
+    flag1 "-d" defaultSize
+    <>
+    flag1 "-D" detach
+    <>
+    envVars environment
+    <>
+    flag1 "-P" printInfo
+    <>
+    flag1 "-X" exitClient
+    <>
+    optionWith "-c" pathText cwd
+    <>
+    identOption "-n" windowName
+    <>
+    identOption "-s" name
+    <>
+    optionWith "-t" unSessionGroupId target
+    <>
+    optionWith "-x" show width
+    <>
+    optionWith "-y" show height
+    <>
+    arg command
diff --git a/lib/Chiasma/Data/SplitParams.hs b/lib/Chiasma/Data/SplitParams.hs
new file mode 100644
--- /dev/null
+++ b/lib/Chiasma/Data/SplitParams.hs
@@ -0,0 +1,84 @@
+module Chiasma.Data.SplitParams where
+
+import Chiasma.Class.CmdArgs (CmdArgs (cmdArgs), flag1, optionArgs)
+import Chiasma.Data.Axis (Axis)
+import Chiasma.Data.Target (Target (Current), formatTarget)
+
+data SplitSize =
+  Units Int
+  |
+  Percentage Int
+  deriving stock (Eq, Show)
+
+instance CmdArgs SplitSize where
+  cmdArgs = \case
+    Units n -> ["-l", show n]
+    Percentage n -> ["-p", show n]
+
+data SplitWindowParams =
+  SplitWindowParams {
+    axis :: Maybe Axis,
+    size :: Maybe SplitSize,
+    before :: Bool,
+    fullSize :: Bool,
+    readStdin :: Bool
+  }
+  deriving stock (Eq, Show)
+
+instance Default SplitWindowParams where
+  def =
+    SplitWindowParams {
+      axis = Nothing,
+      size = Nothing,
+      before = False,
+      fullSize = False,
+      readStdin = False
+    }
+
+instance CmdArgs SplitWindowParams where
+  cmdArgs SplitWindowParams {..} =
+    optionArgs axis
+    <>
+    optionArgs size
+    <>
+    flag1 "-b" before
+    <>
+    flag1 "-f" fullSize
+    <>
+    flag1 "-K" readStdin
+
+data JoinPaneParams =
+  JoinPaneParams {
+    axis :: Maybe Axis,
+    size :: Maybe SplitSize,
+    before :: Bool,
+    detach :: Bool,
+    source :: Maybe Target,
+    target :: Target
+  }
+  deriving stock (Eq, Show)
+
+instance Default JoinPaneParams where
+  def =
+    JoinPaneParams {
+      axis = Nothing,
+      size = Nothing,
+      before = False,
+      detach = False,
+      source = Nothing,
+      target = Current
+    }
+
+instance CmdArgs JoinPaneParams where
+  cmdArgs JoinPaneParams {..} =
+    optionArgs axis
+    <>
+    optionArgs size
+    <>
+    flag1 "-b" before
+    <>
+    flag1 "-d" detach
+    <>
+    foldMap (formatTarget ["-s"]) source
+    <>
+    cmdArgs target
diff --git a/lib/Chiasma/Data/Target.hs b/lib/Chiasma/Data/Target.hs
new file mode 100644
--- /dev/null
+++ b/lib/Chiasma/Data/Target.hs
@@ -0,0 +1,32 @@
+module Chiasma.Data.Target where
+
+import Chiasma.Class.CmdArgs (CmdArgs (cmdArgs))
+import Chiasma.Data.TmuxId (PaneId, SessionId, WindowId, formatId)
+
+data Target =
+  Pane PaneId
+  |
+  Window WindowId
+  |
+  Session SessionId
+  |
+  Current
+  deriving stock (Eq, Show)
+
+formatTarget :: [Text] -> Target -> [Text]
+formatTarget pre = \case
+    Pane i -> pre <> [formatId i]
+    Window i -> pre <> [formatId i]
+    Session i -> pre <> [formatId i]
+    Current -> []
+
+instance CmdArgs Target where
+  cmdArgs = \case
+    Pane i -> ["-t", formatId i]
+    Window i -> ["-t", formatId i]
+    Session i -> ["-t", formatId i]
+    Current -> []
+
+instance Default Target where
+  def =
+    Current
diff --git a/lib/Chiasma/Data/TmuxCommand.hs b/lib/Chiasma/Data/TmuxCommand.hs
new file mode 100644
--- /dev/null
+++ b/lib/Chiasma/Data/TmuxCommand.hs
@@ -0,0 +1,213 @@
+module Chiasma.Data.TmuxCommand where
+
+import Data.List (dropWhileEnd)
+import qualified Data.Text as Text
+import Exon (exon)
+import Text.Show (Show (showsPrec), showParen, showString, shows)
+
+import Chiasma.Class.CmdArgs (cmdArgs)
+import qualified Chiasma.Codec as Codec
+import Chiasma.Codec (multi, single)
+import Chiasma.Codec.Data.Client (Client)
+import Chiasma.Codec.Data.Pane (Pane)
+import Chiasma.Codec.Data.Session (Session)
+import Chiasma.Codec.Data.Window (Window)
+import Chiasma.Data.CapturePaneParams (CapturePaneParams (CapturePaneParams), stripBlank, stripTrailingWs)
+import Chiasma.Data.CopyModeParams (CopyModeParams)
+import Chiasma.Data.DecodeError (DecodeError)
+import Chiasma.Data.KillPaneParams (KillPaneParams)
+import Chiasma.Data.PaneSelection (PaneSelection)
+import Chiasma.Data.PipePaneParams (PipePaneParams)
+import Chiasma.Data.ResizePaneParams (ResizePaneParams)
+import Chiasma.Data.SelectParams (SelectParams)
+import Chiasma.Data.SelectWindowParams (SelectWindowParams)
+import Chiasma.Data.SendKeysParams (SendKeysParams)
+import Chiasma.Data.SessionParams (SessionParams)
+import Chiasma.Data.SplitParams (JoinPaneParams, SplitWindowParams)
+import Chiasma.Data.Target (Target)
+import Chiasma.Data.TmuxId (ClientId (ClientId))
+import Chiasma.Data.TmuxQuery (TmuxQuery)
+import Chiasma.Data.TmuxRequest (TmuxRequest (TmuxRequest))
+import Chiasma.Data.TmuxResponse (TmuxResponse (TmuxResponse))
+import Chiasma.Data.WindowParams (WindowParams)
+import Chiasma.Data.WindowSelection (WindowSelection)
+import Chiasma.Function (applyWhen)
+
+data TmuxCommand :: Type -> Type where
+  Fmap :: (a -> b) -> TmuxCommand a -> TmuxCommand b
+  ListPanes :: PaneSelection -> TmuxCommand [Pane]
+  ListWindows :: WindowSelection -> TmuxCommand [Window]
+  ListSessions :: TmuxCommand [Session]
+  ListClients :: TmuxCommand [Client]
+  SwitchClient :: ClientId -> Target -> TmuxCommand ()
+  NewWindow :: WindowParams -> TmuxCommand Window
+  SplitWindow :: WindowParams -> SplitWindowParams -> TmuxCommand Pane
+  SelectWindow :: SelectWindowParams -> TmuxCommand ()
+  NewSession :: SessionParams -> TmuxCommand Session
+  CopyMode :: CopyModeParams -> TmuxCommand ()
+  SendKeys :: SendKeysParams -> TmuxCommand ()
+  SelectPane :: SelectParams -> TmuxCommand ()
+  KillPane :: KillPaneParams -> TmuxCommand ()
+  MovePane :: JoinPaneParams -> TmuxCommand ()
+  ResizePane :: ResizePaneParams -> TmuxCommand ()
+  PipePane :: PipePaneParams -> TmuxCommand ()
+  CapturePane :: CapturePaneParams -> TmuxCommand [Text]
+  KillServer :: TmuxCommand ()
+
+instance Functor TmuxCommand where
+  fmap = Fmap
+
+instance Show (TmuxCommand a) where
+  showsPrec d = \case
+    Fmap _ cmd ->
+      showParen (d > 10) [exon|Fmap #{showsPrec 11 cmd}|]
+    ListPanes sel ->
+      showParen (d > 10) [exon|ListPanes #{showsPrec 11 sel}|]
+    ListWindows sel ->
+      showParen (d > 10) [exon|ListWindows #{showsPrec 11 sel}|]
+    ListClients ->
+      showString "ListClients"
+    ListSessions ->
+      showString "ListSessions"
+    SwitchClient c t ->
+      showParen (d > 10) [exon|SwitchClient #{showsPrec 11 c} #{showsPrec 11 t}|]
+    NewWindow params ->
+      showParen (d > 10) [exon|NewWindow #{showsPrec 11 params}|]
+    SplitWindow wParams sParams ->
+      showParen (d > 10) [exon|SplitWindow #{showsPrec 11 wParams} #{showsPrec 11 sParams}|]
+    SelectWindow params ->
+      showParen (d > 10) [exon|SelectWindow #{showsPrec 11 params}|]
+    NewSession params ->
+      showParen (d > 10) [exon|NewSession #{showsPrec 11 params}|]
+    CopyMode params ->
+      showParen (d > 10) [exon|SelectPane #{showsPrec 11 params}|]
+    SendKeys params ->
+      showParen (d > 10) [exon|SendKeys #{showsPrec 11 params}|]
+    SelectPane paneId ->
+      showParen (d > 10) [exon|SelectPane #{shows paneId}|]
+    KillPane params ->
+      showParen (d > 10) [exon|KillPane #{showsPrec 11 params}|]
+    MovePane params ->
+      showParen (d > 10) [exon|MovePane #{showsPrec 11 params}|]
+    ResizePane params ->
+      showParen (d > 10) [exon|ResizePane #{showsPrec 11 params}|]
+    PipePane params ->
+      showParen (d > 10) [exon|PipePane #{showsPrec 11 params}|]
+    CapturePane params ->
+      showParen (d > 10) [exon|CapturePane #{showsPrec 11 params}|]
+    KillServer ->
+      showString "KillServer"
+
+query ::
+  ∀ a .
+  TmuxCommand a ->
+  Maybe TmuxQuery
+query = \case
+  Fmap _ cmd -> query cmd
+  ListPanes _ -> Just (Codec.query @Pane)
+  ListWindows _ -> Just (Codec.query @Window)
+  ListSessions -> Just (Codec.query @Session)
+  ListClients -> Just (Codec.query @Client)
+  SwitchClient _ _ -> Nothing
+  NewWindow _ -> Just (Codec.query @Window)
+  SplitWindow _ _ -> Just (Codec.query @Pane)
+  SelectWindow _ -> Nothing
+  NewSession _ -> Just (Codec.query @Session)
+  CopyMode _ -> Nothing
+  SendKeys _ -> Nothing
+  SelectPane _ -> Nothing
+  KillPane _ -> Nothing
+  MovePane _ -> Nothing
+  ResizePane _ -> Nothing
+  PipePane _ -> Nothing
+  CapturePane _ -> Nothing
+  KillServer -> Nothing
+
+request :: TmuxCommand a -> Maybe TmuxQuery -> TmuxRequest
+request = \case
+  Fmap _ c ->
+    request c
+  ListPanes selection ->
+    TmuxRequest "list-panes" (cmdArgs selection)
+  ListWindows selection ->
+    TmuxRequest "list-windows" (cmdArgs selection)
+  ListSessions ->
+    TmuxRequest "list-sessions" []
+  ListClients ->
+    TmuxRequest "list-clients" []
+  SwitchClient (ClientId client) target ->
+    TmuxRequest "switch-client" (["-c", client] <> cmdArgs target)
+  NewWindow params ->
+    TmuxRequest "new-window" (cmdArgs params)
+  SplitWindow wParams sParams ->
+    TmuxRequest "split-window" (cmdArgs wParams <> cmdArgs sParams)
+  SelectWindow params ->
+    TmuxRequest "select-window" (cmdArgs params)
+  NewSession params ->
+    TmuxRequest "new-session" (cmdArgs params)
+  CopyMode params ->
+    TmuxRequest "copy-mode" (cmdArgs params)
+  SendKeys params ->
+    TmuxRequest "send-keys" (cmdArgs params)
+  SelectPane params ->
+    TmuxRequest "select-pane" (cmdArgs params)
+  KillPane params ->
+    TmuxRequest "kill-pane" (cmdArgs params)
+  MovePane params ->
+    TmuxRequest "move-pane" (cmdArgs params)
+  ResizePane params ->
+    TmuxRequest "resize-pane" (cmdArgs params)
+  PipePane params ->
+    TmuxRequest "pipe-pane" (cmdArgs params)
+  CapturePane params ->
+    TmuxRequest "capture-pane" (cmdArgs params)
+  KillServer ->
+    TmuxRequest "kill-server" []
+
+encode :: TmuxCommand a -> TmuxRequest
+encode cmd =
+  request cmd (query cmd)
+
+decode :: TmuxResponse -> TmuxCommand a -> Either DecodeError a
+decode (TmuxResponse out) = \case
+  Fmap f cmd ->
+    f <$> decode (TmuxResponse out) cmd
+  ListPanes _ ->
+    multi out
+  ListWindows _ ->
+    multi out
+  ListSessions ->
+    multi out
+  ListClients ->
+    multi out
+  SwitchClient _ _ ->
+    unit
+  NewWindow _ ->
+    single out
+  SplitWindow _ _ ->
+    single out
+  SelectWindow _ ->
+    unit
+  NewSession _ ->
+    single out
+  CopyMode _ ->
+    unit
+  SendKeys _ ->
+    unit
+  SelectPane _ ->
+    unit
+  KillPane _ ->
+    unit
+  MovePane _ ->
+    unit
+  ResizePane _ ->
+    unit
+  PipePane _ ->
+    unit
+  CapturePane CapturePaneParams {stripBlank, stripTrailingWs} ->
+    pure $
+    applyWhen stripBlank (dropWhileEnd ("" ==)) $
+    applyWhen stripTrailingWs (fmap Text.stripEnd) $
+    out
+  KillServer ->
+    unit
diff --git a/lib/Chiasma/Data/TmuxError.hs b/lib/Chiasma/Data/TmuxError.hs
--- a/lib/Chiasma/Data/TmuxError.hs
+++ b/lib/Chiasma/Data/TmuxError.hs
@@ -1,39 +1,23 @@
 module Chiasma.Data.TmuxError where
 
-import Chiasma.Codec.Decode (TmuxDecodeError)
-import Text.ParserCombinators.Parsec (ParseError)
+import Polysemy.Process.Data.ProcessError (ProcessError)
 
-import Chiasma.Data.Cmd (Cmds(..))
+import Chiasma.Data.CodecError (CodecError (CodecError))
+import Chiasma.Data.DecodeError (DecodeError)
+import Chiasma.Data.TmuxRequest (TmuxRequest)
 
 data TmuxError =
-  ProcessFailed {
-    processFailedCmds :: Cmds,
-    processFailedReason :: Text
-    }
-  |
-  OutputParsingFailed {
-    parsingFailedCmds :: Cmds,
-    parsingFailedOutput :: [Text],
-    parsingFailedError :: ParseError
-  }
+  ProcessFailed ProcessError
   |
-  NoOutput Cmds
+  RequestFailed TmuxRequest [Text]
   |
-  DecodingFailed {
-    decodingFailedCmds :: Cmds,
-    decodingFailedOutput :: Text,
-    decodingFailedError :: TmuxDecodeError
-  }
+  DecodeFailed TmuxRequest DecodeError
   |
-  InvalidOutput {
-    invalidOutputReason :: Text,
-    invalidOutputCommand :: Text
-  }
+  NoClients
   |
-  CommandFailed {
-    commandFailedCmds :: Cmds,
-    commandFailedError :: [Text]
-  }
-  deriving (Eq, Show)
+  NoExe
+  deriving stock (Eq, Show)
 
-deepPrisms ''TmuxError
+codec :: CodecError -> TmuxError
+codec (CodecError req err) =
+  DecodeFailed req err
diff --git a/lib/Chiasma/Data/TmuxId.hs b/lib/Chiasma/Data/TmuxId.hs
--- a/lib/Chiasma/Data/TmuxId.hs
+++ b/lib/Chiasma/Data/TmuxId.hs
@@ -1,35 +1,43 @@
 module Chiasma.Data.TmuxId where
 
-import Data.Text.Prettyprint.Doc (Pretty(..))
+import Prettyprinter (Pretty (..))
 
-sessionPrefix :: Char
-sessionPrefix = '$'
+newtype ClientId =
+  ClientId { unClientId :: Text }
+  deriving stock (Eq, Show, Generic)
+  deriving newtype (IsString)
 
+instance Pretty ClientId where
+  pretty = pretty . unClientId
+
+sessionPrefix :: Text
+sessionPrefix = "$"
+
 newtype SessionId =
   SessionId Int
-  deriving (Eq, Show, Generic)
+  deriving stock (Eq, Show, Generic)
   deriving newtype (Num, Ord)
 
 instance Pretty SessionId where
   pretty = pretty . formatId
 
-windowPrefix :: Char
-windowPrefix = '@'
+windowPrefix :: Text
+windowPrefix = "@"
 
 newtype WindowId =
   WindowId Int
-  deriving (Eq, Show, Generic)
+  deriving stock (Eq, Show, Generic)
   deriving newtype (Num, Ord)
 
 instance Pretty WindowId where
   pretty = pretty . formatId
 
-panePrefix :: Char
-panePrefix = '%'
+panePrefix :: Text
+panePrefix = "%"
 
 newtype PaneId =
   PaneId Int
-  deriving (Eq, Show, Generic)
+  deriving stock (Eq, Show, Generic)
   deriving newtype (Num, Ord)
 
 instance Pretty PaneId where
@@ -39,7 +47,9 @@
   paneId :: a -> PaneId
 
 newtype TmuxIdPrefix a =
-  TmuxIdPrefix Char
+  TmuxIdPrefix { unTmuxIdPrefix :: Text }
+  deriving stock (Eq, Show, Generic)
+  deriving newtype (IsString)
 
 class TmuxId a where
   prefix :: TmuxIdPrefix a
@@ -47,7 +57,7 @@
 
   formatId :: a -> Text
   formatId a =
-    toText (p : show (number a))
+    p <> show (number a)
     where
       (TmuxIdPrefix p) = prefix @a
 
diff --git a/lib/Chiasma/Data/TmuxNative.hs b/lib/Chiasma/Data/TmuxNative.hs
new file mode 100644
--- /dev/null
+++ b/lib/Chiasma/Data/TmuxNative.hs
@@ -0,0 +1,10 @@
+module Chiasma.Data.TmuxNative where
+
+import Path (Abs, File, Path)
+
+data TmuxNative =
+  TmuxNative {
+    executable :: Path Abs File,
+    tmuxServerSocket :: Maybe (Path Abs File)
+  }
+  deriving stock (Eq, Show)
diff --git a/lib/Chiasma/Data/TmuxOutputBlock.hs b/lib/Chiasma/Data/TmuxOutputBlock.hs
new file mode 100644
--- /dev/null
+++ b/lib/Chiasma/Data/TmuxOutputBlock.hs
@@ -0,0 +1,13 @@
+module Chiasma.Data.TmuxOutputBlock where
+
+data End =
+  EndSuccess
+  |
+  EndError
+  deriving stock (Eq, Show)
+
+data TmuxOutputBlock =
+  Success [Text]
+  |
+  Error [Text]
+  deriving stock (Eq, Show)
diff --git a/lib/Chiasma/Data/TmuxQuery.hs b/lib/Chiasma/Data/TmuxQuery.hs
new file mode 100644
--- /dev/null
+++ b/lib/Chiasma/Data/TmuxQuery.hs
@@ -0,0 +1,6 @@
+module Chiasma.Data.TmuxQuery where
+
+newtype TmuxQuery =
+  TmuxQuery { unTmuxQuery :: Text }
+  deriving stock (Eq, Show, Generic)
+  deriving newtype (IsString, Semigroup, Monoid)
diff --git a/lib/Chiasma/Data/TmuxRequest.hs b/lib/Chiasma/Data/TmuxRequest.hs
new file mode 100644
--- /dev/null
+++ b/lib/Chiasma/Data/TmuxRequest.hs
@@ -0,0 +1,24 @@
+module Chiasma.Data.TmuxRequest where
+
+import qualified Data.Text as Text
+import Chiasma.Data.TmuxQuery (TmuxQuery (TmuxQuery))
+
+data TmuxRequest =
+  TmuxRequest {
+    cmd :: Text,
+    args :: [Text],
+    query :: Maybe TmuxQuery
+  }
+  deriving stock (Eq, Show)
+
+queryArgs :: TmuxQuery -> [Text]
+queryArgs (TmuxQuery q) =
+  ["-F", "'", q, "'"]
+
+cmdline :: TmuxRequest -> [Text]
+cmdline TmuxRequest { ..} =
+  cmd : args ++ foldMap queryArgs query ++ ["\n"]
+
+encode :: TmuxRequest -> ByteString
+encode req =
+  encodeUtf8 (Text.unwords (cmdline req))
diff --git a/lib/Chiasma/Data/TmuxResponse.hs b/lib/Chiasma/Data/TmuxResponse.hs
new file mode 100644
--- /dev/null
+++ b/lib/Chiasma/Data/TmuxResponse.hs
@@ -0,0 +1,6 @@
+module Chiasma.Data.TmuxResponse where
+
+newtype TmuxResponse =
+  TmuxResponse { unTmuxResponse :: [Text] }
+  deriving stock (Eq, Show)
+  deriving newtype (Semigroup, Monoid)
diff --git a/lib/Chiasma/Data/TmuxThunk.hs b/lib/Chiasma/Data/TmuxThunk.hs
--- a/lib/Chiasma/Data/TmuxThunk.hs
+++ b/lib/Chiasma/Data/TmuxThunk.hs
@@ -1,16 +1,16 @@
 module Chiasma.Data.TmuxThunk where
 
-import Chiasma.Codec.Decode (TmuxDecodeError)
-import Chiasma.Data.Cmd (Cmd(..))
-import Chiasma.Data.TmuxError (TmuxError(..))
+-- import Chiasma.Codec.Decode (DecodeFailure)
+-- import Chiasma.Data.Cmd (Cmd(..))
+-- import Chiasma.Data.TmuxError (TmuxError(..))
 
-data TmuxThunk next =
-  ∀ a . Read Cmd (Text -> Either TmuxDecodeError a) ([a] -> next)
-  |
-  Write Cmd (() -> next)
-  |
-  Flush (() -> next)
-  |
-  Failed TmuxError
+-- data TmuxThunk next =
+--   ∀ a . Read Cmd (Text -> Either DecodeFailure a) ([a] -> next)
+--   |
+--   Write Cmd (() -> next)
+--   |
+--   Flush (() -> next)
+--   |
+--   Failed TmuxError
 
-deriving instance Functor TmuxThunk
+-- deriving instance Functor TmuxThunk
diff --git a/lib/Chiasma/Data/View.hs b/lib/Chiasma/Data/View.hs
--- a/lib/Chiasma/Data/View.hs
+++ b/lib/Chiasma/Data/View.hs
@@ -9,7 +9,7 @@
     viewIdent :: Ident,
     viewId :: Maybe a
   }
-  deriving (Eq, Show)
+  deriving stock (Eq, Show)
 
 makeClassy_ ''View
 
diff --git a/lib/Chiasma/Data/Views.hs b/lib/Chiasma/Data/Views.hs
--- a/lib/Chiasma/Data/Views.hs
+++ b/lib/Chiasma/Data/Views.hs
@@ -1,9 +1,8 @@
-{-# LANGUAGE DeriveAnyClass #-}
-
 module Chiasma.Data.Views where
 
-import Data.Text.Prettyprint.Doc (Doc)
-import Data.Text.Prettyprint.Doc.Render.Terminal (AnsiStyle)
+import Control.Lens (makeClassy)
+import Prettyprinter (Doc)
+import Prettyprinter.Render.Terminal (AnsiStyle)
 
 import Chiasma.Data.Ident (Ident)
 import Chiasma.Data.TmuxId (PaneId, SessionId, WindowId)
@@ -17,9 +16,7 @@
   NoSuchPane Ident
   |
   NoPaneId Ident
-  deriving (Eq, Show)
-
-deepPrisms ''ViewsError
+  deriving stock (Eq, Show)
 
 data Views =
   Views {
@@ -28,10 +25,11 @@
     _panes :: [View PaneId],
     _log :: [Doc AnsiStyle]
   }
-  deriving (Show, Generic, Default)
+  deriving stock (Show, Generic)
+  deriving anyclass (Default)
 
+makeClassy ''Views
+
 instance Eq Views where
   (Views sa wa pa _) == (Views sb wb pb _) =
     (sa == sb) && (wa == wb) && (pa == pb)
-
-deepLenses ''Views
diff --git a/lib/Chiasma/Data/WindowParams.hs b/lib/Chiasma/Data/WindowParams.hs
new file mode 100644
--- /dev/null
+++ b/lib/Chiasma/Data/WindowParams.hs
@@ -0,0 +1,56 @@
+module Chiasma.Data.WindowParams where
+
+import Path (Abs, Dir, Path)
+
+import Chiasma.Class.CmdArgs (CmdArgs (cmdArgs), arg, envVars, flag1, identOption, optionWith)
+import Chiasma.Data.Ident (Ident)
+import Chiasma.Data.Target (Target)
+import Chiasma.Path (pathText')
+
+data WindowParams =
+  WindowParams {
+    after :: Bool,
+    detach :: Bool,
+    killExisting :: Bool,
+    printInfo :: Bool,
+    cwd :: Maybe (Path Abs Dir),
+    environment :: Map Text Text,
+    name :: Maybe Ident,
+    target :: Target,
+    command :: Maybe Text
+  }
+  deriving stock (Eq, Show)
+
+instance Default WindowParams where
+  def =
+    WindowParams {
+      after = False,
+      detach = False,
+      killExisting = False,
+      printInfo = True,
+      cwd = Nothing,
+      environment = mempty,
+      name = Nothing,
+      target = def,
+      command = Nothing
+    }
+
+instance CmdArgs WindowParams where
+  cmdArgs WindowParams {..} =
+    flag1 "-a" after
+    <>
+    flag1 "-d" detach
+    <>
+    flag1 "-k" killExisting
+    <>
+    flag1 "-P" printInfo
+    <>
+    optionWith "-c" pathText' cwd
+    <>
+    envVars environment
+    <>
+    identOption "-n" name
+    <>
+    cmdArgs target
+    <>
+    arg command
diff --git a/lib/Chiasma/Data/WindowSelection.hs b/lib/Chiasma/Data/WindowSelection.hs
new file mode 100644
--- /dev/null
+++ b/lib/Chiasma/Data/WindowSelection.hs
@@ -0,0 +1,19 @@
+module Chiasma.Data.WindowSelection where
+
+import Chiasma.Class.CmdArgs (CmdArgs (cmdArgs))
+import Chiasma.Data.Target (Target (Current))
+
+data WindowSelection =
+  All
+  |
+  InSession Target
+  deriving stock (Eq, Show)
+
+instance CmdArgs WindowSelection where
+  cmdArgs = \case
+    All -> ["-a"]
+    InSession target -> cmdArgs target
+
+instance Default WindowSelection where
+  def =
+    InSession Current
diff --git a/lib/Chiasma/Data/WindowState.hs b/lib/Chiasma/Data/WindowState.hs
--- a/lib/Chiasma/Data/WindowState.hs
+++ b/lib/Chiasma/Data/WindowState.hs
@@ -15,4 +15,4 @@
     wsLayout :: RenderableTree,
     wsReferencePane :: PaneId
   }
-  deriving (Eq, Show)
+  deriving stock (Eq, Show)
diff --git a/lib/Chiasma/Data/WithPaneId.hs b/lib/Chiasma/Data/WithPaneId.hs
new file mode 100644
--- /dev/null
+++ b/lib/Chiasma/Data/WithPaneId.hs
@@ -0,0 +1,39 @@
+module Chiasma.Data.WithPaneId where
+
+import qualified Data.Text as Text
+
+import Chiasma.Codec (TmuxCodec (decode, query), trim)
+import Chiasma.Codec.Decode (primDecode)
+import Chiasma.Data.DecodeError (DecodeError (DecodeError), DecodeFailure (TooFewFields))
+import qualified Chiasma.Data.TmuxId as TmuxId
+import Chiasma.Data.TmuxId (HasPaneId, PaneId)
+import Chiasma.Data.TmuxQuery (TmuxQuery (TmuxQuery))
+
+data WithPaneId a =
+  WithPaneId {
+    paneId :: PaneId,
+    pane :: a
+  }
+  deriving stock (Eq, Show, Generic)
+
+instance HasPaneId (WithPaneId a) where
+  paneId = paneId
+
+safeBreakOn :: Text -> Text -> Maybe (Text, Text)
+safeBreakOn n = \case
+  "" -> Nothing
+  t -> Just (second (Text.drop 1) (Text.breakOn n t))
+
+instance TmuxCodec a => TmuxCodec (WithPaneId a) where
+  decode payload =
+    case safeBreakOn " " (trim payload) of
+      Just (idField, rest) -> do
+        pane <- decode rest
+        paneId <- first (DecodeError [payload]) (primDecode idField)
+        pure WithPaneId {..}
+      Nothing ->
+        Left (DecodeError [payload] TooFewFields)
+
+  query =
+    let TmuxQuery paneQuery = query @a
+    in TmuxQuery ("#{pane_id} " <> paneQuery)
diff --git a/lib/Chiasma/Effect/Codec.hs b/lib/Chiasma/Effect/Codec.hs
new file mode 100644
--- /dev/null
+++ b/lib/Chiasma/Effect/Codec.hs
@@ -0,0 +1,32 @@
+module Chiasma.Effect.Codec where
+
+import Chiasma.Data.CodecError (CodecError)
+import Chiasma.Data.TmuxCommand (TmuxCommand)
+import Chiasma.Data.TmuxRequest (TmuxRequest)
+import Chiasma.Data.TmuxResponse (TmuxResponse)
+
+data Codec (command :: Type -> Type) (i :: Type) (o :: Type) :: Effect where
+  WithCodec :: command a -> (i -> m o) -> Codec command i o m a
+  Encode :: command a -> Codec command i o m i
+
+makeSem ''Codec
+
+type NativeCodec command =
+  Codec command TmuxRequest TmuxResponse
+
+type NativeCodecE command =
+  NativeCodec command !! CodecError
+
+type NativeCommandCodec =
+  NativeCodec TmuxCommand
+
+type NativeCommandCodecE =
+  NativeCodecE TmuxCommand
+
+type family NativeCodecs (cs :: [Type -> Type]) :: [Effect] where
+  NativeCodecs '[] = '[]
+  NativeCodecs (c : cs) = NativeCodec c : NativeCodecs cs
+
+type family NativeCodecsE (cs :: [Type -> Type]) :: [Effect] where
+  NativeCodecsE '[] = '[]
+  NativeCodecsE (c : cs) = NativeCodecE c : NativeCodecsE cs
diff --git a/lib/Chiasma/Effect/TmuxApi.hs b/lib/Chiasma/Effect/TmuxApi.hs
new file mode 100644
--- /dev/null
+++ b/lib/Chiasma/Effect/TmuxApi.hs
@@ -0,0 +1,12 @@
+module Chiasma.Effect.TmuxApi where
+
+import Chiasma.Data.TmuxCommand (TmuxCommand)
+
+data TmuxApi (command :: Type -> Type) :: Effect where
+  Send :: command a -> TmuxApi command m a
+  Schedule :: command a -> TmuxApi command m ()
+
+makeSem ''TmuxApi
+
+type Tmux =
+  TmuxApi TmuxCommand
diff --git a/lib/Chiasma/Effect/TmuxClient.hs b/lib/Chiasma/Effect/TmuxClient.hs
new file mode 100644
--- /dev/null
+++ b/lib/Chiasma/Effect/TmuxClient.hs
@@ -0,0 +1,28 @@
+module Chiasma.Effect.TmuxClient where
+
+import Prelude hiding (send)
+
+import Chiasma.Data.TmuxRequest (TmuxRequest)
+import Chiasma.Data.TmuxResponse (TmuxResponse)
+
+data TmuxClient (i :: Type) (o :: Type) :: Effect where
+  Send :: i -> TmuxClient i o m o
+  Schedule :: i -> TmuxClient i o m ()
+
+makeSem ''TmuxClient
+
+type ScopedTmux i o =
+  Scoped_ (TmuxClient i o)
+
+type NativeTmux =
+  ScopedTmux TmuxRequest TmuxResponse
+
+flush ::
+  Member (TmuxClient e d) r =>
+  InterpreterFor (TmuxClient e d) r
+flush =
+  interpret \case
+    Send cmd ->
+      send cmd
+    Schedule cmd ->
+      void (send cmd)
diff --git a/lib/Chiasma/Event.hs b/lib/Chiasma/Event.hs
new file mode 100644
--- /dev/null
+++ b/lib/Chiasma/Event.hs
@@ -0,0 +1,58 @@
+module Chiasma.Event where
+
+import Exon (exon)
+import qualified Polysemy.Conc as Conc
+import Polysemy.Conc (withAsync_)
+import qualified Polysemy.Log as Log
+import qualified Polysemy.Time as Time
+import Polysemy.Time (Seconds (Seconds))
+import Prelude hiding (listen)
+
+import Chiasma.Data.Event (Event)
+import Chiasma.Data.ReceiveEvent (ReceiveEvent (ReceiveEvent))
+import Chiasma.Data.TmuxError (TmuxError)
+import Chiasma.Effect.Codec (Codec)
+import qualified Chiasma.Effect.TmuxApi as TmuxApi
+import Chiasma.Effect.TmuxApi (TmuxApi)
+import Chiasma.Effect.TmuxClient (TmuxClient)
+import Chiasma.Tmux (withTmux)
+
+receiveEvent ::
+  ∀ r .
+  Members [TmuxApi ReceiveEvent, Events Event] r =>
+  Sem r ()
+receiveEvent =
+  Conc.publish =<< TmuxApi.send ReceiveEvent
+
+listenLoop ::
+  ∀ err t d r .
+  Show err =>
+  Members [TmuxApi ReceiveEvent !! err, Events Event, Time t d, Log] r =>
+  Sem r ()
+listenLoop = do
+  resume @_ @(TmuxApi _) receiveEvent \ err ->
+    Log.error [exon|Receiving tmux event: #{show err}|]
+  listenLoop
+
+listen ::
+  ∀ enc dec err t d r .
+  Show err =>
+  Member (Codec ReceiveEvent enc dec !! err) r =>
+  Members [Scoped_ (TmuxClient enc dec) !! TmuxError, Events Event, Time t d, Log] r =>
+  Sem r ()
+listen = do
+  resume @_ @(Scoped_ _) (withTmux listenLoop) \ err -> do
+    Log.error [exon|Lost connection to tmux: #{show err}
+Reconnecting...|]
+    Time.sleep (Seconds 1)
+
+withTmuxEvents ::
+  Show err =>
+  Member (Codec ReceiveEvent enc dec !! err) r =>
+  Member (Scoped_ (TmuxClient enc dec) !! TmuxError) r =>
+  Members [Events Event, Time t d, Log, Race, Async, Resource] r =>
+  Sem r a ->
+  Sem r a
+withTmuxEvents =
+  withAsync_ do
+    forever listen
diff --git a/lib/Chiasma/Function.hs b/lib/Chiasma/Function.hs
new file mode 100644
--- /dev/null
+++ b/lib/Chiasma/Function.hs
@@ -0,0 +1,7 @@
+module Chiasma.Function where
+
+applyWhen :: Bool -> (a -> a) -> a -> a
+applyWhen True f x =
+  f x
+applyWhen False _ x =
+  x
diff --git a/lib/Chiasma/Interpreter/Codec.hs b/lib/Chiasma/Interpreter/Codec.hs
new file mode 100644
--- /dev/null
+++ b/lib/Chiasma/Interpreter/Codec.hs
@@ -0,0 +1,50 @@
+module Chiasma.Interpreter.Codec where
+
+import Chiasma.Codec (TmuxCodec)
+import Chiasma.Data.CodecError (CodecError (CodecError))
+import Chiasma.Data.DecodeError (DecodeError)
+import qualified Chiasma.Data.Panes as Panes
+import Chiasma.Data.Panes (Panes)
+import qualified Chiasma.Data.TmuxCommand as TmuxCommand
+import Chiasma.Data.TmuxCommand (TmuxCommand)
+import Chiasma.Data.TmuxRequest (TmuxRequest)
+import Chiasma.Data.TmuxResponse (TmuxResponse)
+import qualified Chiasma.Effect.Codec as Codec
+import Chiasma.Effect.Codec (Codec)
+
+interpretCodecNative ::
+  (∀ x . cmd x -> TmuxRequest) ->
+  (∀ x . TmuxResponse -> cmd x -> Either DecodeError x) ->
+  InterpreterFor (Codec cmd TmuxRequest TmuxResponse !! CodecError) r
+interpretCodecNative enc dec =
+  interpretResumableH \case
+    Codec.Encode cmd ->
+      pureT (enc cmd)
+    Codec.WithCodec cmd use -> do
+      let req = enc cmd
+      out <- runTSimple (use req)
+      Inspector ins <- getInspectorT
+      pureT =<< stopEitherWith (CodecError req) (dec (fold (ins out)) cmd)
+
+interpretCodecTmuxCommand ::
+  InterpreterFor (Codec TmuxCommand TmuxRequest TmuxResponse !! CodecError) r
+interpretCodecTmuxCommand =
+  interpretCodecNative TmuxCommand.encode TmuxCommand.decode
+{-# inline interpretCodecTmuxCommand #-}
+
+interpretCodecPanes ::
+  TmuxCodec p =>
+  InterpreterFor (Codec (Panes p) TmuxRequest TmuxResponse !! CodecError) r
+interpretCodecPanes =
+  interpretCodecNative Panes.encode Panes.decode
+{-# inline interpretCodecPanes #-}
+
+interpretCodecPure ::
+  (∀ a . command a -> Sem r (Either Text a)) ->
+  InterpreterFor (Codec command () decode !! Text) r
+interpretCodecPure run =
+  interpretResumableH \case
+    Codec.Encode _ ->
+      pureT ()
+    Codec.WithCodec cmd _ ->
+      either stop pureT =<< raise (raise (run cmd))
diff --git a/lib/Chiasma/Interpreter/ProcessOutput.hs b/lib/Chiasma/Interpreter/ProcessOutput.hs
new file mode 100644
--- /dev/null
+++ b/lib/Chiasma/Interpreter/ProcessOutput.hs
@@ -0,0 +1,39 @@
+module Chiasma.Interpreter.ProcessOutput where
+
+import Data.Attoparsec.ByteString (parse)
+import Data.Attoparsec.Types (IResult (Done, Fail, Partial))
+import Polysemy.Conc (interpretAtomic)
+import Polysemy.Process.Effect.ProcessOutput (ProcessOutput (Chunk))
+
+import Chiasma.Data.TmuxOutputBlock (TmuxOutputBlock)
+import Chiasma.Native.TmuxOutputBlock (parser)
+
+type ParseResult =
+  IResult ByteString TmuxOutputBlock
+
+type ParseCont =
+  ByteString -> IResult ByteString TmuxOutputBlock
+
+parseResult ::
+  Member (AtomicState (Maybe ParseCont)) r =>
+  ParseResult ->
+  Sem r ([Either Text TmuxOutputBlock], ByteString)
+parseResult = \case
+  Fail _ _ err -> pure ([Left (toText err)], "")
+  Partial c -> ([], "") <$ atomicPut (Just c)
+  Done rest block -> pure ([Right block], rest)
+
+interpretProcessOutputTmuxBlock ::
+  ∀ p r .
+  Member (Embed IO) r =>
+  InterpreterFor (ProcessOutput p (Either Text TmuxOutputBlock)) r
+interpretProcessOutputTmuxBlock =
+  interpretAtomic (Nothing :: Maybe ParseCont) .
+  interpret \case
+    Chunk _ new ->
+      atomicState' (Nothing,) >>= \case
+        Just cont ->
+          parseResult (cont new)
+        Nothing ->
+          parseResult (parse parser new)
+  . raiseUnder
diff --git a/lib/Chiasma/Interpreter/Pure.hs b/lib/Chiasma/Interpreter/Pure.hs
new file mode 100644
--- /dev/null
+++ b/lib/Chiasma/Interpreter/Pure.hs
@@ -0,0 +1,13 @@
+module Chiasma.Interpreter.Pure where
+
+import Chiasma.Data.TmuxError (TmuxError)
+import Chiasma.Effect.Codec (Codec)
+import Chiasma.Effect.TmuxClient (TmuxClient)
+import Chiasma.Interpreter.Codec (interpretCodecPure)
+import Chiasma.Interpreter.TmuxClient (interpretTmuxClientNull)
+
+interpretTmuxPure ::
+  (∀ a . command a -> Sem r (Either Text a)) ->
+  InterpretersFor [Scoped_ (TmuxClient () ()) !! TmuxError, Codec command () () !! Text] r
+interpretTmuxPure run =
+  interpretCodecPure run . interpretTmuxClientNull
diff --git a/lib/Chiasma/Interpreter/TmuxApi.hs b/lib/Chiasma/Interpreter/TmuxApi.hs
new file mode 100644
--- /dev/null
+++ b/lib/Chiasma/Interpreter/TmuxApi.hs
@@ -0,0 +1,90 @@
+module Chiasma.Interpreter.TmuxApi where
+
+import Fcf (Eval, Exp, Pure1, type (@@))
+import Fcf.Class.Functor (FMap)
+import Prelude hiding (send, type (@@))
+
+import Chiasma.Data.CodecError (CodecError)
+import Chiasma.Data.TmuxRequest (TmuxRequest)
+import Chiasma.Data.TmuxResponse (TmuxResponse)
+import Chiasma.Effect.Codec (Codec, encode, withCodec)
+import Chiasma.Effect.TmuxApi (TmuxApi (Schedule, Send), send)
+import qualified Chiasma.Effect.TmuxClient as TmuxClient
+import Chiasma.Effect.TmuxClient (TmuxClient)
+
+type family (f :: l -> k) <$> (fa :: [l]) :: [k] where
+  f <$> fa =
+    FMap (Pure1 f) @@ fa
+
+flush ::
+  Member (TmuxApi c) r =>
+  InterpreterFor (TmuxApi c) r
+flush =
+  interpret \case
+    Send cmd ->
+      send cmd
+    Schedule cmd ->
+      void (send cmd)
+
+interpretTmuxApi ::
+  ∀ command i o err r .
+  Members [TmuxClient i o, Codec command i o !! err] r =>
+  InterpreterFor (TmuxApi command !! err) r
+interpretTmuxApi =
+  interpretResumable \case
+    Send cmd -> do
+      restop @_ @(Codec _ _ _) $ withCodec cmd \ encoded -> do
+        TmuxClient.send encoded
+    Schedule cmd -> do
+      encoded <- restop (encode cmd)
+      TmuxClient.schedule encoded
+
+data TmuxApiEffect :: Type -> (Type -> Type) -> Exp Effect
+
+type instance Eval (TmuxApiEffect err command) =
+  TmuxApi command !! err
+
+type family TmuxApis (commands :: [Type -> Type]) (err :: Type) :: EffectRow where
+  TmuxApis commands err =
+    FMap (TmuxApiEffect err) @@ commands
+
+class InterpretApis (commands :: [Type -> Type]) err i o r where
+  interpretApis :: InterpretersFor (TmuxApis commands err) (TmuxClient i o : r)
+
+instance InterpretApis '[] err i o r where
+  interpretApis =
+    id
+
+instance (
+    r1 ~ (TmuxApis commands err ++ TmuxClient i o : r),
+    Member (TmuxClient i o) r1,
+    Member (Codec command i o !! err) r1,
+    InterpretApis commands err i o r
+  ) => InterpretApis (command : commands) err i o r where
+    interpretApis =
+      interpretApis @commands @err . interpretTmuxApi
+
+type InterpretApisNative commands r =
+  InterpretApis commands CodecError TmuxRequest TmuxResponse r
+
+class RestopApis (commands :: [Type -> Type]) err i o r where
+  restopApis :: InterpretersFor (TmuxApi <$> commands) (TmuxClient i o : r)
+
+instance RestopApis '[] err i o r where
+  restopApis =
+    id
+
+instance (
+    r1 ~ (TmuxApi <$> commands ++ TmuxClient i o : r),
+    Members [TmuxClient i o, Stop err] r1,
+    Member (Codec command i o !! err) r1,
+    RestopApis commands err i o r
+  ) => RestopApis (command : commands) err i o r where
+    restopApis =
+      restopApis @commands @err @i @o .
+      interpretTmuxApi @command @i @o .
+      restop @err @(TmuxApi command) .
+      raiseUnder
+
+type RestopApisNative commands r =
+  RestopApis commands CodecError TmuxRequest TmuxResponse r
diff --git a/lib/Chiasma/Interpreter/TmuxClient.hs b/lib/Chiasma/Interpreter/TmuxClient.hs
new file mode 100644
--- /dev/null
+++ b/lib/Chiasma/Interpreter/TmuxClient.hs
@@ -0,0 +1,201 @@
+module Chiasma.Interpreter.TmuxClient where
+
+import Conc (interpretAtomic, interpretScopedResumableWith_, interpretScopedResumable_)
+import Data.Sequence ((|>))
+import qualified Data.Text as Text
+import Exon (exon)
+import qualified Log as Log
+import Path (Abs, File, Path, relfile, toFilePath)
+import Polysemy.Process.Interpreter.Process (ProcessQueues)
+import qualified Process as Process
+import Process (
+  OutputPipe (Stderr, Stdout),
+  Process,
+  ProcessError,
+  SystemProcess,
+  SystemProcessError,
+  SystemProcessScopeError,
+  interpretProcessInputId,
+  interpretProcessOutputLeft,
+  interpretProcessOutputTextLines,
+  interpretProcess_,
+  interpretSystemProcessNative_,
+  resolveExecutable,
+  withProcess_,
+  )
+import System.Process.Typed (ProcessConfig, proc)
+
+import qualified Chiasma.Data.TmuxError as TmuxError
+import Chiasma.Data.TmuxError (TmuxError (NoExe))
+import Chiasma.Data.TmuxNative (TmuxNative (TmuxNative))
+import qualified Chiasma.Data.TmuxOutputBlock as TmuxOutputBlock
+import Chiasma.Data.TmuxOutputBlock (TmuxOutputBlock)
+import qualified Chiasma.Data.TmuxRequest as TmuxRequest
+import Chiasma.Data.TmuxRequest (TmuxRequest (TmuxRequest))
+import Chiasma.Data.TmuxResponse (TmuxResponse (TmuxResponse))
+import qualified Chiasma.Effect.TmuxClient as TmuxClient
+import Chiasma.Effect.TmuxClient (TmuxClient)
+import Chiasma.Interpreter.ProcessOutput (interpretProcessOutputTmuxBlock)
+
+type TmuxQueues =
+  ProcessQueues (Either Text TmuxOutputBlock) Text
+
+type TmuxProc =
+  Process ByteString (Either Text TmuxOutputBlock)
+
+validate :: TmuxRequest -> TmuxOutputBlock -> Either TmuxError TmuxResponse
+validate request = \case
+  TmuxOutputBlock.Success a ->
+    Right (TmuxResponse a)
+  TmuxOutputBlock.Error a ->
+    Left (TmuxError.RequestFailed request a)
+
+tmuxRequest ::
+  Members [Process ByteString (Either Text TmuxOutputBlock), Log, Stop TmuxError] r =>
+  TmuxRequest ->
+  Sem r TmuxResponse
+tmuxRequest request = do
+  Log.trace [exon|tmux request: #{Text.stripEnd (decodeUtf8 cmdline)}|]
+  Process.send cmdline
+  Process.recv >>= \case
+    Left err -> stop (TmuxError.RequestFailed request [err])
+    Right block -> do
+      Log.trace [exon|tmux response: #{show block}|]
+      stopEither (validate request block)
+  where
+    cmdline =
+      TmuxRequest.encode request
+
+socketArg :: Path Abs File -> [String]
+socketArg socket =
+  ["-S", toFilePath socket]
+
+tmuxProc ::
+  TmuxNative ->
+  ProcessConfig () () ()
+tmuxProc (TmuxNative exe socket) =
+  proc (toFilePath exe) (foldMap socketArg socket <> ["-C", "-u", "attach-session", "-f", "ignore-size"])
+
+interpretSystemProcessTmux ::
+  Members [Reader TmuxNative, Resource, Race, Async, Embed IO] r =>
+  InterpreterFor (Scoped_ (SystemProcess !! SystemProcessError) !! SystemProcessScopeError) r
+interpretSystemProcessTmux sem = do
+  conf <- tmuxProc <$> ask
+  interpretSystemProcessNative_ conf sem
+
+interpretProcessTmux ::
+  Member (Scoped_ (SystemProcess !! SystemProcessError) !! SystemProcessScopeError) r =>
+  Members [Resource, Race, Async, Embed IO] r =>
+  InterpreterFor (Scoped_ TmuxProc !! ProcessError) r
+interpretProcessTmux sem = do
+  interpretProcessOutputTmuxBlock @'Stdout $
+    interpretProcessOutputTextLines @'Stderr $
+    interpretProcessOutputLeft @'Stderr $
+    interpretProcessInputId $
+    interpretProcess_ def $
+    insertAt @1 sem
+{-# inline interpretProcessTmux #-}
+
+flush ::
+  Members [TmuxProc, AtomicState (Seq TmuxRequest), Log, Stop TmuxError] r =>
+  Sem r ()
+flush =
+  traverse_ tmuxRequest =<< atomicState' (mempty,)
+
+tmuxSession ::
+  ∀ r a .
+  Members [Scoped_ TmuxProc !! ProcessError, AtomicState (Seq TmuxRequest), Log, Stop TmuxError] r =>
+  Sem (TmuxProc : r) a ->
+  Sem r a
+tmuxSession action =
+  resumeHoist @ProcessError @(Scoped_ TmuxProc) TmuxError.ProcessFailed $ withProcess_ do
+    void Process.recv
+    tmuxRequest (TmuxRequest "refresh-client" ["-C", "10000x10000"] Nothing)
+    raiseUnder action <* flush
+
+interpretTmuxProcessBuffered ::
+  Members [AtomicState (Seq TmuxRequest), Scoped_ TmuxProc !! ProcessError, Log, Embed IO] r =>
+  InterpreterFor (Scoped_ (TmuxClient TmuxRequest TmuxResponse) !! TmuxError) r
+interpretTmuxProcessBuffered =
+  interpretScopedResumableWith_ @'[TmuxProc] (const tmuxSession) \case
+    TmuxClient.Schedule request ->
+      atomicModify' (|> request)
+    TmuxClient.Send cmd -> do
+      flush
+      tmuxRequest cmd
+{-# inline interpretTmuxProcessBuffered #-}
+
+interpretTmuxWithProcess ::
+  Members [Scoped_ TmuxProc !! ProcessError, Log, Embed IO] r =>
+  InterpreterFor (Scoped_ (TmuxClient TmuxRequest TmuxResponse) !! TmuxError) r
+interpretTmuxWithProcess =
+  interpretAtomic mempty .
+  interpretTmuxProcessBuffered .
+  raiseUnder
+{-# inline interpretTmuxWithProcess #-}
+
+interpretTmuxNative ::
+  ∀ r .
+  Members [Reader TmuxNative, Log, Resource, Race, Async, Embed IO] r =>
+  InterpreterFor (Scoped_ (TmuxClient TmuxRequest TmuxResponse) !! TmuxError) r
+interpretTmuxNative =
+  interpretSystemProcessTmux .
+  interpretProcessTmux .
+  interpretTmuxWithProcess .
+  raiseUnder2
+{-# inline interpretTmuxNative #-}
+
+interpretTmuxFailing ::
+  TmuxError ->
+  InterpreterFor (Scoped_ (TmuxClient TmuxRequest TmuxResponse) !! TmuxError) r
+interpretTmuxFailing err =
+  interpretScopedResumable_ mempty \ () -> \case
+    TmuxClient.Schedule _ ->
+      stop err
+    TmuxClient.Send _ ->
+      stop err
+
+withTmuxNativeEnv ::
+  Member (Embed IO) r =>
+  Maybe (Path Abs File) ->
+  (Maybe TmuxNative -> Sem r a) ->
+  Sem r a
+withTmuxNativeEnv socket use =
+  use . fmap (flip TmuxNative socket) . rightToMaybe =<< resolveExecutable [relfile|tmux|] Nothing
+
+runReaderTmuxNativeEnv ::
+  Members [Error TmuxError, Embed IO] r =>
+  Maybe (Path Abs File) ->
+  InterpreterFor (Reader TmuxNative) r
+runReaderTmuxNativeEnv socket sem = do
+  tn <- withTmuxNativeEnv socket (note NoExe)
+  runReader tn sem
+{-# inline runReaderTmuxNativeEnv #-}
+
+interpretTmuxNativeEnv ::
+  Members [Error TmuxError, Log, Resource, Race, Async, Embed IO] r =>
+  Maybe (Path Abs File) ->
+  InterpreterFor (Scoped_ (TmuxClient TmuxRequest TmuxResponse) !! TmuxError) r
+interpretTmuxNativeEnv socket =
+  runReaderTmuxNativeEnv socket . interpretTmuxNative . raiseUnder
+{-# inline interpretTmuxNativeEnv #-}
+
+interpretTmuxNativeEnvGraceful ::
+  Members [Log, Resource, Race, Async, Embed IO] r =>
+  Maybe (Path Abs File) ->
+  InterpreterFor (Scoped_ (TmuxClient TmuxRequest TmuxResponse) !! TmuxError) r
+interpretTmuxNativeEnvGraceful socket sem =
+  withTmuxNativeEnv socket \case
+    Just tn -> runReader tn (interpretTmuxNative (raiseUnder sem))
+    Nothing -> interpretTmuxFailing NoExe sem
+{-# inline interpretTmuxNativeEnvGraceful #-}
+
+interpretTmuxClientNull ::
+  InterpreterFor (Scoped_ (TmuxClient i ()) !! TmuxError) r
+interpretTmuxClientNull =
+  interpretScopedResumable_ mempty \ () -> \case
+    TmuxClient.Schedule _ ->
+      unit
+    TmuxClient.Send _ ->
+      unit
+{-# inline interpretTmuxClientNull #-}
diff --git a/lib/Chiasma/Lens/Tree.hs b/lib/Chiasma/Lens/Tree.hs
--- a/lib/Chiasma/Lens/Tree.hs
+++ b/lib/Chiasma/Lens/Tree.hs
@@ -1,37 +1,36 @@
-{-# LANGUAGE TemplateHaskell #-}
-{-# LANGUAGE TypeFamilies #-}
-{-# LANGUAGE RankNTypes #-}
-{-# LANGUAGE FunctionalDependencies #-}
-{-# LANGUAGE DeriveDataTypeable #-}
-
 module Chiasma.Lens.Tree where
 
-import Chiasma.Data.Ident (Ident, Identifiable(..))
-import Chiasma.Ui.Data.View
-import Chiasma.Ui.Lens.Ident (matchIdentP)
 import Control.Lens (
   Fold,
   Index,
   IxValue,
-  Ixed(ix),
-  Plated(..),
-  Traversal',
+  Ixed (ix),
+  Plated (..),
   cosmos,
-  each,
   makeClassy_,
-  over,
   preview,
-  toListOf,
   transform,
   )
 import Data.Data (Data)
 import Data.Foldable (foldrM)
+import Prelude hiding (ix, transform)
 
+import Chiasma.Data.Ident (Ident, Identifiable (..))
+import Chiasma.Ui.Data.View (
+  HasTree (_treeSubs),
+  HasTreeSub (leafData),
+  LayoutView,
+  PaneView,
+  Tree (Tree),
+  TreeSub (TreeNode),
+  )
+import Chiasma.Ui.Lens.Ident (matchIdentP)
+
 newtype NodeIndexTree l p =
   NodeIndexTree {
     nitTree :: Tree l p
   }
-  deriving (Eq, Show)
+  deriving stock (Eq, Show)
 
 makeClassy_ ''NodeIndexTree
 
@@ -39,7 +38,7 @@
   LeafIndexTree {
     litTree :: Tree l p
   }
-  deriving (Eq, Show)
+  deriving stock (Eq, Show)
 
 makeClassy_ ''LeafIndexTree
 
@@ -90,7 +89,7 @@
 subtreesWithLayout :: ∀ l p m. Monad m => ((l, TreeSub l p) -> m (l, TreeSub l p)) -> Tree l p -> m (Tree l p)
 subtreesWithLayout f (Tree l0 sub) = do
   (newL, newSub) <- foldrM applySub (l0, []) sub
-  return (Tree newL newSub)
+  pure (Tree newL newSub)
   where
     prependSub s (newL, newN) = (newL, newN : s)
     applySub :: TreeSub l p -> (l, [TreeSub l p]) -> m (l, [TreeSub l p])
@@ -103,7 +102,7 @@
 subtrees :: ∀ l p m. Monad m => (TreeSub l p -> m (TreeSub l p)) -> Tree l p -> m (Tree l p)
 subtrees f (Tree l sub) = do
   newSub <- mapM applySub sub
-  return (Tree l newSub)
+  pure (Tree l newSub)
   where
     applySub :: TreeSub l p -> m (TreeSub l p)
     applySub (TreeNode t) = do
diff --git a/lib/Chiasma/Lens/Where.hs b/lib/Chiasma/Lens/Where.hs
--- a/lib/Chiasma/Lens/Where.hs
+++ b/lib/Chiasma/Lens/Where.hs
@@ -1,10 +1,6 @@
-{-# LANGUAGE RankNTypes #-}
-
-module Chiasma.Lens.Where(
-  where1,
-) where
+module Chiasma.Lens.Where where
 
-import Control.Lens (Over, filtered, taking)
+import Control.Lens (Over, taking)
 
 where1 :: (Applicative f, Traversable t) => (a -> Bool) -> Over (->) f (t a) (t a) a a
 where1 predicate = taking 1 $ traverse . filtered predicate
diff --git a/lib/Chiasma/Monad/EvalFreeT.hs b/lib/Chiasma/Monad/EvalFreeT.hs
--- a/lib/Chiasma/Monad/EvalFreeT.hs
+++ b/lib/Chiasma/Monad/EvalFreeT.hs
@@ -1,50 +1,50 @@
 module Chiasma.Monad.EvalFreeT where
 
-import Control.Monad.Trans.Free (FreeF(..), FreeT(..))
+-- import Control.Monad.Trans.Free (FreeF(..), FreeT(..))
 
-import Chiasma.Codec.Decode (TmuxDecodeError)
-import Chiasma.Data.Cmd (Cmd(..), Cmds(..))
-import Chiasma.Data.TmuxError (TmuxError)
-import Chiasma.Data.TmuxThunk (TmuxThunk(..))
+-- import Chiasma.Codec.Decode (DecodeFailure)
+-- import Chiasma.Data.Cmd (Cmd(..), Cmds(..))
+-- import Chiasma.Data.TmuxError (TmuxError)
+-- import Chiasma.Data.TmuxThunk (TmuxThunk(..))
 
-newtype CmdBuffer = CmdBuffer [Cmd]
+-- newtype CmdBuffer = CmdBuffer [Cmd]
 
-instance Default CmdBuffer where
-  def = CmdBuffer def
+-- instance Default CmdBuffer where
+--   def = CmdBuffer def
 
-type CommandExec m =
-  (∀ b. (Text -> Either TmuxDecodeError b) -> Cmds -> m (Either TmuxError [b]))
+-- type CommandExec m =
+--   (∀ b. (Text -> Either DecodeFailure b) -> Cmds -> m (Either TmuxError [b]))
 
-evalFreeF ::
-  Monad m =>
-  CommandExec m ->
-  CmdBuffer ->
-  FreeF TmuxThunk a (FreeT TmuxThunk m a) ->
-  m (Either TmuxError a)
-evalFreeF _ (CmdBuffer []) (Pure a) =
-  return (Right a)
-evalFreeF exec (CmdBuffer cmds) (Pure a) =
-  Right a <$ exec (const $ Right ()) (Cmds cmds)
-evalFreeF exec (CmdBuffer cmds) (Free (Read cmd decode next)) = do
-  a <- exec decode $ Cmds (cmd : cmds)
-  case a of
-    Right a' -> evalFreeT exec def (next a')
-    Left err -> return (Left err)
-evalFreeF exec (CmdBuffer cmds) (Free (Write cmd next)) =
-  evalFreeT exec (CmdBuffer (cmd : cmds)) (next ())
-evalFreeF exec (CmdBuffer cmds) (Free (Flush next)) =
-  exec (const $ Right ()) (Cmds cmds) >>= \case
-    Right _ -> evalFreeT exec def (next ())
-    Left err -> return (Left err)
-evalFreeF _ _ (Free (Failed err)) =
-  return (Left err)
+-- evalFreeF ::
+--   Monad m =>
+--   CommandExec m ->
+--   CmdBuffer ->
+--   FreeF TmuxThunk a (FreeT TmuxThunk m a) ->
+--   m (Either TmuxError a)
+-- evalFreeF _ (CmdBuffer []) (Pure a) =
+--   pure (Right a)
+-- evalFreeF exec (CmdBuffer cmds) (Pure a) =
+--   Right a <$ exec (const $ Right ()) (Cmds cmds)
+-- evalFreeF exec (CmdBuffer cmds) (Free (Read cmd decode next)) = do
+--   a <- exec decode $ Cmds (cmd : cmds)
+--   case a of
+--     Right a' -> evalFreeT exec def (next a')
+--     Left err -> pure (Left err)
+-- evalFreeF exec (CmdBuffer cmds) (Free (Write cmd next)) =
+--   evalFreeT exec (CmdBuffer (cmd : cmds)) (next ())
+-- evalFreeF exec (CmdBuffer cmds) (Free (Flush next)) =
+--   exec (const $ Right ()) (Cmds cmds) >>= \case
+--     Right _ -> evalFreeT exec def (next ())
+--     Left err -> pure (Left err)
+-- evalFreeF _ _ (Free (Failed err)) =
+--   pure (Left err)
 
-evalFreeT ::
-  Monad m =>
-  CommandExec m ->
-  CmdBuffer ->
-  FreeT TmuxThunk m a ->
-  m (Either TmuxError a)
-evalFreeT exec s (FreeT ma) = do
-  inner <- ma
-  evalFreeF exec s inner
+-- evalFreeT ::
+--   Monad m =>
+--   CommandExec m ->
+--   CmdBuffer ->
+--   FreeT TmuxThunk m a ->
+--   m (Either TmuxError a)
+-- evalFreeT exec s (FreeT ma) = do
+--   inner <- ma
+--   evalFreeF exec s inner
diff --git a/lib/Chiasma/Monad/IndividualProcess.hs b/lib/Chiasma/Monad/IndividualProcess.hs
deleted file mode 100644
--- a/lib/Chiasma/Monad/IndividualProcess.hs
+++ /dev/null
@@ -1,40 +0,0 @@
-module Chiasma.Monad.IndividualProcess where
-
-import Control.Monad.Free (Free(..))
-
-import Chiasma.Api.Class (TmuxApi(..))
-import Chiasma.Data.Cmd (Cmd(..), Cmds(..))
-import Chiasma.Data.TmuxError (TmuxError)
-import Chiasma.Data.TmuxThunk (TmuxThunk(..))
-
-type TmuxProg = Free TmuxThunk
-
-newtype TmuxState = TmuxState [Cmd]
-
-instance Default TmuxState where
-  def = TmuxState def
-
-interpret ::
-  (TmuxApi m api, MonadDeepError e TmuxError m) =>
-  TmuxState ->
-  api ->
-  TmuxProg a ->
-  m a
-interpret (TmuxState cmds) api (Pure a) = a <$ runCommands api (const $ Right ()) (Cmds cmds)
-interpret (TmuxState cmds) api (Free (Read cmd decode next)) = do
-  a <- runCommands api decode $ Cmds (cmd : cmds)
-  interpret def api (next a)
-interpret (TmuxState cmds) api (Free (Write cmd next)) =
-  interpret (TmuxState (cmd : cmds)) api (next ())
-interpret (TmuxState cmds) api (Free (Flush next)) = do
-  _ <- runCommands api (const $ Right ()) (Cmds cmds)
-  interpret def api (next ())
-interpret _ _ (Free (Failed err)) =
-  throwHoist err
-
-runTmux ::
-  (TmuxApi m api, MonadDeepError e TmuxError m) =>
-  api ->
-  TmuxProg a ->
-  m a
-runTmux = interpret def
diff --git a/lib/Chiasma/Monad/Stream.hs b/lib/Chiasma/Monad/Stream.hs
deleted file mode 100644
--- a/lib/Chiasma/Monad/Stream.hs
+++ /dev/null
@@ -1,80 +0,0 @@
-module Chiasma.Monad.Stream where
-
-import Conduit (ConduitT, Flush(..), runConduit, sinkList, yield, yieldMany, (.|))
-import Control.Monad.Trans.Free (FreeT(..))
-import qualified Data.Conduit.Combinators as Conduit (drop, take)
-
-import Chiasma.Api.Class (TmuxApi(..))
-import Chiasma.Codec.Decode (TmuxDecodeError)
-import Chiasma.Data.Cmd (Cmd(..), Cmds(..))
-import Chiasma.Data.TmuxError (TmuxError)
-import qualified Chiasma.Data.TmuxError as TmuxError (TmuxError(ProcessFailed, DecodingFailed, CommandFailed))
-import Chiasma.Data.TmuxThunk (TmuxThunk)
-import Chiasma.Monad.EvalFreeT (evalFreeT)
-import Chiasma.Native.StreamParse (TmuxOutputBlock)
-import qualified Chiasma.Native.StreamParse as TmuxOutputBlock (TmuxOutputBlock(..))
-
-type TmuxProg = FreeT TmuxThunk
-
-type WriteCmd m =
-  ConduitT (Flush Cmd) Void m ()
-
-type ReadOutput m =
-  ConduitT () TmuxOutputBlock m ()
-
-handleProcessOutput ::
-  Cmds ->
-  (Text -> Either TmuxDecodeError a) ->
-  [TmuxOutputBlock] ->
-  Either TmuxError [a]
-handleProcessOutput cs@(Cmds cmds) _ output | length output < length cmds =
-  Left $ TmuxError.ProcessFailed cs "tmux terminated before all commands were processed"
-handleProcessOutput cmds decode output = do
-  readOutput <- foldl validate (Right []) output
-  traverse decode' readOutput
-  where
-    validate (Left err) _ = Left err
-    validate _ (TmuxOutputBlock.Success a) = Right a
-    validate _ (TmuxOutputBlock.Error a) = Left $ TmuxError.CommandFailed cmds a
-    decode' outputLine =
-      mapLeft (TmuxError.DecodingFailed cmds outputLine) . decode $ outputLine
-
-executeCommands ::
-  MonadIO m =>
-  WriteCmd m ->
-  ReadOutput m ->
-  (Text -> Either TmuxDecodeError a) ->
-  Cmds ->
-  m (Either TmuxError [a])
-executeCommands writeCmd readOutput decode cs@(Cmds cmds) = do
-  output <- runConduit $ do
-    yieldMany (Chunk <$> reverse cmds) .| writeCmd
-    yield Flush .| writeCmd
-    readOutput .| Conduit.take (length cmds) .| sinkList
-  return $ handleProcessOutput cs decode output
-
-runTmuxProg ::
-  MonadIO m =>
-  TmuxProg m a ->
-  WriteCmd m ->
-  ReadOutput m ->
-  m (Either TmuxError a)
-runTmuxProg prog writeCmd readOutput = do
-  runConduit $ readOutput .| Conduit.drop 1
-  evalFreeT (executeCommands writeCmd readOutput) def prog
-
-runTmuxE ::
-  (MonadIO m, TmuxApi m api) =>
-  api ->
-  TmuxProg m a ->
-  m (Either TmuxError a)
-runTmuxE api prog =
-  withTmux api (runTmuxProg prog)
-
-runTmux ::
-  (MonadIO m, MonadDeepError e TmuxError m, TmuxApi m api) =>
-  api ->
-  TmuxProg m a ->
-  m a
-runTmux api =
-  hoistEither <=< runTmuxE api
diff --git a/lib/Chiasma/Monad/Tmux.hs b/lib/Chiasma/Monad/Tmux.hs
--- a/lib/Chiasma/Monad/Tmux.hs
+++ b/lib/Chiasma/Monad/Tmux.hs
@@ -1,49 +1,49 @@
 module Chiasma.Monad.Tmux where
 
-import Control.Monad.Free (liftF)
-import Control.Monad.Free.Class (MonadFree)
+-- import Control.Monad.Free (liftF)
+-- import Control.Monad.Free.Class (MonadFree)
 
-import Chiasma.Codec (TmuxCodec, TmuxQuery(unQ))
-import qualified Chiasma.Codec as TmuxCodec (TmuxCodec(decode, query))
-import Chiasma.Data.Cmd (cmd)
-import Chiasma.Data.TmuxError (TmuxError(InvalidOutput))
-import Chiasma.Data.TmuxThunk (TmuxThunk(..))
+-- import Chiasma.Codec (TmuxCodec, TmuxQuery(unQ))
+-- import qualified Chiasma.Codec as TmuxCodec (TmuxCodec(decode, query))
+-- import Chiasma.Data.Cmd (cmd)
+-- import Chiasma.Data.TmuxError (TmuxError(InvalidOutput))
+-- import Chiasma.Data.TmuxThunk (TmuxThunk(..))
 
-read :: ∀ a m . (TmuxCodec a, MonadFree TmuxThunk m) => Text -> [Text] -> m [a]
-read name args =
-  liftF $ Read (cmd name (args <> formatArgs)) TmuxCodec.decode id
-  where
-    formatArgs = ["-F", "'", unQ (TmuxCodec.query @a), "'"]
+-- read :: ∀ a m . (TmuxCodec a, MonadFree TmuxThunk m) => Text -> [Text] -> m [a]
+-- read name args =
+--   liftF $ Read (cmd name (args <> formatArgs)) TmuxCodec.decode id
+--   where
+--     formatArgs = ["-F", "'", unQ (TmuxCodec.query @a), "'"]
 
-unsafeReadOne :: ∀ a m . (TmuxCodec a, MonadFree TmuxThunk m) => Text -> [Text] -> m a
-unsafeReadOne name args = do
-  outputs <- read name args
-  case outputs of
-    [a] -> return a
-    [] -> liftF $ Failed $ InvalidOutput "no data" (name <> unwords args)
-    _ -> liftF $ Failed $ InvalidOutput "multiple outputs for `unsafeReadOne`" (name <> unwords args)
+-- unsafeReadOne :: ∀ a m . (TmuxCodec a, MonadFree TmuxThunk m) => Text -> [Text] -> m a
+-- unsafeReadOne name args = do
+--   outputs <- read name args
+--   case outputs of
+--     [a] -> pure a
+--     [] -> liftF $ Failed $ InvalidOutput "no data" (name <> unwords args)
+--     _ -> liftF $ Failed $ InvalidOutput "multiple outputs for `unsafeReadOne`" (name <> unwords args)
 
-readFirst :: ∀ a m . (TmuxCodec a, MonadFree TmuxThunk m) => Text -> [Text] -> m (Maybe a)
-readFirst name args = do
-  outputs <- read name args
-  return $ case outputs of
-    (a : _) -> Just a
-    [] -> Nothing
+-- readFirst :: ∀ a m . (TmuxCodec a, MonadFree TmuxThunk m) => Text -> [Text] -> m (Maybe a)
+-- readFirst name args = do
+--   outputs <- read name args
+--   pure $ case outputs of
+--     (a : _) -> Just a
+--     [] -> Nothing
 
-unsafeReadFirst :: ∀ a m . (TmuxCodec a, MonadFree TmuxThunk m) => Text -> [Text] -> m a
-unsafeReadFirst name args = do
-  mayFirst <- readFirst name args
-  case mayFirst of
-    (Just a) -> return a
-    Nothing -> liftF $ Failed $ InvalidOutput "no data" (name <> unwords args)
+-- unsafeReadFirst :: ∀ a m . (TmuxCodec a, MonadFree TmuxThunk m) => Text -> [Text] -> m a
+-- unsafeReadFirst name args = do
+--   mayFirst <- readFirst name args
+--   case mayFirst of
+--     (Just a) -> pure a
+--     Nothing -> liftF $ Failed $ InvalidOutput "no data" (name <> unwords args)
 
-readRaw :: ∀ m . (MonadFree TmuxThunk m) => Text -> [Text] -> m [Text]
-readRaw name args =
-  liftF $ Read (cmd name args) Right id
+-- readRaw :: ∀ m . (MonadFree TmuxThunk m) => Text -> [Text] -> m [Text]
+-- readRaw name args =
+--   liftF $ Read (cmd name args) Right id
 
-write :: MonadFree TmuxThunk m => Text -> [Text] -> m ()
-write name args = liftF $ Write (cmd name args) id
+-- write :: MonadFree TmuxThunk m => Text -> [Text] -> m ()
+-- write name args = liftF $ Write (cmd name args) id
 
-flush :: MonadFree TmuxThunk m => m ()
-flush =
-  liftF $ Flush id
+-- flush :: MonadFree TmuxThunk m => m ()
+-- flush =
+--   liftF $ Flush id
diff --git a/lib/Chiasma/Native/Api.hs b/lib/Chiasma/Native/Api.hs
deleted file mode 100644
--- a/lib/Chiasma/Native/Api.hs
+++ /dev/null
@@ -1,67 +0,0 @@
-{-# LANGUAGE UndecidableInstances #-}
-
-module Chiasma.Native.Api where
-
-import Chiasma.Data.TmuxError (TmuxError)
-import Conduit (ConduitT, Flush, mapC, (.|))
-import Control.Monad.Catch (MonadMask)
-import qualified Control.Monad.Catch as Catch (bracket)
-import Data.Conduit.Process.Typed (createSource)
-import qualified Data.Text as Text (unwords)
-import System.Process.Typed (
-  Process,
-  ProcessConfig,
-  getStdin,
-  getStdout,
-  proc,
-  setStdin,
-  setStdout,
-  startProcess,
-  stopProcess,
-  )
-import Text.ParserCombinators.Parsec ()
-
-import Chiasma.Api.Class (TmuxApi(..))
-import Chiasma.Data.Cmd (Cmd(..), CmdArgs(..), CmdName(..))
-import Chiasma.Data.Conduit (createSinkFlush)
-import Chiasma.Native.Process (nativeTmuxProcess, socketArg)
-import Chiasma.Native.StreamParse (parseConduit)
-
-newtype TmuxNative =
-  TmuxNative { tmuxServerSocket :: Maybe FilePath }
-  deriving Show
-
-formatCmd :: Cmd -> ByteString
-formatCmd (Cmd (CmdName name) (CmdArgs args)) =
-  encodeUtf8 . Text.unwords $ name : args ++ ["\n"]
-
-tmuxProcessConfig ::
-  MonadIO m =>
-  Maybe FilePath ->
-  ProcessConfig (ConduitT (Flush ByteString) Void m ()) (ConduitT () ByteString m ()) ()
-tmuxProcessConfig sock =
-  cons args
-  where
-    cons =
-      setStdin createSinkFlush . setStdout createSource . proc "tmux"
-    args =
-      toString <$> (socketArg sock ++ ["-C", "attach"])
-
-withProcess :: (MonadIO m, MonadMask m)
-            => ProcessConfig stdin stdout stderr
-            -> (Process stdin stdout stderr -> m a)
-            -> m a
-withProcess config = Catch.bracket (startProcess config) stopProcess
-
-instance (MonadIO m, MonadDeepError e TmuxError m, MonadMask m) => TmuxApi m TmuxNative where
-  runCommands (TmuxNative socket) =
-    nativeTmuxProcess socket
-
-  withTmux (TmuxNative socket) f =
-    withProcess (tmuxProcessConfig socket) handler
-    where
-      handler prc =
-        let
-          stdin' = mapC (fmap formatCmd) .| getStdin prc
-          stdout' = getStdout prc .| parseConduit
-        in f stdin' stdout'
diff --git a/lib/Chiasma/Native/Parse.hs b/lib/Chiasma/Native/Parse.hs
--- a/lib/Chiasma/Native/Parse.hs
+++ b/lib/Chiasma/Native/Parse.hs
@@ -1,7 +1,7 @@
 module Chiasma.Native.Parse where
 
 import qualified Data.Text as T (pack)
-import Prelude hiding (many)
+import Prelude hiding (many, try)
 import Text.Parsec (
   ParseError,
   many,
diff --git a/lib/Chiasma/Native/Process.hs b/lib/Chiasma/Native/Process.hs
deleted file mode 100644
--- a/lib/Chiasma/Native/Process.hs
+++ /dev/null
@@ -1,57 +0,0 @@
-module Chiasma.Native.Process where
-
-import qualified Data.ByteString.Lazy as Lazy (ByteString)
-import Data.ByteString.Lazy.Internal (unpackChars)
-import qualified Data.Text as Text (intercalate, lines, pack, unwords)
-import System.Exit (ExitCode(ExitSuccess))
-import System.Process.Typed (ProcessConfig, byteStringInput, proc, readProcessStdout, setStdin)
-
-import Chiasma.Codec.Decode (TmuxDecodeError)
-import Chiasma.Data.Cmd (Cmd(..), CmdArgs(..), CmdName(..), Cmds(..))
-import Chiasma.Data.TmuxError (TmuxError)
-import qualified Chiasma.Data.TmuxError as TmuxError (
-  TmuxError(OutputParsingFailed, NoOutput, ProcessFailed, DecodingFailed),
-  )
-import Chiasma.Native.Parse (resultLines)
-
-cmdBytes :: [Text] -> Lazy.ByteString
-cmdBytes cmds = encodeUtf8 $ Text.intercalate "\n" $ reverse $ "" : cmds
-
-socketArg :: Maybe FilePath -> [Text]
-socketArg (Just socket) = ["-S", toText socket]
-socketArg Nothing = []
-
-tmuxProcessConfig :: Maybe FilePath -> [Text] -> ProcessConfig () () ()
-tmuxProcessConfig socket cmds =
-  cons args
-  where
-  cons =
-    setStdin (byteStringInput $ cmdBytes cmds) . proc "tmux"
-  args =
-    toString <$> (socketArg socket <> ["-C", "attach"])
-
-handleProcessOutput :: Cmds -> ExitCode -> (Text -> Either TmuxDecodeError a) -> Text -> Either TmuxError [a]
-handleProcessOutput cmds ExitSuccess decode out = do
-  outputs <- mapLeft (TmuxError.OutputParsingFailed cmds (Text.lines out)) $ resultLines out
-  case reverse outputs of
-    output : _ -> traverse decode' output
-    _ -> Left $ TmuxError.NoOutput cmds
-  where
-    decode' = mapLeft (TmuxError.DecodingFailed cmds out) . decode
-handleProcessOutput cmds _ _ out =
-  Left $ TmuxError.ProcessFailed cmds out
-
-formatCmd :: Cmd -> Text
-formatCmd (Cmd (CmdName name) (CmdArgs args)) =
-  Text.unwords $ name : args
-
-nativeTmuxProcess ::
-  (MonadIO m, MonadDeepError e TmuxError m) =>
-  Maybe FilePath ->
-  (Text -> Either TmuxDecodeError a) ->
-  Cmds ->
-  m [a]
-nativeTmuxProcess socket decode cmds@(Cmds cmds') = do
-  let cmdLines = fmap formatCmd cmds'
-  (code, out) <- readProcessStdout $ tmuxProcessConfig socket cmdLines
-  hoistEither $ handleProcessOutput cmds code decode $ Text.pack $ unpackChars out
diff --git a/lib/Chiasma/Native/StreamParse.hs b/lib/Chiasma/Native/StreamParse.hs
deleted file mode 100644
--- a/lib/Chiasma/Native/StreamParse.hs
+++ /dev/null
@@ -1,65 +0,0 @@
-module Chiasma.Native.StreamParse(
-  parseConduit,
-  parseBlocks,
-  TmuxOutputBlock(..),
-) where
-
-import Conduit (ConduitT, mapC, (.|))
-import Control.Monad.Catch (MonadThrow)
-import Data.Attoparsec.ByteString (Parser)
-import Data.Conduit.Attoparsec (conduitParser)
-import qualified Data.Text as T (pack)
-import Text.Parser.Char (CharParsing, anyChar, newline, string)
-import Text.Parser.Combinators (choice, manyTill, notFollowedBy, skipMany, try)
-import Text.Parser.LookAhead (LookAheadParsing, lookAhead)
-
-data End =
-  EndSuccess
-  |
-  EndError
-  deriving (Eq, Show)
-
-data TmuxOutputBlock =
-  Success [Text]
-  |
-  Error [Text]
-  deriving (Eq, Show)
-
-tillEol :: (Alternative m, CharParsing m) => m Text
-tillEol = T.pack <$> manyTill anyChar newline
-
-beginLine :: (Alternative m, CharParsing m, Monad m) => m ()
-beginLine = void $ string "%begin" >> tillEol
-
-endLine :: (Alternative m, CharParsing m) => m End
-endLine = do
-  end <- choice [EndSuccess <$ string "%end", EndError <$ string "%error"]
-  _ <- tillEol
-  return end
-
-notBeginLine :: (Alternative m, CharParsing m, Monad m) => m ()
-notBeginLine = void $ notFollowedBy (string "%begin") >> tillEol
-
--- |Parse a sequence of lines between a %start and a %end line.
--- Tmux pads output lines with a single space on both sides, so strip those if the leading one is present.
-parseBlock :: (Alternative m, CharParsing m, Monad m, LookAheadParsing m) => m TmuxOutputBlock
-parseBlock = do
-  _ <- skipMany notBeginLine
-  _ <- beginLine
-  dataLines <- manyTill tillEol $ try $ lookAhead endLine
-  end <- endLine
-  return $ case end of
-    EndSuccess -> Success dataLines
-    EndError -> Error dataLines
-
-parseBlocks :: (Alternative m, CharParsing m, Monad m, LookAheadParsing m) => m [TmuxOutputBlock]
-parseBlocks = do
-  result <- many (try parseBlock)
-  skipMany tillEol
-  return result
-
-parser :: Parser TmuxOutputBlock
-parser = parseBlock
-
-parseConduit :: MonadThrow m => ConduitT ByteString TmuxOutputBlock m ()
-parseConduit = conduitParser parser .| mapC snd
diff --git a/lib/Chiasma/Native/TmuxOutputBlock.hs b/lib/Chiasma/Native/TmuxOutputBlock.hs
new file mode 100644
--- /dev/null
+++ b/lib/Chiasma/Native/TmuxOutputBlock.hs
@@ -0,0 +1,46 @@
+module Chiasma.Native.TmuxOutputBlock where
+
+import Data.Attoparsec.ByteString (Parser)
+import Data.ByteString.Internal (packChars)
+import Prelude hiding (try)
+import Text.Parser.Char (CharParsing, anyChar, newline, string)
+import Text.Parser.Combinators (choice, manyTill, notFollowedBy, skipMany, try)
+import Text.Parser.LookAhead (LookAheadParsing, lookAhead)
+
+import Chiasma.Data.TmuxOutputBlock (End (EndError, EndSuccess), TmuxOutputBlock (Error, Success))
+
+tillEol :: (Alternative m, CharParsing m) => m Text
+tillEol = decodeUtf8 . packChars <$> manyTill anyChar newline
+
+beginLine :: (Alternative m, CharParsing m, Monad m) => m ()
+beginLine = void $ string "%begin" >> tillEol
+
+endLine :: (Alternative m, CharParsing m) => m End
+endLine = do
+  end <- choice [EndSuccess <$ string "%end", EndError <$ string "%error"]
+  _ <- tillEol
+  pure end
+
+notBeginLine :: (Alternative m, CharParsing m, Monad m) => m ()
+notBeginLine = void $ notFollowedBy (string "%begin") >> tillEol
+
+-- |Parse a sequence of lines between a %start and a %end line.
+-- Tmux pads output lines with a single space on both sides, so strip those if the leading one is present.
+parseBlock :: (Alternative m, CharParsing m, Monad m, LookAheadParsing m) => m TmuxOutputBlock
+parseBlock = do
+  _ <- skipMany notBeginLine
+  _ <- beginLine
+  dataLines <- manyTill tillEol $ try (lookAhead endLine)
+  end <- endLine
+  pure $ case end of
+    EndSuccess -> Success dataLines
+    EndError -> Error dataLines
+
+parseBlocks :: (Alternative m, CharParsing m, Monad m, LookAheadParsing m) => m [TmuxOutputBlock]
+parseBlocks = do
+  result <- many (try parseBlock)
+  skipMany tillEol
+  pure result
+
+parser :: Parser TmuxOutputBlock
+parser = parseBlock
diff --git a/lib/Chiasma/Pack.hs b/lib/Chiasma/Pack.hs
--- a/lib/Chiasma/Pack.hs
+++ b/lib/Chiasma/Pack.hs
@@ -1,61 +1,64 @@
 module Chiasma.Pack where
 
-import qualified Chiasma.Codec.Data as Codec (Window(Window))
+import Control.Lens (mapMOf_)
+import qualified Data.List.NonEmpty as NonEmpty (reverse, toList)
+import qualified Data.Set as Set (fromList, size)
+import Prettyprinter (Doc, line, pretty, (<+>))
+
+import qualified Chiasma.Codec.Data.Window as Codec (Window (Window))
 import Chiasma.Command.Pane (movePane, resizePane)
+import Chiasma.Data.Axis (Axis (Horizontal, Vertical))
 import Chiasma.Data.TmuxId (PaneId)
-import Chiasma.Data.TmuxThunk (TmuxThunk)
 import Chiasma.Data.Views (Views)
-import Chiasma.Data.WindowState (WindowState(..))
-import Chiasma.Ui.Data.Measure (MLayout(..), MPane(..), MeasureTree, MeasureTreeSub, Measured(Measured))
-import Chiasma.Ui.Data.Tree (Node(Sub, Leaf), Tree(Tree))
+import Chiasma.Data.WindowState (WindowState (..))
+import Chiasma.Effect.TmuxApi (Tmux)
+import Chiasma.Ui.Data.Measure (MLayout (..), MPane (..), MeasureTree, MeasureTreeSub, Measured (Measured))
+import Chiasma.Ui.Data.Tree (Node (Leaf, Sub), Tree (Tree))
 import qualified Chiasma.Ui.Data.Tree as Tree (subTree)
 import Chiasma.Ui.Measure (measureTree)
 import Chiasma.View (viewsLog)
-import Control.Lens (each, mapMOf_)
-import Control.Monad.Free.Class (MonadFree)
-import qualified Data.List.NonEmpty as NonEmpty (reverse, toList)
-import qualified Data.Set as Set (fromList, size)
-import Data.Text.Prettyprint.Doc (Doc, line, pretty, (<+>))
 
 packPane ::
-  (MonadDeepState s Views m, MonadFree TmuxThunk m) =>
+  Members [AtomicState Views, Tmux] r =>
   PaneId ->
-  Bool ->
+  Axis ->
   PaneId ->
-  m ()
-packPane refId vertical paneId =
-  when (paneId /= refId) $ movePane paneId refId vertical
+  Sem r ()
+packPane refId axis paneId =
+  when (paneId /= refId) do
+    movePane paneId refId axis
 
 positionView ::
-  (MonadDeepState s Views m, MonadFree TmuxThunk m) =>
-  Bool ->
+  Members [AtomicState Views, Tmux] r =>
+  Axis ->
   PaneId ->
   MeasureTreeSub ->
-  m ()
-positionView vertical refId =
+  Sem r ()
+positionView axis refId =
   position
   where
     position (Sub (Tree (Measured _ (MLayout layoutRef _ _ _)) _)) =
-      packPane refId vertical layoutRef
+      packPane refId axis layoutRef
     position (Leaf (Measured _ (MPane paneId _ _))) =
-      packPane refId vertical paneId
+      packPane refId axis paneId
 
-describeVertical :: Bool -> Doc a
-describeVertical True = "vertically"
-describeVertical False = "horizontally"
+describeAxis :: Axis -> Doc a
+describeAxis = \case
+  Vertical -> "vertically"
+  Horizontal -> "horizontally"
 
 resizeView ::
-  MonadDeepState s Views m =>
-  MonadFree TmuxThunk m =>
-  Bool ->
+  Members [AtomicState Views, Tmux] r =>
+  Axis ->
   MeasureTreeSub ->
-  m ()
-resizeView vertical (Sub (Tree (Measured size (MLayout refId _ _ _)) _)) = do
-  viewsLog $ "resizing layout with ref" <+> pretty refId <+> "to" <+> pretty size <+> describeVertical vertical
-  resizePane refId vertical size
-resizeView vertical (Leaf (Measured size (MPane paneId _ _))) = do
-  viewsLog $ "resizing pane" <+> pretty paneId <+> "to" <+> pretty size <+> describeVertical vertical
-  resizePane paneId vertical size
+  Sem r ()
+resizeView axis = \case
+  Sub (Tree (Measured size (MLayout refId _ _ _)) _) -> do
+    viewsLog ("resizing layout with ref" <+> pretty refId <+> "to" <+> pretty size <+> describeAxis axis)
+    resizePane refId axis size
+  Leaf (Measured size (MPane paneId _ _)) -> do
+    viewsLog $ "resizing pane" <+> pretty paneId <+> "to" <+> pretty size <+> describeAxis axis
+    resizePane paneId axis size
 
 needPositioning ::
   NonEmpty MeasureTreeSub ->
@@ -83,26 +86,26 @@
       offPos
 
 packTree ::
-  (MonadDeepState s Views m, MonadFree TmuxThunk m) =>
+  Members [AtomicState Views, Tmux] r =>
   MeasureTree ->
-  m ()
+  Sem r ()
 packTree =
   pack
   where
-    pack (Tree (Measured _ (MLayout ref _ _ vertical)) sub) = do
+    pack (Tree (Measured _ (MLayout ref _ _ axis)) sub) = do
       when needPos runPos
       mapMOf_ (each . Tree.subTree) pack sub
-      traverse_ (resizeView vertical) sub
+      traverse_ (resizeView axis) sub
       where
         needPos = needPositioning sub
         runPos = do
           viewsLog $ "repositioning views" <+> pretty sub
-          traverse_ (positionView vertical ref) (NonEmpty.reverse sub)
+          traverse_ (positionView axis ref) (NonEmpty.reverse sub)
 
 packWindow ::
-  (MonadDeepState s Views m, MonadFree TmuxThunk m) =>
+  Members [AtomicState Views, Tmux] r =>
   WindowState ->
-  m ()
+  Sem r ()
 packWindow (WindowState (Codec.Window _ width height) _ _ tree _) = do
   let measures = measureTree tree width height
   viewsLog $ "measured tree:" <> line <> pretty measures
diff --git a/lib/Chiasma/Pane.hs b/lib/Chiasma/Pane.hs
--- a/lib/Chiasma/Pane.hs
+++ b/lib/Chiasma/Pane.hs
@@ -2,13 +2,16 @@
 
 import Chiasma.Data.Ident (Ident)
 import Chiasma.Data.TmuxId (PaneId)
-import qualified Chiasma.Data.View as Tmux (View(View))
+import qualified Chiasma.Data.View as Tmux (View (View))
 import Chiasma.Data.Views (Views)
 import qualified Chiasma.View as Views (insertPane)
 
-addPane :: MonadDeepState s Views m => Ident -> m (Tmux.View PaneId)
-addPane ident = do
-  modify $ Views.insertPane pane
-  return pane
+addPane ::
+  Member (AtomicState Views) r =>
+  Ident ->
+  Sem r (Tmux.View PaneId)
+addPane ident =
+  pane <$ atomicModify' (Views.insertPane pane)
   where
-    pane = Tmux.View ident Nothing
+    pane =
+      Tmux.View ident Nothing
diff --git a/lib/Chiasma/Path.hs b/lib/Chiasma/Path.hs
new file mode 100644
--- /dev/null
+++ b/lib/Chiasma/Path.hs
@@ -0,0 +1,16 @@
+module Chiasma.Path where
+
+import qualified Data.Text as Text
+import Path (Path, toFilePath)
+
+pathText :: Path b t -> Text
+pathText =
+  toText . toFilePath
+{-# inline pathText #-}
+
+pathText' :: Path b t -> Text
+pathText' p =
+  case pathText p of
+    "/" -> "/"
+    t -> Text.dropWhileEnd (== '/') t
+{-# inline pathText' #-}
diff --git a/lib/Chiasma/Prelude.hs b/lib/Chiasma/Prelude.hs
deleted file mode 100644
--- a/lib/Chiasma/Prelude.hs
+++ /dev/null
@@ -1,71 +0,0 @@
-{-# LANGUAGE NoImplicitPrelude #-}
-
-module Chiasma.Prelude (
-  module Cornea,
-  module Data.Default,
-  module Data.Foldable,
-  module Relude,
-  dbg,
-  dbgs,
-  dbgm,
-  makeClassy,
-  mapLeft,
-  tuple,
-  undefined,
-  unit,
-  unsafeLogAnd,
-  unsafeLogS,
-  unsafeLogSAnd,
-  (<$$>),
-) where
-
-import Control.Lens (makeClassy)
-import Cornea
-import Data.Default (Default(def))
-import Data.Either.Combinators (mapLeft)
-import Data.Foldable (foldl, traverse_)
-import Data.Functor.Syntax ((<$$>))
-import GHC.Err (undefined)
-import GHC.IO.Unsafe (unsafePerformIO)
-import Relude hiding (Type, ask, asks, get, gets, hoistEither, hoistMaybe, local, modify, put, state, undefined)
-
-dbg :: Monad m => Text -> m ()
-dbg msg = do
-  () <- return $ unsafePerformIO (putStrLn (toString msg))
-  return ()
-
-dbgs :: Monad m => Show a => a -> m ()
-dbgs =
-  dbg . show
-
-dbgm :: Monad m => Show a => m a -> m a
-dbgm ma = do
-  a <- ma
-  dbgs a
-  return a
-
-unit ::
-  Applicative f =>
-  f ()
-unit =
-  pure ()
-
-tuple ::
-  Applicative f =>
-  f a ->
-  f b ->
-  f (a, b)
-tuple fa fb =
-  (,) <$> fa <*> fb
-
-unsafeLogSAnd :: Show a => a -> b -> b
-unsafeLogSAnd a b =
-  unsafePerformIO $ print a >> return b
-
-unsafeLogAnd :: Text -> b -> b
-unsafeLogAnd a b =
-  unsafePerformIO $ putStrLn (toString a) >> return b
-
-unsafeLogS :: Show a => a -> a
-unsafeLogS a =
-  unsafePerformIO $ print a >> return a
diff --git a/lib/Chiasma/Render.hs b/lib/Chiasma/Render.hs
--- a/lib/Chiasma/Render.hs
+++ b/lib/Chiasma/Render.hs
@@ -1,41 +1,47 @@
 module Chiasma.Render where
 
-import qualified Chiasma.Codec.Data as Codec (Window(Window))
+import Path (Abs, Dir, Path)
+import Prettyprinter (line, pretty)
+
+import Chiasma.Codec.Data.Pane (Pane)
+import qualified Chiasma.Codec.Data.Window as Codec (Window (Window))
+import Chiasma.Data.CodecError (CodecError)
 import Chiasma.Data.Ident (Ident, identText)
+import Chiasma.Data.Panes (TmuxPanes)
+import qualified Chiasma.Data.RenderError as RenderError
 import Chiasma.Data.RenderError (RenderError)
-import Chiasma.Data.TmuxThunk (TmuxThunk)
 import Chiasma.Data.Views (Views)
+import Chiasma.Effect.TmuxApi (Tmux)
 import Chiasma.Pack (packWindow)
 import Chiasma.Session (ensureSession, findOrCreateSession)
 import Chiasma.Ui.Data.RenderableTree (RenderableTree)
 import Chiasma.Ui.Data.View (ViewTree)
 import Chiasma.View (viewsLog)
 import Chiasma.Window (ensureView, ensureWindow, findOrCreateWindow, windowState)
-import Control.Monad.Error.Class (MonadError)
-import Control.Monad.Free.Class (MonadFree)
-import Data.Text.Prettyprint.Doc (line, pretty)
 
 renderTree ::
-  (MonadDeepState s Views m, MonadFree TmuxThunk m) =>
+  Members [TmuxPanes Pane, AtomicState Views, Tmux] r =>
   Ident ->
   Codec.Window ->
   RenderableTree ->
-  m ()
+  Sem r ()
 renderTree windowIdent window tree = do
   viewsLog $ pretty ("rendering tree in window " <> identText windowIdent <> ":") <> line <> pretty tree
   wState <- windowState windowIdent window tree
   packWindow wState
 
 render ::
-  (MonadDeepState s Views m, MonadFree TmuxThunk m, MonadError RenderError m) =>
-  FilePath ->
+  Members [TmuxPanes Pane !! CodecError, AtomicState Views, Tmux !! CodecError, Stop RenderError] r =>
+  Path Abs Dir ->
   Ident ->
   Ident ->
   ViewTree ->
-  m ()
+  Sem r ()
 render cwd sessionIdent windowIdent tree = do
-  initialSession <- findOrCreateSession sessionIdent
-  initialWindow <- findOrCreateWindow windowIdent
-  (sid, newSessionWid) <- ensureSession initialSession initialWindow
-  window@(Codec.Window windowId _ _) <- ensureWindow sid initialWindow newSessionWid tree
-  traverse_ (renderTree windowIdent window) =<< ensureView cwd windowId tree
+  resumeHoist @_ @(TmuxPanes _) RenderError.Codec do
+    resumeHoist @_ @Tmux RenderError.Codec do
+      initialSession <- findOrCreateSession sessionIdent
+      initialWindow <- findOrCreateWindow windowIdent
+      (sid, newSessionWid) <- mapStop RenderError.Layout (ensureSession initialSession initialWindow)
+      window@(Codec.Window windowId _ _) <- ensureWindow sid initialWindow newSessionWid tree
+      traverse_ (renderTree windowIdent window) =<< ensureView cwd windowId tree
diff --git a/lib/Chiasma/Session.hs b/lib/Chiasma/Session.hs
--- a/lib/Chiasma/Session.hs
+++ b/lib/Chiasma/Session.hs
@@ -1,49 +1,46 @@
-{-# LANGUAGE FlexibleContexts #-}
-
 module Chiasma.Session where
 
-import Control.Monad.Free.Class (MonadFree)
-
-import qualified Chiasma.Codec.Data as Codec (Session(Session), Window(Window))
+import Exon (exon)
+import qualified Chiasma.Codec.Data.Session as Codec (Session (Session))
+import qualified Chiasma.Codec.Data.Window as Codec (Window (Window))
 import Chiasma.Command.Session (existingSessionId, newSession)
 import Chiasma.Command.Window (newSessionWindow)
 import Chiasma.Data.Ident (Ident)
+import Chiasma.Data.LayoutError (LayoutError)
 import Chiasma.Data.TmuxId (SessionId, WindowId)
-import Chiasma.Data.TmuxThunk (TmuxThunk)
-import qualified Chiasma.Data.View as Tmux (View(viewId, viewIdent), setViewId)
+import qualified Chiasma.Data.View as Tmux (View (viewId, viewIdent), setViewId)
 import Chiasma.Data.Views (Views)
+import Chiasma.Effect.TmuxApi (Tmux)
 import Chiasma.View (findOrCreateView, viewsLogS)
 import qualified Chiasma.View as Views (insertSession, session, updateSession, updateWindow)
 
 findOrCreateSession ::
-  MonadDeepState s Views m =>
+  Member (AtomicState Views) r =>
   Ident ->
-  m (Tmux.View SessionId)
+  Sem r (Tmux.View SessionId)
 findOrCreateSession =
   findOrCreateView Views.session Views.insertSession
 
 spawnSession ::
-  MonadDeepState s Views m =>
-  MonadFree TmuxThunk m =>
+  Members [AtomicState Views, Tmux, Stop LayoutError] r =>
   Tmux.View SessionId ->
   Tmux.View WindowId ->
-  m (SessionId, WindowId)
+  Sem r (SessionId, WindowId)
 spawnSession session' window = do
-  Codec.Session sid <- newSession (Tmux.viewIdent session')
-  modify $ Views.updateSession $ Tmux.setViewId sid session'
+  Codec.Session sid _ <- newSession (Tmux.viewIdent session')
+  atomicModify' (Views.updateSession (Tmux.setViewId sid session'))
   Codec.Window wid _ _ <- newSessionWindow sid
-  modify $ Views.updateWindow $ Tmux.setViewId wid window
-  viewsLogS $ "spawned session " <> show session' <> " with id " <> show sid <> " and window id " <> show wid
-  return (sid, wid)
+  atomicModify' (Views.updateWindow (Tmux.setViewId wid window))
+  viewsLogS [exon|spawned session #{show session'} with id #{show sid} and window id #{show wid}|]
+  pure (sid, wid)
 
 ensureSession ::
-  MonadDeepState s Views m =>
-  MonadFree TmuxThunk m =>
+  Members [AtomicState Views, Tmux, Stop LayoutError] r =>
   Tmux.View SessionId ->
   Tmux.View WindowId ->
-  m (SessionId, Maybe WindowId)
+  Sem r (SessionId, Maybe WindowId)
 ensureSession session' window = do
   existing <- join <$> traverse existingSessionId (Tmux.viewId session')
   case existing of
-    Just sid -> return (sid, Nothing)
+    Just sid -> pure (sid, Nothing)
     Nothing -> second Just <$> spawnSession session' window
diff --git a/lib/Chiasma/Test/File.hs b/lib/Chiasma/Test/File.hs
deleted file mode 100644
--- a/lib/Chiasma/Test/File.hs
+++ /dev/null
@@ -1,31 +0,0 @@
-module Chiasma.Test.File(
-  tempDirIO,
-  tempDir,
-  fixture,
-) where
-
-import System.Directory (canonicalizePath, createDirectoryIfMissing, removePathForcibly)
-import System.FilePath ((</>))
-
-testDir :: Text -> IO FilePath
-testDir prefix = canonicalizePath $ "test" </> toString prefix
-
--- raises exception if cwd is not the package root so we don't damage anything
-tempDirIO :: Text -> FilePath -> IO FilePath
-tempDirIO prefix path = do
-  base <- testDir prefix
-  let dir = base </> "temp"
-  removePathForcibly dir
-  createDirectoryIfMissing False dir
-  let absPath = dir </> path
-  createDirectoryIfMissing True absPath
-  return absPath
-
-tempDir :: MonadIO m => Text -> FilePath -> m FilePath
-tempDir prefix path =
-  liftIO $ tempDirIO prefix path
-
-fixture :: MonadIO m => Text -> FilePath -> m FilePath
-fixture prefix path = do
-  base <- liftIO $ testDir prefix
-  return $ base </> "fixtures" </> path
diff --git a/lib/Chiasma/Test/Screenshot.hs b/lib/Chiasma/Test/Screenshot.hs
deleted file mode 100644
--- a/lib/Chiasma/Test/Screenshot.hs
+++ /dev/null
@@ -1,67 +0,0 @@
-module Chiasma.Test.Screenshot where
-
-import Control.Monad.Free.Class (MonadFree)
-import qualified Data.ByteString as ByteString (writeFile)
-import qualified Data.Text as Text (lines, unlines)
-import qualified Data.Text.Encoding as Text (encodeUtf8)
-import System.FilePath (takeDirectory, (</>))
-import UnliftIO.Directory (createDirectoryIfMissing, doesFileExist)
-
-import Chiasma.Command.Pane (capturePane)
-import Chiasma.Data.TmuxId (PaneId(PaneId))
-import Chiasma.Data.TmuxThunk (TmuxThunk)
-
-loadScreenshot :: MonadIO m => FilePath -> m (Maybe Text)
-loadScreenshot path =
-  ifM (doesFileExist path) (Just . toText <$> liftIO (readFile path)) (pure Nothing)
-
-storeScreenshot :: MonadIO m => FilePath -> [Text] -> m ()
-storeScreenshot path text = do
-  createDirectoryIfMissing True (takeDirectory path)
-  liftIO $ ByteString.writeFile path (Text.encodeUtf8 . Text.unlines $ text)
-
-takeScreenshot ::
-  MonadFree TmuxThunk m =>
-  MonadIO m =>
-  Int ->
-  m [Text]
-takeScreenshot =
-  capturePane . PaneId
-
-recordScreenshot ::
-  MonadFree TmuxThunk m =>
-  MonadIO m =>
-  FilePath ->
-  Int ->
-  m ()
-recordScreenshot path paneId = do
-  current <- takeScreenshot paneId
-  storeScreenshot path current
-
-testScreenshot ::
-  MonadFree TmuxThunk m =>
-  MonadIO m =>
-  FilePath ->
-  Int ->
-  m (Maybe ([Text], [Text]))
-testScreenshot path pane = do
-  current <- takeScreenshot pane
-  loadScreenshot path >>= check current
-  where
-    check current (Just existing) =
-      return $ Just (current, Text.lines existing)
-    check current Nothing =
-      Nothing <$ storeScreenshot path current
-
-screenshot ::
-  MonadFree TmuxThunk m =>
-  MonadIO m =>
-  Bool ->
-  FilePath ->
-  Text ->
-  Int ->
-  m (Maybe ([Text], [Text]))
-screenshot record storage name paneId =
-  if record then Nothing <$ recordScreenshot path paneId else testScreenshot path paneId
-  where
-    path = storage </> toString name
diff --git a/lib/Chiasma/Test/Tmux.hs b/lib/Chiasma/Test/Tmux.hs
deleted file mode 100644
--- a/lib/Chiasma/Test/Tmux.hs
+++ /dev/null
@@ -1,121 +0,0 @@
-module Chiasma.Test.Tmux where
-
-import Chiasma.Data.TmuxError (TmuxError)
-import Chiasma.Monad.Stream (runTmux)
-import qualified Chiasma.Monad.Tmux as Tmux (write)
-import Chiasma.Native.Api (TmuxNative(..))
-import Control.Concurrent (threadDelay)
-import System.Directory (doesFileExist)
-import System.FilePath ((</>))
-import System.Posix.IO (fdToHandle)
-import System.Posix.Pty (Pty, createPty, resizePty)
-import qualified System.Posix.Signals as Signal (killProcess, signalProcess)
-import System.Posix.Terminal (openPseudoTerminal)
-import System.Process (getPid)
-import System.Process.Typed (
-  Process,
-  ProcessConfig,
-  StreamSpec,
-  proc,
-  setStderr,
-  setStdin,
-  setStdout,
-  unsafeProcessHandle,
-  useHandleClose,
-  withProcessWait,
-  )
-import UnliftIO (finally, throwString)
-import UnliftIO.Exception (tryAny)
-import UnliftIO.Temporary (withSystemTempDirectory)
-
-import Chiasma.Test.File (fixture)
-
-data Terminal = Terminal Handle Pty
-
-data TmuxTestConf =
-  TmuxTestConf {
-    ttcWidth :: Int,
-    ttcHeight :: Int,
-    ttcFontSize :: Int,
-    ttcGui :: Bool
-  }
-  deriving (Eq, Show)
-
-instance Default TmuxTestConf where
-  def = TmuxTestConf 240 61 18 True
-
-usleep :: MonadIO f => Double -> f ()
-usleep =
-  liftIO . threadDelay . round
-
-sleep :: MonadIO f => Double -> f ()
-sleep seconds =
-  usleep $ seconds * 1e6
-
-unsafeTerminal :: IO Terminal
-unsafeTerminal = do
-  (_, slave) <- openPseudoTerminal
-  mayPty <- createPty slave
-  handle <- fdToHandle slave
-  pty <- maybe (throwString "couldn't spawn pty") return mayPty
-  return $ Terminal handle pty
-
-urxvtArgs :: Int -> Int -> Int -> [Text]
-urxvtArgs width height fontSize =
-  ["-geometry", show width <> "x" <> show height, "-fn", "xft:monospace:size=" <> show fontSize, "-e", "tmux"]
-
-testTmuxProcessConfig :: TmuxTestConf -> FilePath -> FilePath -> Terminal -> IO (ProcessConfig () () ())
-testTmuxProcessConfig (TmuxTestConf width height fontSize gui) socket confFile (Terminal handle pty) = do
-  confFileExists <- doesFileExist confFile
-  resizePty pty (width, height)
-  let
-    stream :: StreamSpec st ()
-    stream = useHandleClose handle
-    stdio = setStdin stream . setStdout stream . setStderr stream
-    tmuxArgs = ["-S", toText socket, "-f", toText confFileArg]
-    confFileArg = if confFileExists then confFile else "/dev/null"
-    prc =
-      if gui
-      then proc "urxvt" (toString <$> urxvtArgs width height fontSize ++ tmuxArgs)
-      else proc "tmux" (toString <$> tmuxArgs)
-  return $ stdio prc
-
-killPid :: Integral a => a -> IO ()
-killPid =
-  void . tryAny . Signal.signalProcess Signal.killProcess . fromIntegral
-
-killProcess :: TmuxNative -> Process () () () -> IO ()
-killProcess api prc = do
-  _ <- runExceptT @TmuxError $ runTmux api $ Tmux.write "kill-server" []
-  let handle = unsafeProcessHandle prc
-  mayPid <- getPid handle
-  maybe (return ()) killPid mayPid
-
--- FIXME find a way to wait for tmux deterministically instead of sleeping
--- if the first tmux control mode process from a TmuxProg runs before urxvt has started the server,
--- it will not use the test tmux.conf
--- maybe start tmux first, then urxvt?
-runAndKillTmux :: (TmuxNative -> IO a) -> TmuxNative -> Process () () () -> IO a
-runAndKillTmux thunk api prc = do
-  sleep 0.2
-  finally (thunk api) (killProcess api prc)
-
-withTestTmux :: TmuxTestConf -> (TmuxNative -> IO a) -> FilePath -> IO a
-withTestTmux tConf thunk tempDir = do
-  let socket = tempDir </> "tmux_socket"
-  conf <- fixture "u" "tmux.conf"
-  terminal <- unsafeTerminal
-  pc <- testTmuxProcessConfig tConf socket conf terminal
-  withProcessWait pc $ runAndKillTmux thunk (TmuxNative $ Just socket)
-
-tmuxSpec' :: TmuxTestConf -> (TmuxNative -> IO a) -> IO a
-tmuxSpec' conf thunk =
-  withSystemTempDirectory "chiasma-test" $ withTestTmux conf thunk
-
-tmuxSpec :: (TmuxNative -> IO a) -> IO a
-tmuxSpec =
-  tmuxSpec' def { ttcGui = False }
-
-tmuxGuiSpec :: (TmuxNative -> IO a) -> IO a
-tmuxGuiSpec =
-  tmuxSpec' def
diff --git a/lib/Chiasma/Tmux.hs b/lib/Chiasma/Tmux.hs
new file mode 100644
--- /dev/null
+++ b/lib/Chiasma/Tmux.hs
@@ -0,0 +1,89 @@
+module Chiasma.Tmux where
+
+import Polysemy.Internal (hoistSem)
+import Polysemy.Internal.Sing (KnownList (singList))
+import Polysemy.Internal.Union (hoist, weakenMid)
+
+import Chiasma.Data.Panes (Panes, TmuxPanes)
+import Chiasma.Effect.Codec (Codec)
+import Chiasma.Effect.TmuxApi (TmuxApi)
+import Chiasma.Effect.TmuxClient (ScopedTmux, TmuxClient)
+import Chiasma.Interpreter.TmuxApi (
+  InterpretApis (interpretApis),
+  RestopApis (restopApis),
+  TmuxApis,
+  interpretTmuxApi,
+  type (<$>),
+  )
+
+withTmuxApis' ::
+  ∀ commands err i o r a .
+  InterpretApis commands err i o r =>
+  Member (ScopedTmux i o) r =>
+  Sem (TmuxApis commands err ++ TmuxClient i o : r) a ->
+  Sem r a
+withTmuxApis' =
+  scoped_ . interpretApis @commands @err
+
+insertAfter ::
+  ∀ left e r a .
+  KnownList left =>
+  Sem (left ++ r) a ->
+  Sem (left ++ e : r) a
+insertAfter =
+  hoistSem $ hoist (insertAfter @left @e @r) . weakenMid @r (singList @left) (singList @'[e])
+
+withTmuxApis ::
+  ∀ commands err i o r .
+  KnownList (TmuxApis commands err) =>
+  InterpretApis commands err i o r =>
+  Member (ScopedTmux i o) r =>
+  InterpretersFor (TmuxApis commands err) r
+withTmuxApis =
+  scoped_ .
+  interpretApis @commands @err .
+  insertAfter @(TmuxApis commands err) @(TmuxClient i o) @r
+
+withTmuxApis_ ::
+  ∀ commands err i o apis r .
+  apis ~ TmuxApi <$> commands =>
+  KnownList apis =>
+  RestopApis commands err i o r =>
+  Member (ScopedTmux i o) r =>
+  InterpretersFor apis r
+withTmuxApis_ =
+  scoped_ .
+  restopApis @commands @err .
+  insertAfter @apis @(TmuxClient i o) @r
+
+withTmux ::
+  ∀ command err i o r .
+  Members [ScopedTmux i o, Codec command i o !! err] r =>
+  InterpreterFor (TmuxApi command !! err) r
+withTmux =
+  scoped_ . interpretTmuxApi . raiseUnder
+
+withTmux_ ::
+  ∀ command err i o r .
+  Members [ScopedTmux i o, Codec command i o !! err, Stop err] r =>
+  InterpreterFor (TmuxApi command) r
+withTmux_ =
+  scoped_ .
+  interpretTmuxApi .
+  raiseUnder .
+  restop @err @(TmuxApi command) .
+  raiseUnder
+
+withPanes ::
+  ∀ p err i o r .
+  Members [ScopedTmux i o, Codec (Panes p) i o !! err] r =>
+  InterpreterFor (TmuxPanes p !! err) r
+withPanes =
+  withTmux
+
+withPanes_ ::
+  ∀ p err i o r .
+  Members [ScopedTmux i o, Codec (Panes p) i o !! err, Stop err] r =>
+  InterpreterFor (TmuxPanes p) r
+withPanes_ =
+  withTmux_
diff --git a/lib/Chiasma/TmuxApi.hs b/lib/Chiasma/TmuxApi.hs
new file mode 100644
--- /dev/null
+++ b/lib/Chiasma/TmuxApi.hs
@@ -0,0 +1,7 @@
+module Chiasma.TmuxApi (
+  module Chiasma.Effect.TmuxApi,
+  module Chiasma.Interpreter.TmuxApi,
+) where
+
+import Chiasma.Effect.TmuxApi
+import Chiasma.Interpreter.TmuxApi
diff --git a/lib/Chiasma/TmuxNative.hs b/lib/Chiasma/TmuxNative.hs
new file mode 100644
--- /dev/null
+++ b/lib/Chiasma/TmuxNative.hs
@@ -0,0 +1,68 @@
+module Chiasma.TmuxNative where
+
+import Polysemy.Internal.Sing (KnownList)
+
+import Chiasma.Data.CodecError (CodecError)
+import Chiasma.Data.Panes (Panes, TmuxPanes)
+import Chiasma.Data.TmuxRequest (TmuxRequest)
+import Chiasma.Data.TmuxResponse (TmuxResponse)
+import Chiasma.Effect.Codec (NativeCodecE)
+import Chiasma.Effect.TmuxApi (TmuxApi)
+import Chiasma.Effect.TmuxClient (NativeTmux, TmuxClient)
+import Chiasma.Interpreter.TmuxApi (InterpretApisNative, RestopApisNative, TmuxApis, type (<$>))
+import Chiasma.Tmux (withTmux, withTmuxApis, withTmuxApis', withTmuxApis_, withTmux_)
+
+withTmuxApisNative' ::
+  ∀ commands r a .
+  InterpretApisNative commands r =>
+  Member NativeTmux r =>
+  Sem (TmuxApis commands CodecError ++ TmuxClient TmuxRequest TmuxResponse : r) a ->
+  Sem r a
+withTmuxApisNative' =
+  withTmuxApis' @commands @CodecError
+
+withTmuxApisNative ::
+  ∀ commands r .
+  KnownList (TmuxApis commands CodecError) =>
+  InterpretApisNative commands r =>
+  Member NativeTmux r =>
+  InterpretersFor (TmuxApis commands CodecError) r
+withTmuxApisNative =
+  withTmuxApis @commands @CodecError
+
+withTmuxApisNative_ ::
+  ∀ commands r .
+  KnownList (TmuxApi <$> commands) =>
+  RestopApisNative commands r =>
+  Member NativeTmux r =>
+  InterpretersFor (TmuxApi <$> commands) r
+withTmuxApisNative_ =
+  withTmuxApis_ @commands @CodecError
+
+withTmuxNative ::
+  ∀ command r .
+  Members [NativeTmux, NativeCodecE command] r =>
+  InterpreterFor (TmuxApi command !! CodecError) r
+withTmuxNative =
+  withTmux
+
+withTmuxNative_ ::
+  ∀ command r .
+  Members [NativeTmux, NativeCodecE command, Stop CodecError] r =>
+  InterpreterFor (TmuxApi command) r
+withTmuxNative_ =
+  withTmux_
+
+withPanesNative ::
+  ∀ p r .
+  Members [NativeTmux, NativeCodecE (Panes p)] r =>
+  InterpreterFor (TmuxPanes p !! CodecError) r
+withPanesNative =
+  withTmuxNative
+
+withPanesNative_ ::
+  ∀ p r .
+  Members [NativeTmux, NativeCodecE (Panes p), Stop CodecError] r =>
+  InterpreterFor (TmuxPanes p) r
+withPanesNative_ =
+  withTmuxNative_
diff --git a/lib/Chiasma/Ui/Data/Measure.hs b/lib/Chiasma/Ui/Data/Measure.hs
--- a/lib/Chiasma/Ui/Data/Measure.hs
+++ b/lib/Chiasma/Ui/Data/Measure.hs
@@ -1,8 +1,10 @@
 module Chiasma.Ui.Data.Measure where
 
-import Chiasma.Data.TmuxId (PaneId(..))
-import Data.Text.Prettyprint.Doc (Pretty(..), (<+>))
+import Control.Lens (makeClassy)
+import Prettyprinter (Pretty (..), (<+>))
 
+import Chiasma.Data.Axis (Axis)
+import Chiasma.Data.TmuxId (PaneId (..))
 import Chiasma.Ui.Data.Tree (NNode, NTree)
 
 data MPane =
@@ -11,7 +13,7 @@
     _mainPosition :: Int,
     _offPosition :: Int
   }
-  deriving (Eq, Show)
+  deriving stock (Eq, Show)
 
 makeClassy ''MPane
 
@@ -20,9 +22,9 @@
     _reference :: PaneId,
     _lMainPosition :: Int,
     _lOffPosition :: Int,
-    _vertical :: Bool
+    _axis :: Axis
   }
-  deriving (Eq, Show)
+  deriving stock (Eq, Show)
 
 makeClassy ''MLayout
 
@@ -31,7 +33,7 @@
     _size :: Int,
     _view :: a
   }
-  deriving (Eq, Show)
+  deriving stock (Eq, Show)
 
 makeClassy ''Measured
 
@@ -39,9 +41,8 @@
 type MeasureTreeSub = NNode (Measured MLayout) (Measured MPane)
 
 instance Pretty MLayout where
-  pretty (MLayout (PaneId refId) mainPos offPos vertical') =
-    "l –" <+> "ref:" <+> pretty refId <+> "pos:" <+> pretty mainPos <+> "(" <> pretty offPos <> ")" <+>
-      if vertical' then "v" else "h"
+  pretty (MLayout (PaneId refId) mainPos offPos axis') =
+    "l –" <+> "ref:" <+> pretty refId <+> "pos:" <+> pretty mainPos <+> "(" <> pretty offPos <> ")" <+> pretty axis'
 
 instance Pretty MPane where
   pretty (MPane (PaneId paneId') mainPos offPos) =
diff --git a/lib/Chiasma/Ui/Data/RenderableTree.hs b/lib/Chiasma/Ui/Data/RenderableTree.hs
--- a/lib/Chiasma/Ui/Data/RenderableTree.hs
+++ b/lib/Chiasma/Ui/Data/RenderableTree.hs
@@ -1,18 +1,19 @@
 module Chiasma.Ui.Data.RenderableTree where
 
-import Data.Text.Prettyprint.Doc (Doc, Pretty(..), emptyDoc, space, (<+>))
+import Prettyprinter (Doc, Pretty (..), emptyDoc, space, (<+>))
 
-import Chiasma.Data.TmuxId (PaneId(..))
+import Chiasma.Data.Axis (Axis)
+import Chiasma.Data.TmuxId (PaneId (..))
 import Chiasma.Ui.Data.Tree (NNode, NTree)
-import Chiasma.Ui.Data.ViewGeometry (ViewGeometry(ViewGeometry))
+import Chiasma.Ui.Data.ViewGeometry (ViewGeometry (ViewGeometry))
 import Chiasma.Ui.Data.ViewState (ViewState)
 
 data RLayout =
   RLayout {
     _ref :: RPane,
-    _vertical :: Bool
+    _axis :: Axis
   }
-  deriving (Eq, Show)
+  deriving stock (Eq, Show)
 
 data RPane =
   RPane {
@@ -20,7 +21,7 @@
     _top :: Int,
     _left :: Int
   }
-  deriving (Eq, Show)
+  deriving stock (Eq, Show)
 
 data Renderable a =
   Renderable {
@@ -28,7 +29,7 @@
     _geometry :: ViewGeometry,
     _view :: a
   }
-  deriving (Eq, Show)
+  deriving stock (Eq, Show)
 
 type RenderableLayout = Renderable RLayout
 type RenderablePane = Renderable RPane
@@ -36,8 +37,8 @@
 type RenderableNode = NNode RenderableLayout RenderablePane
 
 instance Pretty RLayout where
-  pretty (RLayout (RPane (PaneId refId) _ _) vertical) =
-    "l –" <+> "ref:" <+> pretty refId <+> "pos:" <+> if vertical then "v" else "h"
+  pretty (RLayout (RPane (PaneId refId) _ _) axis) =
+    "l –" <+> "ref:" <+> pretty refId <+> "pos:" <+> pretty axis
 
 instance Pretty RPane where
   pretty (RPane (PaneId paneId) top left) =
diff --git a/lib/Chiasma/Ui/Data/Tree.hs b/lib/Chiasma/Ui/Data/Tree.hs
--- a/lib/Chiasma/Ui/Data/Tree.hs
+++ b/lib/Chiasma/Ui/Data/Tree.hs
@@ -1,8 +1,7 @@
-{-# LANGUAGE TemplateHaskell #-}
-
 module Chiasma.Ui.Data.Tree where
 
-import Data.Text.Prettyprint.Doc (Pretty(..), vsep, nest)
+import Control.Lens (makeClassy)
+import Prettyprinter (Pretty (..), nest, vsep)
 
 data Tree f l p =
   Tree {
@@ -10,11 +9,11 @@
     _forest :: f (Node f l p)
     }
 
-deriving instance (Eq l, Eq p) => Eq (Tree [] l p)
-deriving instance (Show l, Show p) => Show (Tree [] l p)
+deriving stock instance (Eq l, Eq (Node [] l p)) => Eq (Tree [] l p)
+deriving stock instance (Show l, Show (Node [] l p)) => Show (Tree [] l p)
 
-deriving instance (Eq l, Eq p) => Eq (Tree NonEmpty l p)
-deriving instance (Show l, Show p) => Show (Tree NonEmpty l p)
+deriving stock instance (Eq l, Eq (Node NonEmpty l p)) => Eq (Tree NonEmpty l p)
+deriving stock instance (Show l, Show (Node NonEmpty l p)) => Show (Tree NonEmpty l p)
 
 data Node f l p =
   Sub { _subTree :: Tree f l p }
@@ -24,11 +23,11 @@
 makeClassy ''Tree
 makeClassy ''Node
 
-deriving instance (Eq l, Eq p) => Eq (Node [] l p)
-deriving instance (Show l, Show p) => Show (Node [] l p)
+deriving stock instance (Eq l, Eq p) => Eq (Node [] l p)
+deriving stock instance (Show l, Show p) => Show (Node [] l p)
 
-deriving instance (Eq l, Eq p) => Eq (Node NonEmpty l p)
-deriving instance (Show l, Show p) => Show (Node NonEmpty l p)
+deriving stock instance (Eq l, Eq p) => Eq (Node NonEmpty l p)
+deriving stock instance (Show l, Show p) => Show (Node NonEmpty l p)
 
 type LTree l p = Tree [] l p
 type LNode l p = Node [] l p
diff --git a/lib/Chiasma/Ui/Data/TreeModError.hs b/lib/Chiasma/Ui/Data/TreeModError.hs
--- a/lib/Chiasma/Ui/Data/TreeModError.hs
+++ b/lib/Chiasma/Ui/Data/TreeModError.hs
@@ -1,6 +1,7 @@
 module Chiasma.Ui.Data.TreeModError where
 
 import Chiasma.Data.Ident (Ident)
+
 import Chiasma.Ui.Data.View (LayoutView, PaneView)
 
 data TreeModError =
@@ -17,6 +18,4 @@
   AmbiguousLayout Ident Int
   |
   NoTrees
-  deriving (Eq, Show)
-
-deepPrisms ''TreeModError
+  deriving stock (Eq, Show)
diff --git a/lib/Chiasma/Ui/Data/View.hs b/lib/Chiasma/Ui/Data/View.hs
--- a/lib/Chiasma/Ui/Data/View.hs
+++ b/lib/Chiasma/Ui/Data/View.hs
@@ -1,23 +1,24 @@
 module Chiasma.Ui.Data.View where
 
-import Chiasma.Data.Ident (Ident, Identifiable(..))
-import Control.Lens (Index, IxValue, Ixed(ix), makeClassy_)
+import Control.Lens (Index, IxValue, Ixed (ix), makeClassy, makeClassy_)
 import Control.Lens.Plated (Plated)
+import Data.Bifoldable (Bifoldable (bifoldMap))
 import Data.Data (Data)
-import Data.Text.Prettyprint.Doc (Doc, Pretty(..), emptyDoc, nest, space, vsep, (<+>))
-import Prelude hiding (state)
+import Prettyprinter (Doc, Pretty (..), emptyDoc, nest, space, vsep, (<+>))
 
+import Chiasma.Data.Axis (Axis (Horizontal, Vertical))
+import Chiasma.Data.Ident (Ident, Identifiable (..))
 import Chiasma.Ui.Data.ViewGeometry (ViewGeometry)
-import Chiasma.Ui.Data.ViewState (ViewState(ViewState))
+import Chiasma.Ui.Data.ViewState (ViewState (ViewState))
 import Chiasma.Ui.Lens.Ident (matchIdentP)
 
 data Pane =
   Pane {
     _open :: Bool,
     _pin :: Bool,
-    _cwd :: Maybe FilePath
+    _cwd :: Maybe Text
   }
-  deriving (Eq, Show, Data, Generic)
+  deriving stock (Eq, Show, Data, Generic)
 
 makeClassy ''Pane
 
@@ -26,15 +27,13 @@
 
 newtype Layout =
   Layout {
-    vertical :: Bool
+    axis :: Axis
   }
-  deriving (Eq, Show, Data, Generic)
+  deriving stock (Eq, Show, Data, Generic)
+  deriving newtype (Default)
 
 makeClassy_ ''Layout
 
-instance Default Layout where
-  def = Layout True
-
 data View a =
   View {
     _ident :: Ident,
@@ -42,7 +41,7 @@
     _geometry :: ViewGeometry,
     _extra :: a
   }
-  deriving (Eq, Show, Data, Generic)
+  deriving stock (Eq, Show, Data, Generic)
 
 makeClassy ''View
 
@@ -53,8 +52,11 @@
 type LayoutView = View Layout
 
 instance Pretty Layout where
-  pretty (Layout vertical) =
-    if vertical then "▤" else "▥"
+  pretty = \case
+    Layout Vertical ->
+      "➡"
+    Layout Horizontal ->
+      "⬇"
 
 instance Pretty Pane where
   pretty (Pane open' pin' _) =
@@ -75,16 +77,16 @@
 consPane :: Ident -> PaneView
 consPane ident' = View ident' (ViewState False) def (Pane False False Nothing)
 
-consLayoutAs :: Bool -> Ident -> LayoutView
-consLayoutAs vert ident' = View ident' (ViewState False) def (Layout vert)
+consLayoutAs :: Axis -> Ident -> LayoutView
+consLayoutAs axis ident' = View ident' (ViewState False) def (Layout axis)
 
 consLayout :: Ident -> LayoutView
 consLayout =
-  consLayoutAs False
+  consLayoutAs Horizontal
 
 consLayoutVertical :: Ident -> LayoutView
 consLayoutVertical =
-  consLayoutAs True
+  consLayoutAs Vertical
 
 instance Identifiable (View a) where
   identify = _ident
@@ -95,7 +97,7 @@
     treeData :: l,
     treeSubs :: [TreeSub l p]
     }
-  deriving (Eq, Show, Data, Generic)
+  deriving stock (Eq, Show, Data, Generic)
 
 instance Bifunctor Tree where
   first f (Tree l sub) = Tree (f l) (fmap (first f) sub)
@@ -110,7 +112,7 @@
   TreeNode { _subTree :: Tree l p }
   |
   TreeLeaf { _leafData :: p }
-  deriving (Eq, Show, Data, Generic)
+  deriving stock (Eq, Show, Data, Generic)
 
 instance Bifunctor TreeSub where
   first f (TreeNode t) = TreeNode (first f t)
@@ -138,7 +140,8 @@
 type instance IxValue (Tree l p) = Tree l p
 
 instance Identifiable l => Ixed (Tree l p) where
-  ix = matchIdentP
+  ix i =
+    matchIdentP i
 
 instance (Pretty l, Pretty p) => Pretty (TreeSub l p) where
   pretty (TreeNode a) =
diff --git a/lib/Chiasma/Ui/Data/ViewGeometry.hs b/lib/Chiasma/Ui/Data/ViewGeometry.hs
--- a/lib/Chiasma/Ui/Data/ViewGeometry.hs
+++ b/lib/Chiasma/Ui/Data/ViewGeometry.hs
@@ -1,9 +1,7 @@
-{-# LANGUAGE DeriveAnyClass #-}
-
 module Chiasma.Ui.Data.ViewGeometry where
 
 import Data.Data (Data)
-import Data.Text.Prettyprint.Doc (Doc, Pretty(..), emptyDoc, space, (<+>))
+import Prettyprinter (Doc, Pretty (..), emptyDoc, space, (<+>))
 
 data ViewGeometry =
   ViewGeometry {
@@ -14,7 +12,8 @@
     weight :: Maybe Float,
     position :: Maybe Float
   }
-  deriving (Eq, Show, Data, Generic, Default)
+  deriving stock (Eq, Show, Data, Generic)
+  deriving anyclass (Default)
 
 mayPretty :: Text -> Maybe Float -> Doc a
 mayPretty prefix (Just a) =
diff --git a/lib/Chiasma/Ui/Data/ViewState.hs b/lib/Chiasma/Ui/Data/ViewState.hs
--- a/lib/Chiasma/Ui/Data/ViewState.hs
+++ b/lib/Chiasma/Ui/Data/ViewState.hs
@@ -6,7 +6,7 @@
   ViewState {
     minimized :: Bool
   }
-  deriving (Eq, Show, Data, Generic)
+  deriving stock (Eq, Show, Data, Generic)
 
 instance Default ViewState where
   def = ViewState False
diff --git a/lib/Chiasma/Ui/Lens/Ident.hs b/lib/Chiasma/Ui/Lens/Ident.hs
--- a/lib/Chiasma/Ui/Lens/Ident.hs
+++ b/lib/Chiasma/Ui/Lens/Ident.hs
@@ -1,23 +1,21 @@
-{-# LANGUAGE RankNTypes #-}
+module Chiasma.Ui.Lens.Ident where
 
-module Chiasma.Ui.Lens.Ident(
-  matchIdent,
-  matchIdentL,
-  matchIdentP,
-) where
+import Control.Lens (Prism', prism)
 
-import Control.Lens (Traversal', Prism', filtered, each, prism)
-import Chiasma.Data.Ident (Ident, Identifiable(..), sameIdent)
+import Chiasma.Data.Ident (Ident, Identifiable (..), sameIdent)
 
 matchIdent :: Identifiable a => Ident -> Traversal' a a
-matchIdent = filtered . sameIdent
+matchIdent i =
+  filtered (sameIdent i)
 
 matchIdentL :: Identifiable a => Ident -> Traversal' [a] a
-matchIdentL ident = each . matchIdent ident
+matchIdentL ident =
+  each . matchIdent ident
 
 identEither :: Identifiable a => Ident -> a -> Either a a
 identEither target a =
   if sameIdent target a then Right a else Left a
 
 matchIdentP :: Identifiable a => Ident -> Prism' a a
-matchIdentP ident = prism id (identEither ident)
+matchIdentP ident =
+  prism id (identEither ident)
diff --git a/lib/Chiasma/Ui/Measure.hs b/lib/Chiasma/Ui/Measure.hs
--- a/lib/Chiasma/Ui/Measure.hs
+++ b/lib/Chiasma/Ui/Measure.hs
@@ -1,15 +1,15 @@
 module Chiasma.Ui.Measure where
 
 import qualified Data.List.NonEmpty as NonEmpty (zip)
-import GHC.Float (int2Float)
 
-import Chiasma.Data.Maybe (orElse)
-import Chiasma.Ui.Data.Measure (MLayout(..), MPane(..), MeasureTree, MeasureTreeSub, Measured(..))
-import Chiasma.Ui.Data.RenderableTree (RLayout(..), RPane(..), Renderable(..), RenderableNode, RenderableTree)
-import Chiasma.Ui.Data.Tree (Tree(..))
-import qualified Chiasma.Ui.Data.Tree as Tree (Node(..))
-import Chiasma.Ui.Data.ViewGeometry (ViewGeometry(minSize, maxSize, fixedSize))
-import Chiasma.Ui.Data.ViewState (ViewState(ViewState))
+import qualified Chiasma.Data.Axis as Axis
+import Chiasma.Data.Axis (Axis (Horizontal, Vertical))
+import Chiasma.Ui.Data.Measure (MLayout (..), MPane (..), MeasureTree, MeasureTreeSub, Measured (..))
+import Chiasma.Ui.Data.RenderableTree (RLayout (..), RPane (..), Renderable (..), RenderableNode, RenderableTree)
+import Chiasma.Ui.Data.Tree (Tree (..))
+import qualified Chiasma.Ui.Data.Tree as Tree (Node (..))
+import Chiasma.Ui.Data.ViewGeometry (ViewGeometry (fixedSize, maxSize, minSize))
+import Chiasma.Ui.Data.ViewState (ViewState (ViewState))
 import Chiasma.Ui.Measure.Balance (balanceSizes)
 import Chiasma.Ui.Measure.Weights (viewWeights)
 
@@ -22,7 +22,7 @@
 
 actualSize :: (ViewGeometry -> Maybe Float) ->  ViewState -> ViewGeometry -> Maybe Float
 actualSize getter viewState viewGeom =
-  orElse (getter viewGeom) (effectiveFixedSize viewState viewGeom)
+  getter viewGeom <|> effectiveFixedSize viewState viewGeom
 
 actualMinSizes :: NonEmpty (ViewState, ViewGeometry) -> NonEmpty Float
 actualMinSizes =
@@ -44,7 +44,7 @@
   balanceSizes minSizes maxSizes weights minimized cells
   where
     measureData = fmap subMeasureData views
-    paneSpacers = int2Float (length views) - 1.0
+    paneSpacers = fromIntegral (length views) - 1.0
     cells = total - paneSpacers
     sizesInCells s = if s > 1 then s else s * cells
     minSizes = fmap sizesInCells (actualMinSizes measureData)
@@ -52,25 +52,28 @@
     minimized = fmap (uncurry isMinimized) measureData
     weights = viewWeights measureData
 
-measureSub :: Int -> Int -> Bool -> RenderableNode -> Int -> MeasureTreeSub
-measureSub width height vertical (Tree.Sub tree) size =
-  Tree.Sub $ measureLayout tree newWidth newHeight vertical
+measureSub :: Int -> Int -> Axis -> RenderableNode -> Int -> MeasureTreeSub
+measureSub width height axis (Tree.Sub tree) size =
+  Tree.Sub (measureLayout tree newWidth newHeight axis)
   where
-    (newWidth, newHeight) = if vertical then (width, size) else (size, height)
-measureSub _ _ vertical (Tree.Leaf (Renderable _ _ (RPane paneId top left))) size =
+    (newWidth, newHeight) =
+      case axis of
+        Vertical -> (width, size)
+        Horizontal -> (size, height)
+measureSub _ _ (Axis.vertical -> vertical) (Tree.Leaf (Renderable _ _ (RPane paneId top left))) size =
   Tree.Leaf (Measured size (MPane paneId (if vertical then top else left) (if vertical then left else top)))
 
-measureLayout :: RenderableTree -> Int -> Int -> Bool -> MeasureTree
-measureLayout (Tree (Renderable _ _ (RLayout (RPane refId refTop refLeft) vertical)) sub) width height parentVertical =
-  Tree (Measured sizeInParent (MLayout refId mainPos offPos vertical)) measuredSub
+measureLayout :: RenderableTree -> Int -> Int -> Axis -> MeasureTree
+measureLayout (Tree (Renderable _ _ (RLayout (RPane refId refTop refLeft) axis)) sub) width height (Axis.vertical -> parentVertical) =
+  Tree (Measured sizeInParent (MLayout refId mainPos offPos axis)) measuredSub
   where
     sizeInParent = if parentVertical then height else width
     mainPos = if parentVertical then refTop else refLeft
     offPos = if parentVertical then refLeft else refTop
-    subTotalSize = if vertical then height else width
-    sizes = measureLayoutViews (int2Float subTotalSize) sub
-    measuredSub = uncurry (measureSub width height vertical) <$> NonEmpty.zip sub sizes
+    subTotalSize = if Axis.vertical axis then height else width
+    sizes = measureLayoutViews (fromIntegral subTotalSize) sub
+    measuredSub = uncurry (measureSub width height axis) <$> NonEmpty.zip sub sizes
 
 measureTree :: RenderableTree -> Int -> Int -> MeasureTree
 measureTree tree width height =
-  measureLayout tree width height False
+  measureLayout tree width height Horizontal
diff --git a/lib/Chiasma/Ui/Measure/Balance.hs b/lib/Chiasma/Ui/Measure/Balance.hs
--- a/lib/Chiasma/Ui/Measure/Balance.hs
+++ b/lib/Chiasma/Ui/Measure/Balance.hs
@@ -1,14 +1,7 @@
 module Chiasma.Ui.Measure.Balance where
 
 import Data.List (zipWith3)
-import qualified Data.List.NonEmpty as NonEmpty (
-  filter,
-  toList,
-  zip,
-  zipWith,
-  )
-import GHC.Float (float2Int, int2Float)
-import GHC.Float.RealFracMethods (floorFloatInt)
+import qualified Data.List.NonEmpty as NonEmpty
 
 import Chiasma.Ui.Measure.Weights (
   amendAndNormalizeWeights,
@@ -30,24 +23,37 @@
 
 reverseWeights :: NonEmpty Float -> NonEmpty Float
 reverseWeights weights =
-  if norm > 0 then fmap (/ norm) r else r
+  rev <$> r
   where
-    r = fmap (1 -) weights
-    norm = sum r
+    rev a =
+      fromMaybe a (a / norm)
+    r =
+      fmap (1 -) weights
+    norm =
+      sum r
 
 cutSizes :: Balance -> NonEmpty Float
-cutSizes (Balance min' _ weights _ total) =
-  fmap addDist cut
+cutSizes (Balance minSizes _ weights _ total) =
+  addNegatives <$> truncatedWeighted
   where
-    surplus = sum min' - total
-    dist = fmap (surplus *) (reverseWeights weights)
-    cut = fmap (uncurry (-)) (NonEmpty.zip min' dist)
-    negOrZero a = if a < 0 then a else 0
-    neg = fmap negOrZero cut
-    negTotal = sum neg
-    negCount = length (NonEmpty.filter (< 0) neg)
-    dist2 = negTotal / int2Float (length min' - negCount)
-    addDist a = if a < 0 then 0 else a + dist2
+    surplus =
+      sum minSizes - total
+    surplusDistWeighted =
+      fmap (surplus *) (reverseWeights weights)
+    truncatedWeighted =
+      fmap (uncurry (-)) (NonEmpty.zip minSizes surplusDistWeighted)
+    negOrZero a =
+      if a < 0 then a else 0
+    neg =
+      fmap negOrZero truncatedWeighted
+    negTotal =
+      sum neg
+    negCount =
+      length (NonEmpty.filter (< 0) neg)
+    negativesDist =
+      fromMaybe 0 (negTotal / realToFrac (length minSizes - negCount))
+    addNegatives a =
+      if a < 0 then 0 else a + negativesDist
 
 distributeOnUnbounded :: Balance -> NonEmpty Float
 distributeOnUnbounded (Balance min' max' weights _ total) =
@@ -121,28 +127,37 @@
     maxTotal = sum (catMaybes $ NonEmpty.toList $ balanceMax balance)
 
 roundSizes :: NonEmpty Float -> NonEmpty Int
-roundSizes (head' :| tail') =
-  roundedHead + (float2Int surplus) :| roundedTail
+roundSizes (h :| t) =
+  roundedHead + round surplus :| roundedTail
   where
-    (surplus, roundedTail) = mapAccumL folder diff0 tail'
-    (roundedHead, diff0) = diff head'
+    (surplus, roundedTail) = mapAccumL folder diff0 t
+    (roundedHead, diff0) = diff h
     folder z a =
       (z + z1, a1)
       where
         (a1, z1) = diff a
-    diff a = (floorFloatInt a, a - int2Float (floor a))
+    diff a = (floor a, a - fromIntegral (floor a :: Int))
 
+-- |Tmux doesn't render panes smaller than two cells.
 ensureMinimum2 :: NonEmpty Float -> NonEmpty Float
 ensureMinimum2 sizes =
   choose <$> positives
   where
-    positive = max 0
-    positives = positive <$> sizes
-    positivesCount = length $ NonEmpty.filter (>= 2) sizes
-    unders = amountUnderTwo <$> positives
-    sub = sum unders / int2Float positivesCount
-    amountUnderTwo a = positive (2 - a)
-    choose a = max 2 (a - sub)
+    positive =
+      max 0
+    positives =
+      positive <$> sizes
+    overTwoCount =
+      length (NonEmpty.filter (>= 2) sizes)
+    unders =
+      amountUnderTwo <$> positives
+    -- If no sizes are larger than 2, nothing will have to be subtracted, all sizes will be clamped to 2
+    amountUnderTwoDist =
+      fromMaybe 0 (sum unders / realToFrac overTwoCount)
+    amountUnderTwo a =
+      positive (2 - a)
+    choose a =
+      max 2 (a - amountUnderTwoDist)
 
 rectifySizes :: NonEmpty Float -> NonEmpty Int
 rectifySizes =
diff --git a/lib/Chiasma/Ui/Measure/Weights.hs b/lib/Chiasma/Ui/Measure/Weights.hs
--- a/lib/Chiasma/Ui/Measure/Weights.hs
+++ b/lib/Chiasma/Ui/Measure/Weights.hs
@@ -1,14 +1,9 @@
-module Chiasma.Ui.Measure.Weights(
-  viewWeights,
-  normalizeWeights,
-  amendAndNormalizeWeights,
-) where
+module Chiasma.Ui.Measure.Weights where
 
-import qualified Data.List.NonEmpty as NonEmpty (toList, filter)
-import GHC.Float (int2Float)
+import qualified Data.List.NonEmpty as NonEmpty (filter, toList)
 
-import Chiasma.Ui.Data.ViewGeometry (ViewGeometry(ViewGeometry))
-import Chiasma.Ui.Data.ViewState (ViewState(ViewState))
+import Chiasma.Ui.Data.ViewGeometry (ViewGeometry (ViewGeometry))
+import Chiasma.Ui.Data.ViewState (ViewState (ViewState))
 
 effectiveWeight :: ViewState -> ViewGeometry -> Maybe Float
 effectiveWeight (ViewState minimized) (ViewGeometry _ _ fixedSize _ weight _) =
@@ -18,18 +13,21 @@
 amendWeights weights =
   fmap (fromMaybe emptyWeight) weights
   where
-    total = sum (catMaybes $ NonEmpty.toList weights)
-    normTotal = if total == 0 then 1 else total
-    empties = length (NonEmpty.filter isNothing weights)
-    normEmpties = if empties == 0 then 1 else empties
-    emptyWeight = normTotal / int2Float normEmpties
+    total =
+      sum (catMaybes (NonEmpty.toList weights))
+    normTotal =
+      if total == 0 then 1 else total
+    empties =
+      length (NonEmpty.filter isNothing weights)
+    emptyWeight =
+      fromMaybe normTotal (normTotal / realToFrac empties)
 
 normalizeWeights :: NonEmpty Float -> NonEmpty Float
 normalizeWeights weights =
-  fmap (/ normTotal) weights
+  fmap norm weights
   where
-    total = sum weights
-    normTotal = if total == 0 then 1 else total
+    norm a =
+      fromMaybe a (a / sum weights)
 
 amendAndNormalizeWeights :: NonEmpty (Maybe Float) -> NonEmpty Float
 amendAndNormalizeWeights = normalizeWeights . amendWeights
diff --git a/lib/Chiasma/Ui/ShowTree.hs b/lib/Chiasma/Ui/ShowTree.hs
--- a/lib/Chiasma/Ui/ShowTree.hs
+++ b/lib/Chiasma/Ui/ShowTree.hs
@@ -3,11 +3,11 @@
 import Chiasma.Data.Ident (identText)
 import Chiasma.Ui.Data.View (
   LayoutView,
-  Pane(Pane),
+  Pane (Pane),
   PaneView,
-  Tree(Tree),
-  TreeSub(TreeNode, TreeLeaf),
-  View(View),
+  Tree (Tree),
+  TreeSub (TreeLeaf, TreeNode),
+  View (View),
   ViewTree,
   )
 
@@ -38,4 +38,8 @@
 showViewTree = showTree . formatViewTree
 
 printViewTree :: MonadIO m => ViewTree -> m ()
-printViewTree = liftIO . traverse_ putStrLn . fmap toString . showViewTree
+printViewTree =
+  liftIO .
+  traverse_ putStrLn .
+  fmap toString .
+  showViewTree
diff --git a/lib/Chiasma/Ui/ViewTree.hs b/lib/Chiasma/Ui/ViewTree.hs
--- a/lib/Chiasma/Ui/ViewTree.hs
+++ b/lib/Chiasma/Ui/ViewTree.hs
@@ -1,41 +1,34 @@
 module Chiasma.Ui.ViewTree where
 
+import Control.Lens (anyOf, cosmos, ix, mapMOf, transformM)
+import Control.Monad.Trans.Class (lift)
+import Control.Monad.Trans.Except (ExceptT (ExceptT), runExceptT)
+import Control.Monad.Trans.Writer (WriterT, runWriterT, tell)
+import Data.Composition ((.:))
+import Data.Semigroup (Sum (Sum))
+import Exon (exon)
+import Prelude hiding (ix, tell)
+
 import Chiasma.Data.Ident (Ident)
 import Chiasma.Lens.Tree (
-  LeafIndexTree(..),
+  LeafIndexTree (..),
   _litTree,
   leafDataTraversal,
   )
-import Control.Lens (
-  Traversal,
-  Traversal',
-  anyOf,
-  cosmos,
-  filtered,
-  has,
-  ix,
-  mapMOf,
-  over,
-  transformM,
-  )
-import Control.Monad.Error.Class (throwError)
-import Control.Monad.Trans.Writer (WriterT, runWriterT, tell)
-import Data.Composition ((.:))
-
-import Chiasma.Ui.Data.TreeModError (TreeModError(PaneMissing, AmbiguousPane, LayoutMissing, AmbiguousLayout))
+import Chiasma.Ui.Data.TreeModError (TreeModError (AmbiguousLayout, AmbiguousPane, LayoutMissing, PaneMissing))
 import Chiasma.Ui.Data.View (
-  Pane(Pane),
+  Pane (Pane),
   PaneView,
-  Tree(Tree),
-  TreeSub(TreeNode, TreeLeaf),
-  View(View),
+  Tree (Tree),
+  TreeSub (TreeLeaf, TreeNode),
+  View (View),
   ViewTree,
   ViewTreeSub,
   )
 import qualified Chiasma.Ui.Data.View as Pane (open)
 import qualified Chiasma.Ui.Data.View as TreeSub (leafData)
 import qualified Chiasma.Ui.Data.View as View (extra)
-import Chiasma.Ui.Data.ViewState (ViewState(ViewState))
+import Chiasma.Ui.Data.ViewState (ViewState (ViewState))
 import Chiasma.Ui.Pane (paneSetOpen, paneToggleOpen)
 
 modCounted :: Monad m => (a -> m a) -> a -> WriterT (Sum Int) m a
@@ -55,9 +48,9 @@
   let st = (transformM $ mapMOf (ix ident) (modCounted f)) tree
   (result, Sum count) <- lift $ runWriterT st
   case count of
-    1 -> return result
-    0 -> throwError $ LayoutMissing ident
-    n -> throwError $ AmbiguousLayout ident n
+    1 -> pure result
+    0 -> ExceptT (pure (Left (LayoutMissing ident)))
+    n -> ExceptT (pure (Left (AmbiguousLayout ident n)))
 
 toggleLayout1 :: Ident -> ViewTree -> Either TreeModError ViewTree
 toggleLayout1 ident tree =
@@ -68,9 +61,9 @@
   let st = (transformM $ mapMOf (ix ident) (modCounted f)) (LeafIndexTree tree)
   (result, Sum count) <- lift $ runWriterT st
   case count of
-    1 -> return $ litTree result
-    0 -> throwError $ PaneMissing ident
-    n -> throwError $ AmbiguousPane ident n
+    1 -> pure $ litTree result
+    0 -> ExceptT (pure (Left (PaneMissing ident)))
+    n -> ExceptT (pure (Left (AmbiguousPane ident n)))
 
 modifyPane :: (PaneView -> PaneView) -> Ident -> ViewTree -> Either TreeModError ViewTree
 modifyPane modification ident tree =
@@ -115,7 +108,7 @@
   Multiple Int
   |
   Consistent
-  deriving (Eq, Show)
+  deriving stock (Eq, Show)
 
 instance Semigroup ToggleStatus where
   Pristine <> a = a
@@ -134,7 +127,7 @@
   NotFound
   |
   Ambiguous Int
-  deriving (Eq, Show, Functor)
+  deriving stock (Eq, Show, Functor)
 
 instance Semigroup (ToggleResult a) where
   NotFound <> a = a
@@ -157,6 +150,12 @@
     Success a >>= f = f a
     NotFound >>= _ = NotFound
     Ambiguous n >>= _ = Ambiguous n
+
+toggleResultEither :: ToggleResult a -> Either Text a
+toggleResultEither = \case
+  Success a -> Right a
+  NotFound -> Left "not found"
+  Ambiguous n -> Left [exon|ambiguous: #{show n}|]
 
 openPinnedSubs :: ToggleStatus -> ViewTree -> (ToggleStatus, ViewTree)
 openPinnedSubs Pristine t =
diff --git a/lib/Chiasma/View.hs b/lib/Chiasma/View.hs
--- a/lib/Chiasma/View.hs
+++ b/lib/Chiasma/View.hs
@@ -1,17 +1,16 @@
-{-# LANGUAGE RankNTypes #-}
-
 module Chiasma.View where
 
+import qualified Control.Lens as Lens
+import Exon (exon)
+import Prettyprinter (Doc, pretty)
+import Prettyprinter.Render.Terminal (AnsiStyle)
+
 import Chiasma.Data.Ident (Ident, identText, sameIdent)
 import Chiasma.Data.TmuxId (PaneId, SessionId, WindowId)
-import Chiasma.Data.View (View(View), viewIdent)
-import Chiasma.Data.Views (Views, ViewsError(..))
+import Chiasma.Data.View (View (View), viewIdent)
+import Chiasma.Data.Views (Views, ViewsError (..))
 import qualified Chiasma.Data.Views as Views (log, panes, sessions, windows)
 import Chiasma.Lens.Where (where1)
-import Control.Lens (Lens', over)
-import qualified Control.Lens as Lens (over, set, view)
-import Data.Text.Prettyprint.Doc (Doc, pretty)
-import Data.Text.Prettyprint.Doc.Render.Terminal (AnsiStyle)
 
 sameId :: Eq a => a -> View a -> Bool
 sameId id' (View _ (Just vid)) = id' == vid
@@ -26,11 +25,11 @@
   find (sameId id') . Lens.view viewsL
 
 insertView :: Lens' Views [View a] -> View a -> Views -> Views
-insertView viewsL newView = Lens.over viewsL (newView :)
+insertView viewsL newView = over viewsL (newView :)
 
 updateView :: Lens' Views [View a] -> (Ident -> ViewsError) -> View a -> Views -> Views
 updateView viewsL _ newView =
-  Lens.set (viewsL . where1 (sameIdent (viewIdent newView))) newView
+  set (viewsL . where1 (sameIdent (viewIdent newView))) newView
 
 session :: Ident -> Views -> Either ViewsError (View SessionId)
 session = view Views.sessions NoSuchSession
@@ -78,26 +77,42 @@
 type Getter a = Ident -> Views -> Either ViewsError (View a)
 type Setter a = View a -> Views -> Views
 
-addView :: MonadDeepState s Views m => Setter a -> Ident -> m (View a)
-addView setter ident = do
-  modify $ setter newView
-  viewsLogS $ "added tmux view " <> identText ident
-  return newView
-  where
-    newView = View ident Nothing
-
-findOrCreateView :: (MonadDeepState s Views m) => Getter a -> Setter a -> Ident -> m (View a)
-findOrCreateView getter setter ident = do
-  existing <- gets $ getter ident
-  either (const $ addView setter ident) return existing
-
-viewsLog :: MonadDeepState s Views m => Doc AnsiStyle -> m ()
+viewsLog ::
+  Member (AtomicState Views) r =>
+  Doc AnsiStyle ->
+  Sem r ()
 viewsLog message =
-  modify f
+  atomicModify' f
   where
     f :: Views -> Views
     f = over Views.log (message :)
 
-viewsLogS :: MonadDeepState s Views m => Text -> m ()
+viewsLogS ::
+  Member (AtomicState Views) r =>
+  Text ->
+  Sem r ()
 viewsLogS =
   viewsLog . pretty
+
+addView ::
+  Member (AtomicState Views) r =>
+  Setter a ->
+  Ident ->
+  Sem r (View a)
+addView setter ident = do
+  atomicModify' (setter newView)
+  viewsLogS [exon|added tmux view #{identText ident}|]
+  pure newView
+  where
+    newView =
+      View ident Nothing
+
+findOrCreateView ::
+  Member (AtomicState Views) r =>
+  Getter a ->
+  Setter a ->
+  Ident ->
+  Sem r (View a)
+findOrCreateView getter setter ident = do
+  existing <- atomicGets (getter ident)
+  either (const (addView setter ident)) pure existing
diff --git a/lib/Chiasma/View/State.hs b/lib/Chiasma/View/State.hs
--- a/lib/Chiasma/View/State.hs
+++ b/lib/Chiasma/View/State.hs
@@ -2,16 +2,12 @@
 
 import Chiasma.Data.Ident (Ident)
 import Chiasma.Data.TmuxId (PaneId)
-import Control.Monad.DeepError (MonadDeepError, hoistEither)
-import Control.Monad.DeepState (MonadDeepState, gets)
-
 import Chiasma.Data.Views (Views, ViewsError)
 import qualified Chiasma.View as Views (paneId)
 
 paneId ::
-  (MonadDeepState s Views m, MonadDeepError e ViewsError m) =>
+  Members [AtomicState Views, Stop ViewsError] r =>
   Ident ->
-  m PaneId
-paneId paneIdent = do
-  pid <- gets $ Views.paneId paneIdent
-  hoistEither pid
+  Sem r PaneId
+paneId paneIdent =
+  stopEither =<< atomicGets (Views.paneId paneIdent)
diff --git a/lib/Chiasma/Window.hs b/lib/Chiasma/Window.hs
--- a/lib/Chiasma/Window.hs
+++ b/lib/Chiasma/Window.hs
@@ -1,64 +1,66 @@
 module Chiasma.Window where
 
-import qualified Chiasma.Codec.Data as Codec (Pane(Pane, paneId), Window(Window, windowId))
-import qualified Chiasma.Codec.Data.PaneDetail as Codec (PaneDetail(PaneDetail))
-import qualified Chiasma.Codec.Data.PaneDetail as PaneDetail (PaneDetail(..))
-import qualified Chiasma.Command.Pane as Cmd (closePane, firstWindowPane, windowPanesAs)
-import qualified Chiasma.Command.Window as Cmd (newWindow, splitWindowAs, window)
+import qualified Data.List.NonEmpty as NonEmpty (head, nonEmpty)
+import Path (Abs, Dir, Path, parseAbsDir)
+import Prettyprinter (line, pretty, vsep, (<+>))
+
+import qualified Chiasma.Codec.Data.Pane as Pane
+import qualified Chiasma.Codec.Data.Pane as Codec (Pane (Pane, paneId))
+import Chiasma.Codec.Data.Pane (Pane (Pane))
+import qualified Chiasma.Codec.Data.Window as Codec (Window (Window, windowId))
+import qualified Chiasma.Command.Pane as Cmd (closePane, firstWindowPane, windowPanes)
+import qualified Chiasma.Command.Window as Cmd (newWindow, splitWindowInDir, window)
+import Chiasma.Data.Axis (Axis)
 import Chiasma.Data.Ident (Ident, identText, identify)
-import Chiasma.Data.Maybe (findMaybe, maybeExcept, orElse)
+import Chiasma.Data.Panes (TmuxPanes)
 import Chiasma.Data.RenderError (RenderError)
-import qualified Chiasma.Data.RenderError as RenderError (RenderError(NoPrincipal))
+import qualified Chiasma.Data.RenderError as RenderError (RenderError (NoPrincipal))
 import Chiasma.Data.TmuxId (PaneId, SessionId, WindowId)
-import Chiasma.Data.TmuxThunk (TmuxThunk)
-import qualified Chiasma.Data.View as Tmux (View(View))
+import qualified Chiasma.Data.View as Tmux (View (View))
 import Chiasma.Data.Views (Views)
-import Chiasma.Data.WindowState (WindowState(..))
+import Chiasma.Data.WindowState (WindowState (..))
+import Chiasma.Effect.TmuxApi (Tmux)
 import Chiasma.Pane (addPane)
 import Chiasma.Ui.Data.RenderableTree (
-  RLayout(..),
-  RPane(..),
-  Renderable(..),
+  RLayout (..),
+  RPane (..),
+  Renderable (..),
   RenderableNode,
   RenderableTree,
   )
-import qualified Chiasma.Ui.Data.Tree as Tree (Node(Sub, Leaf), Tree(Tree))
-import Chiasma.Ui.Data.View (Tree(..), TreeSub(..), ViewTree, ViewTreeSub)
-import qualified Chiasma.Ui.Data.View as Ui (Layout(..), Pane(Pane), PaneView, View(View))
-import Chiasma.Ui.Data.ViewGeometry (ViewGeometry(ViewGeometry, position))
+import qualified Chiasma.Ui.Data.Tree as Tree (Node (Leaf, Sub), Tree (Tree))
+import Chiasma.Ui.Data.View (Tree (..), TreeSub (..), ViewTree, ViewTreeSub)
+import qualified Chiasma.Ui.Data.View as Ui (Layout (..), Pane (Pane), PaneView, View (View))
+import Chiasma.Ui.Data.ViewGeometry (ViewGeometry (ViewGeometry, position))
 import Chiasma.Ui.Data.ViewState (ViewState)
 import Chiasma.View (findOrCreateView, viewsLog, viewsLogS)
 import qualified Chiasma.View as Views (insertPane, insertWindow, pane, paneById, updatePane, updateWindow, window)
-import Control.Monad.Error.Class (MonadError)
-import Control.Monad.Free.Class (MonadFree)
-import qualified Data.List.NonEmpty as NonEmpty (head, nonEmpty)
-import Data.Text.Prettyprint.Doc (line, pretty, vsep, (<+>))
 
 findOrCreateWindow ::
-  (MonadDeepState s Views m) =>
+  Member (AtomicState Views) r =>
   Ident ->
-  m (Tmux.View WindowId)
+  Sem r (Tmux.View WindowId)
 findOrCreateWindow =
   findOrCreateView Views.window Views.insertWindow
 
 registerWindowId ::
-  (MonadDeepState s Views m) =>
+  Member (AtomicState Views) r =>
   Ident ->
   WindowId ->
-  m ()
+  Sem r ()
 registerWindowId ident windowId =
-  modify $ Views.updateWindow $ Tmux.View ident (Just windowId)
+  atomicModify' (Views.updateWindow (Tmux.View ident (Just windowId)))
 
 spawnWindow ::
-  (MonadDeepState s Views m, MonadFree TmuxThunk m) =>
+  Members [AtomicState Views, Tmux] r =>
   SessionId ->
   Ident ->
-  m Codec.Window
+  Sem r Codec.Window
 spawnWindow sid ident = do
   win@(Codec.Window windowId _ _) <- Cmd.newWindow sid ident
   registerWindowId ident windowId
   viewsLogS $ "spawned window in session " <> show sid <> " with id " <> show windowId
-  return win
+  pure win
 
 findPrincipalSub :: ViewTreeSub -> Maybe Ui.PaneView
 findPrincipalSub (TreeNode t) = findPrincipal t
@@ -67,109 +69,112 @@
 
 findPrincipal :: ViewTree -> Maybe Ui.PaneView
 findPrincipal (Tree _ sub) =
-  findMaybe findPrincipalSub sub
+  firstJust findPrincipalSub sub
 
 principalPane ::
-  (MonadDeepState s Views m, MonadError RenderError m) =>
+  Members [AtomicState Views, Tmux, Stop RenderError] r =>
   ViewTree ->
-  m (Ui.PaneView, Tmux.View PaneId)
+  Sem r (Ui.PaneView, Tmux.View PaneId)
 principalPane tree = do
-  uiPane@(Ui.View uiPaneIdent _ _ _) <- maybeExcept (RenderError.NoPrincipal $ identify tree) $ findPrincipal tree
-  existingTmuxPane <- gets $ Views.pane uiPaneIdent
-  tmuxPane <- either (const $ addPane uiPaneIdent) return existingTmuxPane
-  return (uiPane, tmuxPane)
+  uiPane@(Ui.View uiPaneIdent _ _ _) <- stopNote (RenderError.NoPrincipal (identify tree)) $ (findPrincipal tree)
+  existingTmuxPane <- atomicGets (Views.pane uiPaneIdent)
+  tmuxPane <- either (const $ addPane uiPaneIdent) pure existingTmuxPane
+  pure (uiPane, tmuxPane)
 
 syncPrincipal ::
-  (MonadDeepState s Views m, MonadFree TmuxThunk m, MonadError RenderError m) =>
+  Members [TmuxPanes Pane, AtomicState Views, Tmux, Stop RenderError] r =>
   WindowId ->
   ViewTree ->
-  m ()
+  Sem r ()
 syncPrincipal windowId tree@(Tree (Ui.View layoutIdent _ _ _) _) = do
-  (Codec.Pane paneId _ _) <- Cmd.firstWindowPane windowId
-  existing <- gets (Views.paneById paneId)
+  (Codec.Pane paneId _ _ _ _) <- Cmd.firstWindowPane windowId
+  existing <- atomicGets (Views.paneById paneId)
   case existing of
     Nothing -> do
       (_, Tmux.View paneIdent _) <- principalPane tree
       viewsLog $ "setting principal of layout" <+> pretty layoutIdent <+> " to pane " <+> pretty paneIdent <+> "/" <+>
         pretty paneId
-      modify $ Views.updatePane (Tmux.View paneIdent (Just paneId))
-    _ -> return ()
+      atomicModify' $ Views.updatePane (Tmux.View paneIdent (Just paneId))
+    _ -> pure ()
 
 ensureWindow ::
-  (MonadDeepState s Views m, MonadFree TmuxThunk m, MonadError RenderError m) =>
+  Members [TmuxPanes Pane, AtomicState Views, Tmux, Stop RenderError] r =>
   SessionId ->
   Tmux.View WindowId ->
   Maybe WindowId ->
   ViewTree ->
-  m Codec.Window
+  Sem r Codec.Window
 ensureWindow sid (Tmux.View ident mayWid) newSessionWid tree = do
-  preexisting <- join <$> traverse Cmd.window (orElse newSessionWid mayWid)
-  window <- maybe (spawnWindow sid ident) return preexisting
+  preexisting <- join <$> traverse Cmd.window (newSessionWid <|> mayWid)
+  window <- maybe (spawnWindow sid ident) pure preexisting
   syncPrincipal (Codec.windowId window) tree
-  return window
+  pure window
 
-findOrCreatePane :: MonadDeepState s Views m => Ident -> m (Tmux.View PaneId)
+findOrCreatePane ::
+  Member (AtomicState Views) r =>
+  Ident ->
+  Sem r (Tmux.View PaneId)
 findOrCreatePane =
   findOrCreateView Views.pane Views.insertPane
 
 nativePane ::
-  MonadFree TmuxThunk m =>
+  Member (TmuxPanes Pane) r =>
   WindowId ->
   Tmux.View PaneId ->
-  m (Maybe Codec.PaneDetail)
+  Sem r (Maybe Pane)
 nativePane windowId (Tmux.View _ (Just paneId)) =
-  find sameId <$> Cmd.windowPanesAs windowId
+  find sameId <$> Cmd.windowPanes windowId
   where
-    sameId (Codec.PaneDetail i _ _ _ _) = i == paneId
-nativePane _ _ = return Nothing
+    sameId (Pane i _ _ _ _) = i == paneId
+nativePane _ _ = pure Nothing
 
 openPane ::
-  (MonadDeepState s Views m, MonadFree TmuxThunk m) =>
-  FilePath ->
+  Members [AtomicState Views, Tmux] r =>
+  Path Abs Dir ->
   WindowId ->
-  m Codec.PaneDetail
+  Sem r Pane
 openPane dir windowId = do
-  detail <- Cmd.splitWindowAs dir windowId
-  viewsLogS $ "opened pane " <> show (PaneDetail.paneId detail) <> " in window " <> show windowId
-  return detail
+  detail <- Cmd.splitWindowInDir dir windowId
+  viewsLogS $ "opened pane " <> show (Pane.paneId detail) <> " in window " <> show windowId
+  pure detail
 
 ensurePaneOpen ::
-  (MonadDeepState s Views m, MonadFree TmuxThunk m) =>
-  FilePath ->
-  Maybe Codec.PaneDetail ->
+  Members [AtomicState Views, Tmux] r =>
+  Path Abs Dir ->
+  Maybe Pane ->
   WindowId ->
-  m Codec.PaneDetail
+  Sem r Pane
 ensurePaneOpen _ (Just detail) _ =
-  return detail
+  pure detail
 ensurePaneOpen dir Nothing windowId =
   openPane dir windowId
 
 ensurePaneClosed ::
-  (MonadDeepState s Views m, MonadFree TmuxThunk m) =>
-  Maybe Codec.PaneDetail ->
-  m ()
-ensurePaneClosed (Just (Codec.PaneDetail i _ _ _ _)) = do
+  Members [AtomicState Views, Tmux] r =>
+  Maybe Pane ->
+  Sem r ()
+ensurePaneClosed (Just (Pane i _ _ _ _)) = do
   viewsLogS $ "closing pane " <> show i
   Cmd.closePane i
-ensurePaneClosed _ = return ()
+ensurePaneClosed _ = pure ()
 
 ensurePane ::
-  (MonadDeepState s Views m, MonadFree TmuxThunk m) =>
-  FilePath ->
+  Members [TmuxPanes Pane, AtomicState Views, Tmux] r =>
+  Path Abs Dir ->
   WindowId ->
   Ui.PaneView ->
-  m (Maybe RenderableNode)
+  Sem r (Maybe RenderableNode)
 ensurePane cwd windowId (Ui.View paneIdent vState geometry (Ui.Pane open _ customDir)) = do
   tmuxPane <- findOrCreatePane paneIdent
   existingPane <- nativePane windowId tmuxPane
   updatedPane <-
     if open then Just <$> ensurePaneOpen dir existingPane windowId
     else Nothing <$ ensurePaneClosed existingPane
-  modify $ Views.updatePane (Tmux.View paneIdent (PaneDetail.paneId <$> updatedPane))
-  return $ cons <$> updatedPane
+  atomicModify' $ Views.updatePane (Tmux.View paneIdent (Pane.paneId <$> updatedPane))
+  pure $ cons <$> updatedPane
   where
-    dir = fromMaybe cwd customDir
-    cons (Codec.PaneDetail i _ _ top left) =
+    dir = fromMaybe cwd (parseAbsDir . toString =<< customDir)
+    cons (Pane i _ _ top left) =
       Tree.Leaf . Renderable vState geometry $ RPane i top left
 
 refPane :: RenderableNode -> RPane
@@ -179,12 +184,12 @@
 renderableTree ::
   ViewState ->
   ViewGeometry ->
-  Bool ->
+  Axis ->
   [RenderableNode] ->
   Maybe RenderableTree
-renderableTree vState geometry vertical sub = do
+renderableTree vState geometry axis sub = do
   sub' <- NonEmpty.nonEmpty sub
-  return $ Tree.Tree (Renderable vState geometry (RLayout (refPane $ NonEmpty.head sub') vertical)) sub'
+  pure $ Tree.Tree (Renderable vState geometry (RLayout (refPane $ NonEmpty.head sub') axis)) sub'
 
 viewPosition :: ViewTreeSub -> Float
 viewPosition (TreeNode (Tree (Ui.View _ _ ViewGeometry { position = pos } _) _)) =
@@ -193,33 +198,33 @@
   fromMaybe 0.5 pos
 
 ensureView ::
-  (MonadDeepState s Views m, MonadFree TmuxThunk m) =>
-  FilePath ->
+  Members [TmuxPanes Pane, AtomicState Views, Tmux] r =>
+  Path Abs Dir ->
   WindowId ->
   ViewTree ->
-  m (Maybe RenderableTree)
+  Sem r (Maybe RenderableTree)
 ensureView cwd windowId =
   ensureTree
   where
-    ensureTree (Tree (Ui.View layoutIdent vState geometry (Ui.Layout vertical)) sub) = do
+    ensureTree (Tree (Ui.View layoutIdent vState geometry (Ui.Layout axis)) sub) = do
       ensuredSub <- traverse ensureNode sortedSub
       viewsLog $ pretty ("new sub for layout `" <> identText layoutIdent <> "`:") <> line <>
         vsep (pretty <$> ensuredSub)
-      return $ renderableTree vState geometry vertical $ catMaybes ensuredSub
+      pure $ renderableTree vState geometry axis $ catMaybes ensuredSub
       where
         sortedSub = sortOn viewPosition sub
     ensureNode (TreeNode t) = do
       newTree <- ensureTree t
-      return $ Tree.Sub <$> newTree
+      pure $ Tree.Sub <$> newTree
     ensureNode (TreeLeaf v) =
       ensurePane cwd windowId v
 
 windowState ::
-  (MonadDeepState s Views m, MonadFree TmuxThunk m) =>
+  Member (TmuxPanes Pane) r =>
   Ident ->
   Codec.Window ->
   RenderableTree ->
-  m WindowState
+  Sem r WindowState
 windowState windowIdent window tree = do
   nativeRef <- Cmd.firstWindowPane (Codec.windowId window)
-  return $ WindowState window nativeRef windowIdent tree (Codec.paneId nativeRef)
+  pure $ WindowState window nativeRef windowIdent tree (Codec.paneId nativeRef)
diff --git a/lib/Prelude.hs b/lib/Prelude.hs
deleted file mode 100644
--- a/lib/Prelude.hs
+++ /dev/null
@@ -1,5 +0,0 @@
-module Prelude (
-  module Chiasma.Prelude,
-) where
-
-import Chiasma.Prelude
diff --git a/readme.md b/readme.md
deleted file mode 100644
--- a/readme.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# Intro
-
-**chiasma** is a tmux api client for Haskell.
diff --git a/test/Chiasma/Test/CodecTest.hs b/test/Chiasma/Test/CodecTest.hs
deleted file mode 100644
--- a/test/Chiasma/Test/CodecTest.hs
+++ /dev/null
@@ -1,24 +0,0 @@
-module Chiasma.Test.CodecTest where
-
-import Chiasma.Codec (TmuxCodec(decode, query), TmuxQuery(TmuxQuery))
-import Chiasma.Data.TmuxId (PaneId(..), WindowId(..))
-import Hedgehog ((===))
-
-import Chiasma.Test.Util (UnitTest)
-
-data Dat =
-  Dat {
-    paneId :: PaneId,
-    windowId :: WindowId
-  }
-  deriving (Eq, Show, Generic)
-
-instance TmuxCodec Dat
-
-test_decode :: UnitTest
-test_decode =
-  Right (Dat (PaneId 1) (WindowId 2)) === decode " %1 @2 "
-
-test_query :: UnitTest
-test_query =
-  TmuxQuery "#{pane_id} #{window_id}" === query @Dat
diff --git a/test/Chiasma/Test/LensTest.hs b/test/Chiasma/Test/LensTest.hs
deleted file mode 100644
--- a/test/Chiasma/Test/LensTest.hs
+++ /dev/null
@@ -1,102 +0,0 @@
-module Chiasma.Test.LensTest where
-
-import Chiasma.Data.Ident (Ident(Str))
-import Chiasma.Lens.Tree (leafByIdent, modifyLeafByIdent, treesAndSubs)
-import Chiasma.Ui.Data.View (
-  Pane(Pane),
-  PaneView,
-  Tree(Tree),
-  TreeSub(TreeNode, TreeLeaf),
-  View(View),
-  ViewTree,
-  ViewTreeSub,
-  consLayout,
-  consPane,
-  )
-import qualified Chiasma.Ui.Data.View as View (_ident, ident)
-import Chiasma.Ui.Data.ViewState (ViewState(ViewState))
-import Chiasma.Ui.ViewTree (togglePane)
-import qualified Chiasma.Ui.ViewTree as ToggleResult (ToggleResult(..))
-import Control.Lens (transformM)
-import qualified Control.Lens as Lens (set)
-import Hedgehog ((===))
-import Test.Tasty (TestTree, testGroup)
-import Chiasma.Test.Util (UnitTest, unitTest)
-
-id0, id1, id2, id3, id4 :: Ident
-id0 = Str "0"
-id1 = Str "1"
-id2 = Str "2"
-id3 = Str "3"
-id4 = Str "4"
-
-tree :: ViewTree
-tree =
-  Tree (consLayout id0) [subtree, TreeLeaf (consPane id2)]
-  where
-    subtree = st id2 subtree2
-    subtree2 = st id3 subtree3
-    subtree3 = st id4 (TreeLeaf openPane)
-    openPane = View id1 (ViewState False) def (Pane True False Nothing)
-    st i s =
-      TreeNode $ Tree (consLayout i) [s]
-
-test_modify :: UnitTest
-test_modify = do
-  let
-    ident = Str "changed"
-    modded = modifyLeafByIdent id1 (Lens.set View.ident ident) tree
-  Nothing === leafByIdent ident tree
-  Just ident === (View._ident <$> leafByIdent ident modded)
-
-failOnPaneIdent :: Ident -> ViewTree -> Maybe ViewTree
-failOnPaneIdent target t@(Tree _ sub) =
-  t <$ traverse match sub
-  where
-    match (TreeLeaf (View i _ _ _)) = if target == i then Nothing else Just ()
-    match _ = Just ()
-
-test_monadicModify :: UnitTest
-test_monadicModify = do
-  Nothing === (transformM (failOnPaneIdent id2) tree)
-  Just tree === (transformM (failOnPaneIdent id4) tree)
-
-insertPane :: Ident -> PaneView -> ViewTree -> ViewTree
-insertPane targetLayout pane (Tree l sub) =
-  if View._ident l == targetLayout then Tree l (TreeLeaf pane : sub) else Tree l sub
-
-ensurePaneUnique :: Ident -> ViewTreeSub -> Maybe ViewTreeSub
-ensurePaneUnique paneIdent (TreeLeaf (View ident _ _ _)) | ident == paneIdent = Nothing
-ensurePaneUnique _ n = Just n
-
-subtreesTarget :: ViewTree
-subtreesTarget =
-  Tree (consLayout id0) [subtree, TreeLeaf (consPane id2)]
-  where
-    subtree = TreeNode $ Tree (consLayout id2) [TreeLeaf $ consPane id4, subtree2]
-    subtree2 = TreeNode $ Tree (consLayout id3) [subtree3]
-    subtree3 = TreeNode $ Tree (consLayout id4) [TreeLeaf openPane]
-    openPane = View id1 (ViewState False) def (Pane True False Nothing)
-
-test_subtrees :: UnitTest
-test_subtrees =
-  Just subtreesTarget === treesAndSubs (Just . insertPane id2 (consPane id4)) (ensurePaneUnique id4) tree
-
-togglePaneTree :: ViewTree
-togglePaneTree =
-  Tree (consLayout id0) [TreeLeaf (consPane id0), TreeLeaf (consPane id0), TreeLeaf (consPane id2)]
-
-test_togglePane :: UnitTest
-test_togglePane = do
-  ToggleResult.Ambiguous 2 === togglePane id0 togglePaneTree
-  ToggleResult.NotFound === togglePane id1 togglePaneTree
-  ToggleResult.Success (1 :: Int) === (1 <$ togglePane id2 togglePaneTree)
-
-test_lenses :: TestTree
-test_lenses =
-  testGroup "lenses" [
-    unitTest "modify leaves by ident" test_modify,
-    unitTest "monadically transform leaves" test_monadicModify,
-    unitTest "traverse all subtrees" test_subtrees,
-    unitTest "toggle a pane" test_togglePane
-  ]
diff --git a/test/Chiasma/Test/OutputParseTest.hs b/test/Chiasma/Test/OutputParseTest.hs
deleted file mode 100644
--- a/test/Chiasma/Test/OutputParseTest.hs
+++ /dev/null
@@ -1,29 +0,0 @@
-module Chiasma.Test.OutputParseTest where
-
-import Chiasma.Native.Parse (resultLines)
-import qualified Data.Text as T (unlines)
-import Hedgehog ((===))
-
-import Chiasma.Test.Util (UnitTest)
-
-paneLine :: Text
-paneLine = "%0 100 100"
-
-tmuxOutput :: Text
-tmuxOutput = T.unlines [
-  "%begin 123",
-  "%end 123",
-  "%session-changed $0 0",
-  "%begin 234",
-  paneLine,
-  "b",
-  "%end 234",
-  "%begin 345",
-  "c",
-  "d",
-  "%end 345"
-  ]
-
-test_outputParse :: UnitTest
-test_outputParse =
-  Right [[], [paneLine, "b"], ["c", "d"]] === resultLines tmuxOutput
diff --git a/test/Chiasma/Test/PinTest.hs b/test/Chiasma/Test/PinTest.hs
deleted file mode 100644
--- a/test/Chiasma/Test/PinTest.hs
+++ /dev/null
@@ -1,98 +0,0 @@
-module Chiasma.Test.PinTest where
-
-import Chiasma.Data.Ident (Ident(Str))
-import Chiasma.Ui.Data.View (
-  Pane(Pane),
-  Tree(Tree),
-  TreeSub(TreeNode, TreeLeaf),
-  View(View),
-  ViewTree,
-  ViewTreeSub,
-  consLayout,
-  )
-import Chiasma.Ui.ViewTree (toggleLayout, togglePane)
-import qualified Chiasma.Ui.ViewTree as ToggleResult (ToggleResult(..))
-import Hedgehog ((===))
-import Test.Tasty (TestTree, testGroup)
-
-import Chiasma.Test.Util (UnitTest, unitTest)
-
-node :: ViewTreeSub -> View Pane -> ViewTreeSub
-node sub p =
-  TreeNode $ Tree (consLayout "l") [sub, TreeLeaf p]
-
-paneWith :: Bool -> Text -> ViewTreeSub
-paneWith open i = TreeLeaf $ View (Str i) def def (Pane open False Nothing)
-
-pane :: Text -> ViewTreeSub
-pane =
-  paneWith False
-
-ppaneWithIdent :: Text -> Bool -> View Pane
-ppaneWithIdent name open =
-  View (Str name) def def (Pane open True Nothing)
-
-ppaneWith :: Bool -> View Pane
-ppaneWith =
-  ppaneWithIdent "pin"
-
-ppane :: View Pane
-ppane =
-  ppaneWith False
-
-subtree4 :: ViewTreeSub
-subtree4 = node subtree5 ppane
-
-subtree5 :: ViewTreeSub
-subtree5 = node subtree6 ppane
-
-subtree6 :: ViewTreeSub
-subtree6 = TreeNode $ Tree (consLayout (Str "l")) [pane "p2"]
-
-tree :: ViewTree
-tree =
-  Tree (consLayout (Str "root")) [subtree4, subtree1, subtree4]
-  where
-    subtree1 = node subtree2 ppane
-    subtree2 = node subtree3 ppane
-    subtree3 = TreeNode $ Tree (consLayout (Str "l1")) [pane "p1"]
-
-target :: ViewTree
-target =
-  Tree (consLayout (Str "root")) [subtree4, subtree1, subtree4]
-  where
-    subtree1 = node subtree2 (ppaneWith True)
-    subtree2 = node subtree3 (ppaneWith True)
-    subtree3 = TreeNode $ Tree (consLayout (Str "l1")) [TreeLeaf $ View (Str "p1") def def (Pane True False Nothing)]
-
-test_pinOpenNonpinned :: UnitTest
-test_pinOpenNonpinned =
-  ToggleResult.Success target === togglePane (Str "p1") tree
-
-test_layoutPinOpenNonpinned :: UnitTest
-test_layoutPinOpenNonpinned =
-  ToggleResult.Success target === toggleLayout (Str "l1") tree
-
-pinnedTree :: ViewTree
-pinnedTree =
-  Tree (consLayout (Str "root")) [pane "left", subtree]
-  where
-    subtree = TreeNode $ Tree (consLayout (Str "l1")) [pane "p2", TreeLeaf $ ppaneWithIdent "p1" False]
-
-pinnedTarget :: ViewTree
-pinnedTarget =
-  Tree (consLayout (Str "root")) [pane "left", subtree]
-  where
-    subtree = TreeNode $ Tree (consLayout (Str "l1")) [paneWith False "p2", TreeLeaf $ ppaneWithIdent "p1" True]
-
-test_pinOpenPinned :: UnitTest
-test_pinOpenPinned =
-  ToggleResult.Success pinnedTarget === toggleLayout (Str "l1") pinnedTree
-
-test_pin :: TestTree
-test_pin =
-  testGroup "pin" [
-    unitTest "open an unpinned pane" test_pinOpenNonpinned,
-    unitTest "open an unpinned layout" test_layoutPinOpenNonpinned,
-    unitTest "open a pinned pane" test_pinOpenPinned
-  ]
diff --git a/test/Chiasma/Test/StreamParseTest.hs b/test/Chiasma/Test/StreamParseTest.hs
deleted file mode 100644
--- a/test/Chiasma/Test/StreamParseTest.hs
+++ /dev/null
@@ -1,30 +0,0 @@
-module Chiasma.Test.StreamParseTest where
-
-import Chiasma.Native.StreamParse (TmuxOutputBlock(Success), parseConduit)
-import Conduit
-import Data.ByteString.Internal (packChars)
-import Data.Conduit.List
-import qualified Data.Text as T (unpack)
-import Hedgehog ((===))
-
-import Chiasma.Test.Util (UnitTest)
-
-paneLine :: Text
-paneLine = "%0 100 100"
-
-tmuxOutput :: [Text]
-tmuxOutput = [
-  "%begin 123\n",
-  "%end 123\n",
-  "%session-changed $0 0\n%begin 234\n",
-  paneLine <> "\nb\n%end 234\n",
-  "%begin 345\n",
-  "c\n",
-  "d\n",
-  "%end 345\n"
-  ]
-
-test_streamParse :: UnitTest
-test_streamParse = do
-  r <- runConduit $ sourceList tmuxOutput .| mapC (packChars . T.unpack) .| parseConduit .| sinkList
-  [Success [], Success [paneLine, "b"], Success ["c", "d"]] === r
diff --git a/test/Chiasma/Test/Util.hs b/test/Chiasma/Test/Util.hs
deleted file mode 100644
--- a/test/Chiasma/Test/Util.hs
+++ /dev/null
@@ -1,14 +0,0 @@
-module Chiasma.Test.Util where
-
-import Hedgehog (TestT, withTests, property, test)
-import Test.Tasty (TestName, TestTree)
-import Test.Tasty.Hedgehog (testProperty)
-
-type UnitTest = TestT IO ()
-
-unitTest ::
-  TestName ->
-  UnitTest ->
-  TestTree
-unitTest desc =
-  testProperty desc . withTests 1 . property . test
diff --git a/test/Main.hs b/test/Main.hs
deleted file mode 100644
--- a/test/Main.hs
+++ /dev/null
@@ -1,24 +0,0 @@
-module Main where
-
-import Chiasma.Test.CodecTest (test_decode, test_query)
-import Chiasma.Test.LensTest (test_lenses)
-import Chiasma.Test.OutputParseTest (test_outputParse)
-import Chiasma.Test.PinTest (test_pin)
-import Chiasma.Test.StreamParseTest (test_streamParse)
-import Chiasma.Test.Util (unitTest)
-import Test.Tasty (TestTree, defaultMain, testGroup)
-
-tests :: TestTree
-tests =
-  testGroup "all" [
-    unitTest "tmux-decode a value" test_decode,
-    unitTest "tmux-encode a query" test_query,
-    test_lenses,
-    unitTest "parse tmux output" test_outputParse,
-    test_pin,
-    unitTest "parse tmux output in stream" test_streamParse
-  ]
-
-main :: IO ()
-main =
-  defaultMain tests
