diff --git a/vipra-backend/src/main/java/de/vipra/rest/resource/InfoResource.java b/vipra-backend/src/main/java/de/vipra/rest/resource/InfoResource.java
new file mode 100644
index 0000000000000000000000000000000000000000..18dde950fac14ac1fba9931fb718af5809580280
--- /dev/null
+++ b/vipra-backend/src/main/java/de/vipra/rest/resource/InfoResource.java
@@ -0,0 +1,5 @@
+package de.vipra.rest.resource;
+
+public class InfoResource {
+
+}
diff --git a/vipra-ui/app/html/topics/articles.html b/vipra-ui/app/html/topics/articles.html
new file mode 100644
index 0000000000000000000000000000000000000000..7b188562b7717c706b8fb32c0e04b886e0cd51bd
--- /dev/null
+++ b/vipra-ui/app/html/topics/articles.html
@@ -0,0 +1,38 @@
+<div ui-view ng-cloak>
+  <div class="page-header">
+    <h1>
+      <div ng-bind="topic.name" ng-hide="isRename"></div>
+      <div class="input-group input-group-lg" ng-show="isRename">
+        <input type="text" class="form-control" ng-model="topic.name" id="topicName" ng-keyup="keyup($event)">
+        <div class="input-group-btn">
+          <button class="btn btn-success" ng-click="endRename(true)">
+            <span class="glyphicon glyphicon-ok"></span>
+          </button>
+          <button class="btn btn-danger" ng-click="endRename(false)">
+            <span class="glyphicon glyphicon-remove"></span>
+          </button>
+        </div>
+      </div>
+    </h1>
+
+    <bs-alert type="danger" ng-if="renameErrors">
+      <span ng-bind-html="renameErrors"></span>
+    </bs-alert>
+    
+    <table class="item-actions">
+      <tr>
+        <td>
+          <bs-dropdown label="Actions">
+            <li><a ng-click="startRename()">Rename</a></li>
+          </bs-dropdown>
+        </td>
+        <td>
+          <a class="btn btn-default" ui-sref="network({type:'topics', id:topic.id})">Network graph</a>
+        </td>
+      </tr>
+    </table>
+  </div>
+
+  <h3>Info <hide-link target="#info"/></h3>
+  
+</div>
\ No newline at end of file
diff --git a/vipra-ui/app/js/app.js b/vipra-ui/app/js/app.js
index f88787b5c56818d6834f7a5114c2f18bf09a841c..d44403e71662ce72062bd7f3489e5cbc8e28c345 100644
--- a/vipra-ui/app/js/app.js
+++ b/vipra-ui/app/js/app.js
@@ -18,21 +18,19 @@
 
   app.config(['$stateProvider', '$urlRouterProvider', function($stateProvider, $urlRouterProvider) {
 
-    var tplBase = 'html';
-
     $urlRouterProvider.otherwise('/');
 
     // states
 
     $stateProvider.state('index', {
       url: '/',
-      templateUrl: tplBase + '/index.html',
+      templateUrl: Vipra.const.tplBase + '/index.html',
       controller: 'IndexController'
     });
 
     $stateProvider.state('network', {
       url: '/network/:type/:id',
-      templateUrl: tplBase + '/network.html',
+      templateUrl: Vipra.const.tplBase + '/network.html',
       controller: 'NetworkController'
     });
 
@@ -40,14 +38,14 @@
 
     $stateProvider.state('articles', {
       url: '/articles?page',
-      templateUrl: tplBase + '/articles/index.html',
+      templateUrl: Vipra.const.tplBase + '/articles/index.html',
       controller: 'ArticlesIndexController',
       reloadOnSearch: false
     });
 
     $stateProvider.state('articles.show', {
       url: '/:id',
-      templateUrl: tplBase + '/articles/show.html',
+      templateUrl: Vipra.const.tplBase + '/articles/show.html',
       controller: 'ArticlesShowController'
     });
 
@@ -55,29 +53,35 @@
 
     $stateProvider.state('topics', {
       url: '/topics?page',
-      templateUrl: tplBase + '/topics/index.html',
+      templateUrl: Vipra.const.tplBase + '/topics/index.html',
       controller: 'TopicsIndexController',
       reloadOnSearch: false
     });
 
     $stateProvider.state('topics.show', {
       url: '/:id',
-      templateUrl: tplBase + '/topics/show.html',
+      templateUrl: Vipra.const.tplBase + '/topics/show.html',
       controller: 'TopicsShowController'
     });
 
+    $stateProvider.state('topics.show.articles', {
+      url: '/articles',
+      templateUrl: Vipra.const.tplBase + '/topics/articles.html',
+      controller: 'TopicsArticlesController'
+    })
+
     // states: words
 
     $stateProvider.state('words', {
       url: '/words?page',
-      templateUrl: tplBase + '/words/index.html',
+      templateUrl: Vipra.const.tplBase + '/words/index.html',
       controller: 'WordsIndexController',
       reloadOnSearch: false
     });
 
     $stateProvider.state('words.show', {
       url: '/:id',
-      templateUrl: tplBase + '/words/show.html',
+      templateUrl: Vipra.const.tplBase + '/words/show.html',
       controller: 'WordsShowController'
     });
 
diff --git a/vipra-ui/app/js/config.js b/vipra-ui/app/js/config.js
new file mode 100644
index 0000000000000000000000000000000000000000..67ae35dcc03490f07c31be2baef975e706f60100
--- /dev/null
+++ b/vipra-ui/app/js/config.js
@@ -0,0 +1,10 @@
+(function() {
+
+  window.Vipra = window.Vipra || {};
+
+  Vipra.config = {
+    restUrl: '//' + location.hostname + ':8000/vipra/rest',
+    websocketUrl: '//' + location.hostname + ':8000/vipra/ws'
+  };
+
+})();
\ No newline at end of file
diff --git a/vipra-ui/app/js/constants.js b/vipra-ui/app/js/constants.js
new file mode 100644
index 0000000000000000000000000000000000000000..b60fe8af4e2d41e875fc7bf7a33b7bb6cda0fb65
--- /dev/null
+++ b/vipra-ui/app/js/constants.js
@@ -0,0 +1,12 @@
+(function() {
+
+  window.Vipra = window.Vipra || {};
+
+  Vipra.const = {
+    tplBase: 'html',
+    latestItems: 3,
+    searchResults: 10,
+    pageSize: 100
+  };
+
+})();
\ No newline at end of file
diff --git a/vipra-ui/app/js/controllers.js b/vipra-ui/app/js/controllers.js
index ae8935fd3dd446f873d682de949680e001de4e0d..49ac6552309c2f3389ce550caa7e4caf45918b86 100644
--- a/vipra-ui/app/js/controllers.js
+++ b/vipra-ui/app/js/controllers.js
@@ -9,11 +9,6 @@
     'vipra.factories'
   ]);
 
-  var latestItemsCount = 3,
-      searchItemsCount = 10,
-      pageSize = 100,
-      paginationPadding = 4;
-
   app.controller('RootController', ['$scope', '$state', function($scope, $state) {
     $scope.$state = $state;
   }]);
@@ -26,15 +21,15 @@
 
     $scope.search = $location.search().query;
 
-    ArticleFactory.query({limit:latestItemsCount, sort:'-created'}, function(response) {
+    ArticleFactory.query({limit:Vipra.const.latestItems, sort:'-created'}, function(response) {
       $scope.latestArticles = response.data;
     });
 
-    TopicFactory.query({limit:latestItemsCount, sort:'-created'}, function(response) {
+    TopicFactory.query({limit:Vipra.const.latestItems, sort:'-created'}, function(response) {
       $scope.latestTopics = response.data;
     });
 
-    WordFactory.query({limit:latestItemsCount, sort:'-created'}, function(response) {
+    WordFactory.query({limit:Vipra.const.latestItems, sort:'-created'}, function(response) {
       $scope.latestWords = response.data;
     });
 
@@ -42,7 +37,7 @@
       if($scope.search) {
         $location.search('query', $scope.search);
         $scope.searching = true;
-        SearchFactory.query({limit:searchItemsCount, query:$scope.search}, function(response) {
+        SearchFactory.query({limit:Vipra.const.searchResults, query:$scope.search}, function(response) {
           $scope.searching = false;
           $scope.searchResults = response.data;
           $scope.queryTime = response.$queryTime;
@@ -255,7 +250,7 @@
     function($scope, $state, $stateParams, ArticleFactory, Store) {
 
     $scope.page = Math.max($stateParams.page || 1, 1);
-    $scope.limit = pageSize;
+    $scope.limit = Vipra.const.pageSize;
     $scope.sort = Store('sortarticles') || 'date';
     $scope.order = Store('orderarticles') || '';
 
@@ -291,10 +286,10 @@
 
     ArticleFactory.get({id: $stateParams.id}, function(response) {
       $scope.article = response.data;
-      $scope.article.text = createInitial($scope.article.text);
-      $scope.articleDate = formatDate($scope.article.date);
-      $scope.articleCreated = formatDateTime($scope.article.created);
-      $scope.articleModified = formatDateTime($scope.article.modified);
+      $scope.article.text = Vipra.createInitial($scope.article.text);
+      $scope.articleDate = Vipra.formatDate($scope.article.date);
+      $scope.articleCreated = Vipra.formatDateTime($scope.article.created);
+      $scope.articleModified = Vipra.formatDateTime($scope.article.modified);
       $scope.articleMeta = response.meta;
       $scope.queryTime = response.$queryTime;
 
@@ -302,7 +297,7 @@
       var topicShareSeries = [],
           topics = $scope.article.topics;
       for(var i = 0; i < topics.length; i++) {
-        var share = toPercent(topics[i].count / $scope.article.stats.wordCount);
+        var share = Vipra.toPercent(topics[i].count / $scope.article.stats.wordCount);
         topics[i].share = share;
         topicShareSeries.push({name: topics[i].topic.name.ellipsize(20), y: share});
       }
@@ -339,7 +334,7 @@
     function($scope, $stateParams, Store, TopicFactory) {
 
     $scope.page = Math.max($stateParams.page || 1, 1);
-    $scope.limit = pageSize;
+    $scope.limit = Vipra.const.pageSize;
     $scope.sort = Store('sorttopics') || 'name';
     $scope.order = Store('ordertopics') || '';
 
@@ -375,8 +370,8 @@
 
     TopicFactory.get({id: $stateParams.id}, function(response) {
       $scope.topic = response.data;
-      $scope.topicCreated = formatDateTime($scope.topic.created);
-      $scope.topicModified = formatDateTime($scope.topic.modified);
+      $scope.topicCreated = Vipra.formatDateTime($scope.topic.created);
+      $scope.topicModified = Vipra.formatDateTime($scope.topic.modified);
       $scope.topicMeta = response.meta;
       $scope.queryTime = response.$queryTime;
     });
@@ -397,7 +392,7 @@
           $scope.isRename = false;
         }, function(response) {
           if(response.data)
-          $scope.renameErrors = getErrors(response.data.errors);
+          $scope.renameErrors = Vipra.getErrors(response.data.errors);
         });
       } else {
         $scope.isRename = false;
@@ -414,6 +409,19 @@
 
   }]);
 
+  /**
+   * Topic Show Articles route
+   */
+  app.controller('TopicArticlesController', ['$scope', '$stateParams', 'TopicFactory',
+    function($scope, $stateParams, TopicFactory) {
+
+    TopicFactory.articles({id: $stateParams.id}, function(response) {
+      $scope.articles = response.data;
+      $scope.queryTime = response.$queryTime;
+    });
+
+  }]);
+
   /****************************************************************************
    * Word Controllers
    ****************************************************************************/
@@ -459,7 +467,7 @@
 
     WordFactory.get({id: $stateParams.id}, function(response) {
       $scope.word = response.data;
-      $scope.wordCreated = formatDateTime($scope.word.created);
+      $scope.wordCreated = Vipra.formatDateTime($scope.word.created);
       $scope.wordMeta = response.meta;
       $scope.queryTime = response.$queryTime;
     });
@@ -486,8 +494,9 @@
       $scope.calculatePages = function() {
         var pages = [],
             max   = Math.ceil($scope.total/$scope.limit*1.0),
-            start = Math.max($scope.page - paginationPadding, 1),
-            end   = Math.min(Math.max($scope.page + paginationPadding, start + paginationPadding * 2), max);
+            pad   = 4,
+            start = Math.max($scope.page - pad, 1),
+            end   = Math.min(Math.max($scope.page + pad, start + pad * 2), max);
         for(var i = start; i <= end; i++) {
           pages.push(i);
         }
diff --git a/vipra-ui/app/js/directives.js b/vipra-ui/app/js/directives.js
index 125427e7a876af5bd63c0807ad93d85d55f130ad..48364ce22994f8a1cb73e01273289a21a1fe96ef 100644
--- a/vipra-ui/app/js/directives.js
+++ b/vipra-ui/app/js/directives.js
@@ -8,8 +8,6 @@
     'ui.router'
   ]);
 
-  var slideDuration = 250;
-
   app.directive('topicLink', function() {
     return {
       scope: {
@@ -150,7 +148,7 @@
       transclude: true,
       replace: true,
       link: function($scope, $elem, $attrs) {
-        $scope.dropdownId = randomId();
+        $scope.dropdownId = Vipra.randomId();
         $scope.label = $attrs.label;
         $scope.align = 'dropdown-menu-left';
         if($attrs.align === 'right')
diff --git a/vipra-ui/app/js/factories.js b/vipra-ui/app/js/factories.js
index 37a1632fc03d42fbb7975c636cc8095ae4903da5..9224eb92350d82deec477ead6cc79143a5f82106 100644
--- a/vipra-ui/app/js/factories.js
+++ b/vipra-ui/app/js/factories.js
@@ -6,37 +6,35 @@
 
   var app = angular.module('vipra.factories', []);
 
-  var endpoint = '//' + location.hostname + ':8080/vipra/rest';
-
   app.factory('ArticleFactory', ['$resource', function($resource) {
-    return $resource(endpoint + '/articles/:id', {}, {
+    return $resource(Vipra.config.restUrl + '/articles/:id', {}, {
       query: { isArray: false }
     });
   }]);
 
   app.factory('TopicFactory', ['$resource', function($resource) {
-    return $resource(endpoint + '/topics/:id', {}, {
+    return $resource(Vipra.config.restUrl + '/topics/:id', {}, {
       query: { isArray: false },
       update: { method: 'PUT' },
-      articles: { url: endpoint + '/topics/:id/articles' }
+      articles: { url: Vipra.config.restUrl + '/topics/:id/articles' }
     });
   }]);
 
   app.factory('WordFactory', ['$resource', function($resource) {
-    return $resource(endpoint + '/words/:id', {}, {
+    return $resource(Vipra.config.restUrl + '/words/:id', {}, {
       query: { isArray: false },
-      topics: { url: endpoint + '/words/:id/topics' }
+      topics: { url: Vipra.config.restUrl + '/words/:id/topics' }
     });
   }]);
 
   app.factory('SearchFactory', ['$resource', function($resource) {
-    return $resource(endpoint + '/search', {}, {
+    return $resource(Vipra.config.restUrl + '/search', {}, {
       query: { isArray: false }
     });
   }]);
 
+  // https://gist.github.com/Fluidbyte/4718380
   app.factory('Store', ['$state', function($state) {
-    // https://gist.github.com/Fluidbyte/4718380
     return function(key, value) {
       key += '-' + $state.current.name;
       var lsSupport = false;
diff --git a/vipra-ui/app/js/filters.js b/vipra-ui/app/js/filters.js
index ae98ac22d553e57fa2b1f32afc2cfe40ddbd84ed..b4a1b8aea5399fcf1dd773fe15e7c4fedd3b01f0 100644
--- a/vipra-ui/app/js/filters.js
+++ b/vipra-ui/app/js/filters.js
@@ -7,15 +7,15 @@
   var app = angular.module('vipra.filters', []);
 
   app.filter('toPercent', function() {
-    return toPercent;
+    return Vipra.toPercent;
   });
 
   app.filter('formatDate', function() {
-    return formatDate;
+    return Vipra.formatDate;
   });
 
   app.filter('formatDateTime', function() {
-    return formatDateTime;
+    return Vipra.formatDateTime;
   });
 
 })();
\ No newline at end of file
diff --git a/vipra-ui/app/js/helpers.js b/vipra-ui/app/js/helpers.js
index 64f87779659b9a80b8caaf30409e084f12f1947c..4bcc959e90064fee5025f1d835d51282ee66c591 100644
--- a/vipra-ui/app/js/helpers.js
+++ b/vipra-ui/app/js/helpers.js
@@ -4,34 +4,36 @@
  ******************************************************************************/
 (function() {
 
-  window.formatDate = function(date) {
+  window.Vipra = window.Vipra || {};
+
+  /**
+   * Helpers
+   */
+
+  Vipra.formatDate = function(date) {
     return new Date(date).toLocaleDateString();
   };
 
-  window.formatDateTime = function(date) {
+  Vipra.formatDateTime = function(date) {
     date = new Date(date);
     return date.toLocaleDateString() + " " + date.toLocaleTimeString();
   };
 
-  window.toPercent = function(input) {
+  Vipra.toPercent = function(input) {
     if(typeof input !== 'number')
       input = parseInt(input, 10);
     return Math.round(input * 100);
   };
 
-  window.createInitial = function(text) {
+  Vipra.createInitial = function(text) {
     return '<span class="initial">' + text.substring(0, 1) + "</span>" + text.substring(1);
   };
 
-  window.randomId = function() {
+  Vipra.randomId = function() {
     return 'id' + Math.random().toString(36).substring(7);
   };
 
-  window.console = window.console || {
-    log: function () {}
-  };
-
-  window.getErrors = function(errors) {
+  Vipra.getErrors = function(errors) {
     var html = [];
     if(errors && errors.length) {
       for(var i = 0; i < errors.length; i++)
@@ -40,21 +42,32 @@
     return html.join('<br>');
   }
 
-  String.prototype.ellipsize = function(max) {
-    max = max || 20;
-    if(this.length > max) {
-      return this.substring(0, max) + '...';
-    }
-    return this;
-  };
+  /**
+   * Polyfills
+   */
 
-  String.prototype.multiline = function(max) {
-    return this.split(new RegExp("((?:\\w+ ){" + max + "})", "g")).filter(Boolean).join("\n");
-  };
+  if(typeof String.prototype.ellipsize === 'undefined')
+    String.prototype.ellipsize = function(max) {
+      max = max || 20;
+      if(this.length > max) {
+        return this.substring(0, max) + '...';
+      }
+      return this;
+    };
+
+  if(typeof String.prototype.multiline === 'undefined')
+    String.prototype.multiline = function(max) {
+      return this.split(new RegExp("((?:\\w+ ){" + max + "})", "g")).filter(Boolean).join("\n");
+    };
 
   if(typeof String.prototype.startsWith === 'undefined')
     String.prototype.startsWith = function(start) {
       return this.lastIndexOf(start, 0) === 0;
     };
 
+  if(typeof window.console === 'undefined')
+    window.console = {
+      log: function () {}
+    };
+
 })();
\ No newline at end of file