diff --git a/CHANGES b/CHANGES
new file mode 100644
--- /dev/null
+++ b/CHANGES
@@ -0,0 +1,24 @@
+0.22.5 (2014/4/15)
+
+- allow http-client 0.3.*, fixing cabal install again with GHC <= 7.6 (not yet 7.8)
+- use pretty-show only with GHC 7.4+, fixing GHC 7.2 (fixes #155)
+- allow warp 2.1, fixing cabal install
+
+0.22.4 (2014/2/10)
+
+- Fix: include the right unminified version of jquery.url.js (1.1) to avoid js breakage
+
+0.22.3 (2014/2/10)
+
+- Fix: version number reported by --version
+
+0.22.2 (2014/2/10)
+
+- new option --static-root to set the base url for static files
+- allow blaze-html 0.7 (#159)
+- Fix: include unminified source of all javascript to help packagers (#161)
+- Fix: work around clang-related build failures with OS X mavericks/XCode 5
+
+0.22.1 (2014/1/6) and older
+
+See http://hledger.org/release-notes or doc/CHANGES.md.
diff --git a/CHANGES.md b/CHANGES.md
deleted file mode 100644
--- a/CHANGES.md
+++ /dev/null
@@ -1,19 +0,0 @@
-## 2014/2/10 hledger-web 0.22.4
-
-* web: include the right unminified version of jquery.url.js (1.1) to avoid js breakage
-
-## 2014/2/10 hledger-web 0.22.3
-
-* web: fix version number reported by --version
-
-## 2014/2/10 hledger-web 0.22.2
-
-New:
-
-* web: new option `--static-root` to set the base url for static files
-
-Improved:
-
-* web: include unminified source of all javascript to help packagers (fixes #161)
-* web: work around clang-related build failures with OS X mavericks/XCode 5
-* web: allow blaze-html 0.7 (closes #159)
diff --git a/Foundation.hs b/Foundation.hs
--- a/Foundation.hs
+++ b/Foundation.hs
@@ -19,7 +19,7 @@
 -- import qualified Settings
 import Settings.Development (development)
 import Settings.StaticFiles
-import Settings (staticRoot, widgetFile, Extra (..))
+import Settings (widgetFile, Extra (..))
 #ifndef DEVELOPMENT
 import Settings (staticDir)
 import Text.Jasmine (minifym)
@@ -117,11 +117,11 @@
 
         hamletToRepHtml $(hamletFile "templates/default-layout-wrapper.hamlet")
 
-    -- This is done to provide an optimization for serving static files from
-    -- a separate domain. Please see the staticRoot setting in Settings.hs
-    urlRenderOverride y (StaticR s) =
-        Just $ uncurry (joinPath y (Settings.staticRoot $ settings y)) $ renderRoute s
-    urlRenderOverride _ _ = Nothing
+    -- -- This is done to provide an optimization for serving static files from
+    -- -- a separate domain. Please see the staticRoot setting in Settings.hs
+    -- urlRenderOverride y (StaticR s) =
+    --     Just $ uncurry (joinPath y (Settings.staticRoot $ settings y)) $ renderRoute s
+    -- urlRenderOverride _ _ = Nothing
 
 #ifndef DEVELOPMENT
     -- This function creates static content files in the static folder
diff --git a/Hledger/Web/Main.hs b/Hledger/Web/Main.hs
--- a/Hledger/Web/Main.hs
+++ b/Hledger/Web/Main.hs
@@ -20,7 +20,6 @@
 import Network.Wai.Handler.Launch (runUrlPort)
 --
 import Prelude hiding (putStrLn)
-import Control.Applicative ((<$>))
 import Control.Monad (when)
 import Data.Text (pack)
 import System.Exit (exitSuccess)
@@ -60,13 +59,12 @@
   let j' = filterJournalTransactions (queryFromOpts d $ reportopts_ $ cliopts_ opts) j
       p = port_ opts
       u = base_url_ opts
-      staticRoot = pack <$> static_root_ opts
   _ <- printf "Starting web app on port %d with base url %s\n" p u
   app <- makeApplication opts j' AppConfig{appEnv = Development
                                           ,appPort = p
                                           ,appRoot = pack u
                                           ,appHost = HostIPv4
-                                          ,appExtra = Extra "" Nothing staticRoot
+                                          ,appExtra = Extra "" Nothing
                                           }
   if server_ opts
    then do
diff --git a/Hledger/Web/Options.hs b/Hledger/Web/Options.hs
--- a/Hledger/Web/Options.hs
+++ b/Hledger/Web/Options.hs
@@ -1,7 +1,6 @@
 module Hledger.Web.Options
 where
 import Prelude
-import Control.Applicative ((<$>))
 import Data.Maybe
 import System.Console.CmdArgs
 import System.Console.CmdArgs.Explicit
@@ -27,7 +26,6 @@
   flagNone ["server"]  (setboolopt "server") ("log requests, don't auto-exit")
  ,flagReq ["base-url"]  (\s opts -> Right $ setopt "base-url" s opts) "URL" ("set the base url (default: "++defbaseurlexample++")")
  ,flagReq ["port"]  (\s opts -> Right $ setopt "port" s opts) "PORT" ("listen on this tcp port (default: "++show defport++")")
- ,flagReq ["static-root"]  (\s opts -> Right $ setopt "static-root" s opts) "Static Root" ("The root url from which the static files will be loaded (default: BASE_URL/static)")
  ]
  
 webmode :: Mode [([Char], [Char])]
@@ -49,7 +47,6 @@
      server_   :: Bool
     ,base_url_ :: String
     ,port_     :: Int
-    ,static_root_ :: Maybe String
     ,cliopts_  :: CliOpts
  } deriving (Show)
 
@@ -59,7 +56,6 @@
     def
     def
     def
-    def
 
 -- instance Default WebOpts where def = defwebopts
 
@@ -71,7 +67,6 @@
               port_ = p
              ,server_ = boolopt "server" rawopts
              ,base_url_ = maybe (defbaseurl p) stripTrailingSlash $ maybestringopt "base-url" rawopts
-             ,static_root_ = stripTrailingSlash <$> maybestringopt "static-root" rawopts
              ,cliopts_   = cliopts
              }
   where
diff --git a/Settings.hs b/Settings.hs
--- a/Settings.hs
+++ b/Settings.hs
@@ -54,10 +54,8 @@
 -- have to make a corresponding change here.
 --
 -- To see how this value is used, see urlRenderOverride in Foundation.hs
-staticRoot :: AppConfig DefaultEnv Extra -> Text
-staticRoot conf = case extraStaticRoot $ appExtra conf of
-                    Just root -> root
-                    Nothing -> [st|#{appRoot conf}/static|]
+staticRoot :: AppConfig DefaultEnv x -> Text
+staticRoot conf = [st|#{appRoot conf}/static|]
 
 -- | Settings for 'widgetFile', such as which template languages to support and
 -- default Hamlet settings.
@@ -77,13 +75,11 @@
               widgetFileSettings
 
 data Extra = Extra
-    { extraCopyright  :: Text
-    , extraAnalytics  :: Maybe Text -- ^ Google Analytics
-    , extraStaticRoot :: Maybe Text
+    { extraCopyright :: Text
+    , extraAnalytics :: Maybe Text -- ^ Google Analytics
     } deriving Show
 
 parseExtra :: DefaultEnv -> Object -> Parser Extra
 parseExtra _ o = Extra
     <$> o .:  "copyright"
     <*> o .:? "analytics"
-    <*> o .:? "staticRoot"
diff --git a/hledger-web.cabal b/hledger-web.cabal
--- a/hledger-web.cabal
+++ b/hledger-web.cabal
@@ -1,5 +1,5 @@
 name:           hledger-web
-version: 0.22.4
+version: 0.22.5
 stability:      beta
 category:       Finance
 synopsis:       A web interface for the hledger accounting tool.
@@ -48,7 +48,7 @@
                 templates/homepage.julius
                 templates/homepage.lucius
                 templates/normalize.lucius
-                CHANGES.md
+                CHANGES
 
 source-repository head
     type:     git
@@ -71,7 +71,7 @@
     Default:       False
 
 library
-    cpp-options:   -DVERSION="0.22.4"
+    cpp-options:   -DVERSION="0.22.5"
     if flag(dev) || flag(library-only)
         cpp-options: -DDEVELOPMENT
  
@@ -126,7 +126,7 @@
                    , hamlet
                    , hjsmin
                    , http-conduit
-                   , http-client          >= 0.2 && < 0.3
+                   , http-client          >= 0.2 && < 0.4
                    , HUnit
                    , network-conduit
                    , old-locale
@@ -141,7 +141,7 @@
                    , wai
                    , wai-extra
                    , wai-handler-launch   >= 1.3 && < 2.1
-                   , warp                 >= 1.3 && < 2.1
+                   , warp                 >= 1.3 && < 2.2
                    , yaml
                    , yesod                >= 1.2 && < 1.3
                    , yesod-core
@@ -162,7 +162,7 @@
     if flag(library-only)
         Buildable: False
 
-    cpp-options:   -DVERSION="0.22.4"
+    cpp-options:   -DVERSION="0.22.5"
     if flag(dev)
         cpp-options:   -DDEVELOPMENT
 
@@ -218,7 +218,7 @@
                    , hamlet
                    , hjsmin
                    , http-conduit
-                   , http-client          >= 0.2 && < 0.3
+                   , http-client          >= 0.2 && < 0.4
                    , HUnit
                    , network-conduit
                    , old-locale
@@ -233,7 +233,7 @@
                    , wai
                    , wai-extra
                    , wai-handler-launch   >= 1.3 && < 2.1
-                   , warp                 >= 1.3 && < 2.1
+                   , warp                 >= 1.3 && < 2.2
                    , yaml
                    , yesod                >= 1.2 && < 1.3
                    , yesod-core
diff --git a/static/jquery.url.js b/static/jquery.url.js
--- a/static/jquery.url.js
+++ b/static/jquery.url.js
@@ -1,181 +1,164 @@
-// JQuery URL Parser
+// JQuery URL Parser plugin - https://github.com/allmarkedup/jQuery-URL-Parser
 // Written by Mark Perkins, mark@allmarkedup.com
 // License: http://unlicense.org/ (i.e. do what you want with it!)
 
-jQuery.url = function()
-{
-	var segments = {};
+;(function($, undefined) {
+    
+    var tag2attr = {
+        a       : 'href',
+        img     : 'src',
+        form    : 'action',
+        base    : 'href',
+        script  : 'src',
+        iframe  : 'src',
+        link    : 'href'
+    },
+    
+	key = ["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","fragment"], // keys available to query
 	
-	var parsed = {};
+	aliases = { "anchor" : "fragment" }, // aliases for backwards compatability
+
+	parser = {
+		strict  : /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,  //less intuitive, more accurate to the specs
+		loose   :  /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/ // more intuitive, fails on relative paths and deviates from specs
+	},
 	
-	/**
-    * Options object. Only the URI and strictMode values can be changed via the setters below.
-    */
-  	var options = {
+	querystring_parser = /(?:^|&|;)([^&=;]*)=?([^&;]*)/g, // supports both ampersand and semicolon-delimted query string key/value pairs
 	
-		url : window.location, // default URI is the page in which the script is running
-		
-		strictMode: false, // 'loose' parsing by default
+	fragment_parser = /(?:^|&|;)([^&=;]*)=?([^&;]*)/g; // supports both ampersand and semicolon-delimted fragment key/value pairs
 	
-		key: ["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], // keys available to query 
-		
-		q: {
-			name: "queryKey",
-			parser: /(?:^|&|;)([^&=;]*)=?([^&;]*)/g
-		},
+	function parseUri( url, strictMode )
+	{
+		var str = decodeURI( url ),
+		    res   = parser[ strictMode || false ? "strict" : "loose" ].exec( str ),
+		    uri = { attr : {}, param : {}, seg : {} },
+		    i   = 14;
 		
-		parser: {
-			strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,  //less intuitive, more accurate to the specs
-			loose:  /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/ // more intuitive, fails on relative paths and deviates from specs
+		while ( i-- )
+		{
+			uri.attr[ key[i] ] = res[i] || "";
 		}
 		
-	};
-	
-    /**
-     * Deals with the parsing of the URI according to the regex above.
- 	 * Written by Steven Levithan - see credits at top.
-     */		
-	var parseUri = function()
-	{
-		str = decodeURI( options.url );
+		// build query and fragment parameters
 		
-		var m = options.parser[ options.strictMode ? "strict" : "loose" ].exec( str );
-		var uri = {};
-		var i = 14;
-
-		while ( i-- ) {
-			uri[ options.key[i] ] = m[i] || "";
-		}
-
-		uri[ options.q.name ] = {};
-		uri[ options.key[12] ].replace( options.q.parser, function ( $0, $1, $2 ) {
-			if ($1) {
-				uri[options.q.name][$1] = $2;
-			}
-		});
-
-		return uri;
-	};
-
-    /**
-     * Returns the value of the passed in key from the parsed URI.
-  	 * 
-	 * @param string key The key whose value is required
-     */		
-	var key = function( key )
-	{
-		if ( jQuery.isEmptyObject(parsed) )
-		{
-			setUp(); // if the URI has not been parsed yet then do this first...	
-		} 
-		if ( key == "base" )
-		{
-			if ( parsed.port !== null && parsed.port !== "" )
+		uri.param['query'] = {};
+		uri.param['fragment'] = {};
+		
+		uri.attr['query'].replace( querystring_parser, function ( $0, $1, $2 ){
+			if ($1)
 			{
-				return parsed.protocol+"://"+parsed.host+":"+parsed.port+"/";	
+				uri.param['query'][$1] = $2;
 			}
-			else
+		});
+		
+		uri.attr['fragment'].replace( fragment_parser, function ( $0, $1, $2 ){
+			if ($1)
 			{
-				return parsed.protocol+"://"+parsed.host+"/";
+				uri.param['fragment'][$1] = $2;
 			}
-		}
-	
-		return ( parsed[key] === "" ) ? null : parsed[key];
+		});
+				
+		// split path and fragement into segments
+		
+        uri.seg['path'] = uri.attr.path.replace(/^\/+|\/+$/g,'').split('/');
+        
+        uri.seg['fragment'] = uri.attr.fragment.replace(/^\/+|\/+$/g,'').split('/');
+        
+        // compile a 'base' domain attribute
+        
+        uri.attr['base'] = uri.attr.host ? uri.attr.protocol+"://"+uri.attr.host + (uri.attr.port ? ":"+uri.attr.port : '') : '';
+        
+		return uri;
 	};
 	
-	/**
-     * Returns the value of the required query string parameter.
-  	 * 
-	 * @param string item The parameter whose value is required
-     */		
-	var param = function( item )
-	{
-		if ( jQuery.isEmptyObject(parsed) )
-		{
-			setUp(); // if the URI has not been parsed yet then do this first...	
-		}
-		if ( item === undefined )
-		{
-            return parsed.queryKey;
-        }
-        else
-        {
-            return ( parsed.queryKey[item] === null ) ? null : parsed.queryKey[item];
-        }
-	};
-
-    /**
-     * 'Constructor' (not really!) function.
-     *  Called whenever the URI changes to kick off re-parsing of the URI and splitting it up into segments. 
-     */	
-	var setUp = function()
+	function getAttrName( elm )
 	{
-		parsed = parseUri();
-		
-		getSegments();	
-	};
+		var tn = elm.tagName;
+		if ( tn !== undefined ) return tag2attr[tn.toLowerCase()];
+		return tn;
+	}
 	
-    /**
-     * Splits up the body of the URI into segments (i.e. sections delimited by '/')
-     */
-	var getSegments = function()
+	$.fn.url = function( strictMode )
 	{
-		var p = parsed.path;
-		segments = []; // clear out segments array
-		segments = parsed.path.length == 1 ? {} : ( p.charAt( p.length - 1 ) == "/" ? p.substring( 1, p.length - 1 ) : path = p.substring( 1 ) ).split("/");
+	    var url = '';
+	    
+	    if ( this.length )
+	    {
+	        url = $(this).attr( getAttrName(this[0]) ) || '';
+	    }
+	    
+        return $.url({ url : url, strict : strictMode });
 	};
 	
-	return {
-		
-	    /**
-	     * Sets the parsing mode - either strict or loose. Set to loose by default.
-	     *
-	     * @param string mode The mode to set the parser to. Anything apart from a value of 'strict' will set it to loose!
-	     */
-		setMode : function( mode )
-		{
-			options.strictMode = mode == "strict" ? true : false;
-			return this;
-		},
-		
-		/**
-	     * Sets URI to parse if you don't want to to parse the current page's URI.
-		 * Calling the function with no value for newUri resets it to the current page's URI.
-	     *
-	     * @param string newUri The URI to parse.
-	     */		
-		setUrl : function( newUri )
-		{
-			options.url = newUri === undefined ? window.location : newUri;
-			setUp();
-			return this;
-		},		
-		
-		/**
-	     * Returns the value of the specified URI segment. Segments are numbered from 1 to the number of segments.
-		 * For example the URI http://test.com/about/company/ segment(1) would return 'about'.
-		 *
-		 * If no integer is passed into the function it returns the number of segments in the URI.
-	     *
-	     * @param int pos The position of the segment to return. Can be empty.
-	     */	
-		segment : function( pos )
-		{
-			if ( jQuery.isEmptyObject(parsed) )
-			{
-				setUp(); // if the URI has not been parsed yet then do this first...	
-			} 
-			if ( pos === undefined )
-			{
-				return segments.length;
-			}
-			return ( segments[pos] === "" || segments[pos] === undefined ) ? null : segments[pos];
-		},
-		
-		attr : key, // provides public access to private 'key' function - see above
-		
-		param : param // provides public access to private 'param' function - see above
-		
+	$.url = function( opts )
+	{
+	    var url     = '',
+	        strict  = false;
+
+	    if ( typeof opts === 'string' )
+	    {
+	        url = opts;
+	    }
+	    else
+	    {
+	        opts = opts || {};
+	        strict = opts.strict || strict;
+            url = opts.url === undefined ? window.location.toString() : opts.url;
+	    }
+	    	            
+        return {
+            
+            data : parseUri(url, strict),
+            
+            // get various attributes from the URI
+            attr : function( attr )
+            {
+                attr = aliases[attr] || attr;
+                return attr !== undefined ? this.data.attr[attr] : this.data.attr;
+            },
+            
+            // return query string parameters
+            param : function( param )
+            {
+                return param !== undefined ? this.data.param.query[param] : this.data.param.query;
+            },
+            
+            // return fragment parameters
+            fparam : function( param )
+            {
+                return param !== undefined ? this.data.param.fragment[param] : this.data.param.fragment;
+            },
+            
+            // return path segments
+            segment : function( seg )
+            {
+                if ( seg === undefined )
+                {
+                    return this.data.seg.path;                    
+                }
+                else
+                {
+                    seg = seg < 0 ? this.data.seg.path.length + seg : seg - 1; // negative segments count from the end
+                    return this.data.seg.path[seg];                    
+                }
+            },
+            
+            // return fragment segments
+            fsegment : function( seg )
+            {
+                if ( seg === undefined )
+                {
+                    return this.data.seg.fragment;                    
+                }
+                else
+                {
+                    seg = seg < 0 ? this.data.seg.fragment.length + seg : seg - 1; // negative segments count from the end
+                    return this.data.seg.fragment[seg];                    
+                }
+            }
+            
+        };
+        
 	};
 	
-}();
+})(jQuery);
