diff --git a/essence-of-live-coding-warp.cabal b/essence-of-live-coding-warp.cabal
--- a/essence-of-live-coding-warp.cabal
+++ b/essence-of-live-coding-warp.cabal
@@ -1,5 +1,5 @@
 name:                essence-of-live-coding-warp
-version:             0.2.5
+version:             0.2.6
 synopsis: General purpose live coding framework
 description:
   essence-of-live-coding is a general purpose and type safe live coding framework.
@@ -33,7 +33,7 @@
 source-repository this
   type:     git
   location: git@github.com:turion/essence-of-live-coding.git
-  tag:      v0.2.5
+  tag:      v0.2.6
 
 library
   exposed-modules:
@@ -44,7 +44,7 @@
     , http-types >= 0.12.3
     , wai >= 3.2.2.1
     , warp >= 3.3.13
-    , essence-of-live-coding >= 0.2.5
+    , essence-of-live-coding >= 0.2.6
   hs-source-dirs:      src
   default-language:    Haskell2010
   default-extensions:  StrictData
@@ -55,7 +55,8 @@
   hs-source-dirs: test
   build-depends:
       base >= 4.11 && < 5
-    , http-client >= 0.7.1
-    , essence-of-live-coding >= 0.2.5
+    , http-client >= 0.6.4.1
+    , bytestring >= 0.10
+    , essence-of-live-coding >= 0.2.6
     , essence-of-live-coding-warp
   default-language:    Haskell2010
diff --git a/src/LiveCoding/Warp.hs b/src/LiveCoding/Warp.hs
--- a/src/LiveCoding/Warp.hs
+++ b/src/LiveCoding/Warp.hs
@@ -1,6 +1,11 @@
 {-# LANGUAGE Arrows #-}
 {-# LANGUAGE RecordWildCards #-}
 {-# LANGUAGE TupleSections #-}
+{- | Live coding backend to the [@warp@](https://hackage.haskell.org/package/warp) server.
+
+If you write a cell that consumes 'Request's and produces 'Response's,
+you can use the functions here that run this cell as a @warp@ application.
+-}
 module LiveCoding.Warp
   ( runWarpC
   , runWarpC_
@@ -69,8 +74,27 @@
       arrM $ liftIO . threadDelay     -< 1000 -- Prevent too much CPU load
       returnA                         -< Nothing
 
+-- | A simple live-codable web application is a cell that consumes HTTP 'Request's and emits 'Response's for each.
+type LiveWebApp = Cell IO Request Response
+
+{- | Like 'runWarpC', but don't consume additional input or produce additional output.
+
+Suitable for a main program, for example like this:
+
+@
+mainCell :: Cell IO Request Response
+mainCell = undefined
+
+liveProgram :: LiveProgram (HandlingStateT IO)
+liveProgram = liveCell mainCell
+
+main :: IO ()
+main = liveMain liveProgram
+@
+-}
+
 runWarpC_
   :: Port
-  -> Cell IO Request Response
+  -> LiveWebApp
   -> Cell (HandlingStateT IO) () ()
 runWarpC_ port cell = runWarpC port (arr snd >>> cell >>> arr ((), )) >>> arr (const ())
diff --git a/test/Main.hs b/test/Main.hs
--- a/test/Main.hs
+++ b/test/Main.hs
@@ -1,9 +1,12 @@
-{-# LANGUAGE Arrows #-}
 {-# LANGUAGE OverloadedStrings #-}
 
 -- base
+import Control.Monad (unless)
 import System.Exit
 
+-- bytestring
+import Data.ByteString.Lazy
+
 -- http-client
 import Network.HTTP.Client hiding (Response)
 
@@ -13,15 +16,22 @@
 -- essence-of-live-coding-warp
 import LiveCoding.Warp
 
-response :: Response
-response = responseLBS status200 [("Content-Type", "text/plain")] "hai"
+response :: ByteString -> Response
+response = responseLBS status200 [("Content-Type", "text/plain")]
 
-main :: IO ()
-main = do
-  launch $ liveCell $ runHandlingStateC $ runWarpC_ 8080 $ arr $ const response
-  manager <- newManager $ defaultManagerSettings
+liveProgram :: ByteString -> LiveProgram (HandlingStateT IO)
+liveProgram = liveCell . runWarpC_ 8080 . arr . const . response
+
+testRequest :: Manager -> ByteString -> IO ()
+testRequest manager expected = do
   request <- parseRequest "http://localhost:8080"
   response <- httpLbs request manager
-  if responseBody response == "hai"
-    then return ()
-    else exitFailure
+  unless (responseBody response == expected) exitFailure
+
+main :: IO ()
+main = do
+  launchedProgram <- launch $ liveProgram "hai"
+  manager <- newManager defaultManagerSettings
+  testRequest manager "hai"
+  update launchedProgram $ liveProgram "hellooo"
+  testRequest manager "hellooo"
