More documentation and tests
diff --git a/dev/js/runner/datepicker.html b/dev/js/runner/datepicker.html
new file mode 100644
index 0000000..141d7c6
--- /dev/null
+++ b/dev/js/runner/datepicker.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Spec Runner for the DatePicker</title>
+  <link rel="shortcut icon" type="image/png" href="../lib/jasmine-2.1.1/jasmine_favicon.png">
+  <link rel="stylesheet" href="../lib/jasmine-2.1.1/jasmine.css">
+  <script src="../lib/require.js"></script>
+  <script src="../lib/jasmine-2.1.1/jasmine.js"></script>
+  <script src="../lib/jasmine-2.1.1/jasmine-html.js"></script>
+  <script src="../lib/jasmine-2.1.1/boot.js"></script>
+  <script>
+    require.config({
+      baseUrl: "../src",
+      paths: {
+        "lib" : "../lib",
+        "spec" : "../spec"
+      }
+    });
+    require([
+      'lib/domReady!',
+      'spec/datepickerSpec'
+    ],
+    function () {
+      if (jsApiReporter.finished === true)
+        jasmine.getEnv().execute();
+    });
+  </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/dev/js/spec/datepickerSpec.js b/dev/js/spec/datepickerSpec.js
new file mode 100644
index 0000000..bcab225
--- /dev/null
+++ b/dev/js/spec/datepickerSpec.js
@@ -0,0 +1,84 @@
+define(['datepicker'], function (dpClass) {
+  describe('KorAP.Datepicker', function () {
+
+    it('should be initializable', function () {
+      var dp = dpClass.create();
+      var e = dp.show();
+      expect(e.nodeName).toEqual('DIV');
+      expect(e.classList.contains('datepicker')).toBeTruthy();
+      expect(e.getAttribute('tabindex')).toEqual('0');
+    });
+
+    it('should generate valid dates', function () {
+      var dp = dpClass.create();
+      expect(dp.today()).toMatch("\\d{4}-[01\\d-[01]\\d");
+    });    
+
+    it('should have year and month helpers', function () {
+      var dp = dpClass.create();
+      var e = dp.show(2013, 2);
+      expect(e.nodeName).toEqual('DIV');
+      expect(e.classList.contains('datepicker')).toBeTruthy();
+      expect(e.getAttribute('tabindex')).toEqual('0');
+      expect(e.querySelector('div.year > span:nth-child(2)').firstChild.data).toEqual('2013');
+      expect(e.querySelector('div.month > span:nth-child(2)').firstChild.data).toEqual('February');
+    });
+
+    it('should have modifyable year', function () {
+      var dp = dpClass.create();
+      var e = dp.show(2013, 2);
+      expect(e.nodeName).toEqual('DIV');
+      expect(e.classList.contains('datepicker')).toBeTruthy();
+      expect(e.getAttribute('tabindex')).toEqual('0');
+      expect(e.querySelector('div.year > span:nth-child(2)').firstChild.data).toEqual('2013');
+      expect(e.querySelector('div.month > span:nth-child(2)').firstChild.data).toEqual('February');
+
+      dp.incrYear();
+      expect(e.querySelector('div.year > span:nth-child(2)').firstChild.data).toEqual('2014');
+      expect(e.querySelector('div.month > span:nth-child(2)').firstChild.data).toEqual('February');
+
+      dp.incrYear();
+      expect(e.querySelector('div.year > span:nth-child(2)').firstChild.data).toEqual('2015');
+      expect(e.querySelector('div.month > span:nth-child(2)').firstChild.data).toEqual('February');
+
+      dp.decrYear();
+      expect(e.querySelector('div.year > span:nth-child(2)').firstChild.data).toEqual('2014');
+      expect(e.querySelector('div.month > span:nth-child(2)').firstChild.data).toEqual('February');
+
+      // Max value
+      var e = dp.show(9998, 2);
+      expect(e.querySelector('div.year > span:nth-child(2)').firstChild.data).toEqual('9998');
+      expect(e.querySelector('div.month > span:nth-child(2)').firstChild.data).toEqual('February');
+
+      dp.incrYear();
+      expect(e.querySelector('div.year > span:nth-child(2)').firstChild.data).toEqual('9999');
+      expect(e.querySelector('div.month > span:nth-child(2)').firstChild.data).toEqual('February');
+
+      dp.incrYear();
+      expect(e.querySelector('div.year > span:nth-child(2)').firstChild.data).toEqual('9999');
+      expect(e.querySelector('div.month > span:nth-child(2)').firstChild.data).toEqual('February');
+
+      // Min value
+      var e = dp.show(2, 2);
+      expect(e.querySelector('div.year > span:nth-child(2)').firstChild.data).toEqual('2');
+      expect(e.querySelector('div.month > span:nth-child(2)').firstChild.data).toEqual('February');
+
+      dp.decrYear();
+      expect(e.querySelector('div.year > span:nth-child(2)').firstChild.data).toEqual('1');
+      expect(e.querySelector('div.month > span:nth-child(2)').firstChild.data).toEqual('February');
+
+      dp.decrYear();
+      expect(e.querySelector('div.year > span:nth-child(2)').firstChild.data).toEqual('0');
+      expect(e.querySelector('div.month > span:nth-child(2)').firstChild.data).toEqual('February');
+
+      dp.decrYear();
+      expect(e.querySelector('div.year > span:nth-child(2)').firstChild.data).toEqual('0');
+      expect(e.querySelector('div.month > span:nth-child(2)').firstChild.data).toEqual('February');
+    });
+
+    it('should have modifyable month', function () {
+      var dp = dpClass.create();
+      var e = dp.show(2013, 6);
+    });
+  });
+});
diff --git a/dev/js/src/datepicker.js b/dev/js/src/datepicker.js
index 8608efe..9882364 100644
--- a/dev/js/src/datepicker.js
+++ b/dev/js/src/datepicker.js
@@ -39,6 +39,7 @@
       return this;
     },
 
+
     /**
      * Get or select a specific date.
      */
@@ -80,6 +81,7 @@
       this._click = cb;
     },
 
+
     /**
      * Show the datepicker.
      * Will either show the selected year/month
@@ -111,6 +113,7 @@
       return this._element;
     },
 
+
     /**
      * Get the HTML element associated with the datepicker.
      */
@@ -118,6 +121,7 @@
       return this._element;
     },
 
+
     /**
      * Get the current date in string format.
      */
@@ -131,28 +135,37 @@
       return str;
     },
 
+
     /**
      * Increment the year.
      */
     incrYear : function () {
-      this._showYear++;
-      this._updateYear();
-      this._updateMonth();
-      this._updateDay();
+      if (this._showYear < 9999) {
+	this._showYear++;
+	this._updateYear();
+	this._updateMonth();
+	this._updateDay();
+	return this;
+      };
       return;
     },
 
+
     /**
      * Decrement the year.
      */
     decrYear : function () {
-      this._showYear--;
-      this._updateYear();
-      this._updateMonth();
-      this._updateDay();
+      if (this._showYear > 0) {
+	this._showYear--;
+	this._updateYear();
+	this._updateMonth();
+	this._updateDay();
+	return this;
+      };
       return;
     },
 
+
     /**
      * Increment the month.
      */
@@ -166,8 +179,10 @@
 	this._updateMonth();
 	this._updateDay();
       };
+      return this;
     },
 
+
     /**
      * Decrement the month.
      */
@@ -181,6 +196,8 @@
 	this._updateMonth();
 	this._updateDay();
       };
+
+      return this;
     },
 
 
diff --git a/lib/Kalamar.pm b/lib/Kalamar.pm
index a19a916..58558ff 100644
--- a/lib/Kalamar.pm
+++ b/lib/Kalamar.pm
@@ -18,7 +18,7 @@
   # Add additional plugin path
   push(@{$self->plugins->namespaces}, __PACKAGE__ . '::Plugin');
 
-  # korap.ids-mannheim.de specific
+  # korap.ids-mannheim.de specific path prefixing
   $self->hook(
     before_dispatch => sub {
       my $c = shift;
@@ -30,6 +30,7 @@
     }) if $self->mode eq 'production';
 
   # Cache static assets
+  # (not necessary, as long as shipped by nginx or Apache)
   $self->hook(
     after_static => sub {
       my $res = shift->res;
@@ -40,9 +41,9 @@
 
   # Set secrets for signed cookies
   if (-e (my $secret = $self->home . '/kalamar.secret')) {
-    $self->secrets([
-      b($secret)->slurp->split("\n")
-    ]);
+
+    # Load file and split lines for multiple secrets
+    $self->secrets([b($secret)->slurp->split("\n")]);
   }
   else {
     $self->log->warn('Please create a kalamar.secret file');
@@ -55,9 +56,10 @@
     'Notifications',             # Client notifications
     'Search',                    # Abstract Search framework
     'CHI',                       # Global caching mechanism
+    'MailException'              # Alert via Email on exception
     'TagHelpers::Pagination',    # Pagination widget
     'TagHelpers::MailToChiffre', # Obfuscate email addresses
-    'KalamarHelpers'             # Specific Helpers for Kalamar
+    'KalamarHelpers',            # Specific Helpers for Kalamar
   ) {
     $self->plugin($_);
   };
@@ -72,10 +74,10 @@
   # Establish routes
   my $r = $self->routes;
 
-  # Base query page
+  # Base query route
   $r->get('/')->to('search#query')->name('index');
 
-  # Documentation
+  # Documentation routes
   $r->get('/doc')->to('documentation#page', page => 'korap')->name('doc_start');
   $r->get('/doc/:page')->to('documentation#page', scope => undef);
   $r->get('/doc/*scope/:page')->to('documentation#page')->name('doc');
diff --git a/lib/Kalamar/Controller/Search.pm b/lib/Kalamar/Controller/Search.pm
index 13af8c5..913abc2 100644
--- a/lib/Kalamar/Controller/Search.pm
+++ b/lib/Kalamar/Controller/Search.pm
@@ -66,11 +66,12 @@
 };
 
 
-
 # Get information about a match
+# Note: This is called 'match_info' as 'match' is reserved
 sub match_info {
   my $c = shift;
 
+  # Old API foundry/layer usage
   my $foundry = '*';
   my %query = (foundry => '*');
   if ($c->param('foundry')) {
@@ -83,6 +84,7 @@
     };
   };
 
+  # Async
   $c->render_later;
 
   # Use the API for fetching matching information non-blocking
@@ -136,7 +138,8 @@
 =head1 DESCRIPTION
 
 L<Kalamar::Controller::Search> is the controller class for
-search related endpoints in Kalamar.
+search related endpoints in Kalamar. Actions are released when routes
+match.
 
 
 =head1 METHODS
@@ -144,39 +147,85 @@
 L<Kalamar::Controller::Search> inherits all methods from
 L<Mojolicious::Controller> and implements the following new ones.
 
-=head2 search
+=head2 query
 
-Action for all documentation pages.
+  GET /?q=Baum&ql=poliqarp
+
+Action for all queries to the system. Returns C<HTML> only for the moment.
+
+The following parameters are supported.
+
 
 =head3 q
 
-Query parameter
+The query string. This may any query written in a supported query language.
 
 =head3 ql
 
-Query language
+The query language. This may be any query language supported by the system,
+written as the API expects the string.
 
 =head3 action
 
-May be C<inspect>.
+May be C<inspect>. In that case, the serialized request is mirrored instead of
+processed.
+
+B<This switch is experimental and may change without warnings!>
+
 
 =head3 snippet
 
+If set, the query is returned in the snippet view template.
+
+B<This parameter is experimental and may change without warnings!>
+
+
 =head3 cutoff
 
+If set, the query will be cut off after the matches.
+
+B<This parameter is directly forwarded to the API and may not be supported in the future.>
+
+
 =head3 count
 
+If set, the query will be only return the given number of matches,
+in case the API supports it. Will fallback to the default number of matches defined
+by the API or the backend.
+
+B<This parameter is directly forwarded to the API and may not be supported in the future.>
+
+
 =head3 p
 
-Number of page
+If set, the query will page to the given number of pages in the result set.
+Will default to 1.
+
+B<This parameter is directly forwarded to the API and may not be supported in the future.>
+
 
 
 =head2 match
 
-/:corpus_id/:doc_id/:text_id/:match_id
+  /:corpus_id/:doc_id/:text_id/:match_id?foundry=*
+
+Returns information to a match either as a C<JSON> or an C<HTML> document.
+The path defines the concrete match, by corpus identifier, document identifier,
+text identifier (all information as given by DeReKo), and match identifier
+(essentially the position of the match in the document, including highlight information).
+
+The following parameters are supported.
+
 
 =head3 foundry
 
+Expects a foundry definition for retrieved information.
+If not given, returns all annotations for the match.
+If given, returns only given layer information for the defined foundry.
+
+B<This parameter is experimental and may change without warnings!>
+
+
 =head3 layer
 
 =head3 spans
@@ -201,13 +250,13 @@
 Author: L<Nils Diewald|http://nils-diewald.de/>
 
 Kalamar is developed as part of the L<KorAP|http://korap.ids-mannheim.de/>
-Corpus Analysis Platform at the Institute for German Language
-(L<IDS|http://ids-mannheim.de/>),
-funded by the
+Corpus Analysis Platform at the
+L<Institute for the German Language (IDS)|http://ids-mannheim.de/>,
+member of the
 L<Leibniz-Gemeinschaft|http://www.leibniz-gemeinschaft.de/en/about-us/leibniz-competition/projekte-2011/2011-funding-line-2/>
 and supported by the L<KobRA|http://www.kobra.tu-dortmund.de> project,
-funded by the Federal Ministry of Education and Research
-(L<BMBF|http://www.bmbf.de/en/>).
+funded by the
+L<Federal Ministry of Education and Research (BMBF)|http://www.bmbf.de/en/>.
 
 Kalamar is free software published under the
 L<BSD-2 License|https://raw.githubusercontent.com/KorAP/Kalamar/master/LICENSE).