diff --git a/CHANGES.md b/CHANGES.md
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,3 +1,7 @@
+## 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
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.3
+version: 0.22.4
 stability:      beta
 category:       Finance
 synopsis:       A web interface for the hledger accounting tool.
@@ -17,7 +17,7 @@
 maintainer:     Simon Michael <simon@joyful.com>
 homepage:       http://hledger.org
 bug-reports:    http://hledger.org/bugs
-tested-with:    GHC==7.4.2, GHC==7.6.3
+tested-with:    GHC==7.6.3
 cabal-version:  >= 1.8
 build-type:     Simple
 extra-tmp-files:
@@ -71,7 +71,7 @@
     Default:       False
 
 library
-    cpp-options:   -DVERSION="0.22.3"
+    cpp-options:   -DVERSION="0.22.4"
     if flag(dev) || flag(library-only)
         cpp-options: -DDEVELOPMENT
  
@@ -162,7 +162,7 @@
     if flag(library-only)
         Buildable: False
 
-    cpp-options:   -DVERSION="0.22.3"
+    cpp-options:   -DVERSION="0.22.4"
     if flag(dev)
         cpp-options:   -DDEVELOPMENT
 
diff --git a/static/jquery.url.js b/static/jquery.url.js
--- a/static/jquery.url.js
+++ b/static/jquery.url.js
@@ -1,164 +1,181 @@
-// JQuery URL Parser plugin - https://github.com/allmarkedup/jQuery-URL-Parser
+// JQuery URL Parser
 // Written by Mark Perkins, mark@allmarkedup.com
 // License: http://unlicense.org/ (i.e. do what you want with it!)
 
-;(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
+jQuery.url = function()
+{
+	var segments = {};
 	
-	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
-	},
+	var parsed = {};
 	
-	querystring_parser = /(?:^|&|;)([^&=;]*)=?([^&;]*)/g, // supports both ampersand and semicolon-delimted query string key/value pairs
+	/**
+    * Options object. Only the URI and strictMode values can be changed via the setters below.
+    */
+  	var options = {
 	
-	fragment_parser = /(?:^|&|;)([^&=;]*)=?([^&;]*)/g; // supports both ampersand and semicolon-delimted fragment key/value pairs
+		url : window.location, // default URI is the page in which the script is running
+		
+		strictMode: false, // 'loose' parsing by default
 	
-	function parseUri( url, strictMode )
-	{
-		var str = decodeURI( url ),
-		    res   = parser[ strictMode || false ? "strict" : "loose" ].exec( str ),
-		    uri = { attr : {}, param : {}, seg : {} },
-		    i   = 14;
+		key: ["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], // keys available to query 
 		
-		while ( i-- )
-		{
-			uri.attr[ key[i] ] = res[i] || "";
-		}
+		q: {
+			name: "queryKey",
+			parser: /(?:^|&|;)([^&=;]*)=?([^&;]*)/g
+		},
 		
-		// build query and fragment parameters
+		parser: {
+			strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,  //less intuitive, more accurate to the specs
+			loose:  /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/ // more intuitive, fails on relative paths and deviates from specs
+		}
 		
-		uri.param['query'] = {};
-		uri.param['fragment'] = {};
+	};
+	
+    /**
+     * 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 );
 		
-		uri.attr['query'].replace( querystring_parser, function ( $0, $1, $2 ){
-			if ($1)
-			{
-				uri.param['query'][$1] = $2;
+		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;
 			}
 		});
-		
-		uri.attr['fragment'].replace( fragment_parser, function ( $0, $1, $2 ){
-			if ($1)
+
+		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['fragment'][$1] = $2;
+				return parsed.protocol+"://"+parsed.host+":"+parsed.port+"/";	
 			}
-		});
-				
-		// 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;
+			else
+			{
+				return parsed.protocol+"://"+parsed.host+"/";
+			}
+		}
+	
+		return ( parsed[key] === "" ) ? null : parsed[key];
 	};
 	
-	function getAttrName( elm )
+	/**
+     * Returns the value of the required query string parameter.
+  	 * 
+	 * @param string item The parameter whose value is required
+     */		
+	var param = function( item )
 	{
-		var tn = elm.tagName;
-		if ( tn !== undefined ) return tag2attr[tn.toLowerCase()];
-		return tn;
-	}
-	
-	$.fn.url = function( strictMode )
+		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()
 	{
-	    var url = '';
-	    
-	    if ( this.length )
-	    {
-	        url = $(this).attr( getAttrName(this[0]) ) || '';
-	    }
-	    
-        return $.url({ url : url, strict : strictMode });
+		parsed = parseUri();
+		
+		getSegments();	
 	};
 	
-	$.url = function( opts )
+    /**
+     * Splits up the body of the URI into segments (i.e. sections delimited by '/')
+     */
+	var getSegments = function()
 	{
-	    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];                    
-                }
-            }
-            
-        };
-        
+		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("/");
 	};
 	
-})(jQuery);
+	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
+		
+	};
+	
+}();
