veyron/examples/pipetobrowser: Polish and performance improvements

-human friendly dates using humanize third-party NPM module
-ability to load remote plugins
-ton of bug fixes
-UI polish

Change-Id: I9d53719d44ed75611e488b1c37bb9e1d0a01b970
diff --git a/examples/pipetobrowser/Makefile b/examples/pipetobrowser/Makefile
index 474caf5..e941562 100644
--- a/examples/pipetobrowser/Makefile
+++ b/examples/pipetobrowser/Makefile
@@ -20,8 +20,8 @@
 all: node_modules browser/third-party browser/third-party/veyron browser/build.js browser/index.html $(VEYRON_ROOT)/veyron/go/bin
 
 # Build p2b cli binary
-$(VEYRON_ROOT)/veyron/go/bin: cli/main.go
-	$(VEYRON_BUILD_SCRIPT) install veyron/examples/pipetobrowser/...
+$(VEYRON_ROOT)/veyron/go/bin: p2b/main.go
+	$(VEYRON_BUILD_SCRIPT) install veyron/...
 
 # Install what we need from NPM, tools such as jspm, serve, etc...
 node_modules: package.json
@@ -31,9 +31,9 @@
 
 # Build and copies Veyron from local source
 browser/third-party/veyron: $(VEYRON_JS_API)
-	mkdir browser/third-party -p
+	mkdir -p browser/third-party
 	(cd $(VEYRON_JS_API) && ./vgrunt build)
-	mkdir browser/third-party/veyron -p
+	mkdir -p browser/third-party/veyron
 	cp -rf $(VEYRON_JS_API)/dist/*.* browser/third-party/veyron
 
 # Install JSPM and Bower packages as listed in browser/package.json from JSPM and browser/bower.json from bower
@@ -75,10 +75,6 @@
 
 # Deploys Veyron daemons
 daemons:
-	@if [[ ! -e $(VEYRON_PROXY) ]]; then \
-		echo "Veyron proxy could not be found in $(VEYRON_PROXY). Please build and install veyron2 and services first"; \
-		exit 1; \
-	fi
 	identity --name=veyron_p2b_identity > $(VEYRON_IDENTITY_PATH)
 	export VEYRON_IDENTITY=$(VEYRON_IDENTITY_PATH) ; \
 	identityd --address=:$(VEYRON_IDENTITY_PORT) & \
@@ -86,7 +82,6 @@
 	export NAMESPACE_ROOT=/localhost:$(VEYRON_MOUNTTABLE_PORT) ; \
 	proxyd -address=$(VEYRON_PROXY_ADDR) & \
 	wsprd --v=1 -logtostderr=true -vproxy=$(VEYRON_PROXY_ADDR) --port $(VEYRON_WSPR_PORT) & \
-	$(VEYRON_STORE) --address=:$(VEYRON_STORE_PORT) --name=global/$(USER)/store &
 
 # Kills the running daemons
 clean-daemons:
diff --git a/examples/pipetobrowser/browser/actions/add-pipe-viewer.js b/examples/pipetobrowser/browser/actions/add-pipe-viewer.js
index 419d70d..a284731 100644
--- a/examples/pipetobrowser/browser/actions/add-pipe-viewer.js
+++ b/examples/pipetobrowser/browser/actions/add-pipe-viewer.js
@@ -67,8 +67,11 @@
 
   // Add a new tab and show a loading indicator for now,
   // then replace the loading view with the actual viewer when ready
+  // close the stream when tab closes
   var loadingView = new LoadingView();
-  pipesViewInstance.addTab(tabKey, tabName, loadingView);
+  pipesViewInstance.addTab(tabKey, tabName, loadingView, () => {
+    stream.end();
+  });
 
   // Add the redirect stream action
   var icon = 'hardware:cast';
diff --git a/examples/pipetobrowser/browser/actions/navigate-help.js b/examples/pipetobrowser/browser/actions/navigate-help.js
index d2c0691..01f2117 100644
--- a/examples/pipetobrowser/browser/actions/navigate-help.js
+++ b/examples/pipetobrowser/browser/actions/navigate-help.js
@@ -12,7 +12,7 @@
 import { HelpView } from 'views/help/view'
 
 var log = new Logger('actions/navigate-help');
-const ACTION_NAME = 'help';
+var ACTION_NAME = 'help';
 
 /*
  * Registers the action
diff --git a/examples/pipetobrowser/browser/actions/redirect-pipe.js b/examples/pipetobrowser/browser/actions/redirect-pipe.js
index 28ce6d9..6bcb93b 100644
--- a/examples/pipetobrowser/browser/actions/redirect-pipe.js
+++ b/examples/pipetobrowser/browser/actions/redirect-pipe.js
@@ -61,7 +61,7 @@
   getAllPublishedP2BNames().then((allNames) => {
     // append current plugin name to the veyron names for better UX
     dialog.existingNames = allNames.map((n) => {
-      return n + (currentPluginName || '');
+      return n + '/' + currentPluginName;
     });
   }).catch((e) => {
     log.debug('getAllPublishedP2BNames failed', e);
diff --git a/examples/pipetobrowser/browser/app.html b/examples/pipetobrowser/browser/app.html
index c592cb2..d6dfbd6 100644
--- a/examples/pipetobrowser/browser/app.html
+++ b/examples/pipetobrowser/browser/app.html
@@ -9,6 +9,14 @@
   <title>Pipe To Browser - because life is too short to stare at unformatted stdout text, and is hard enough already not to have a spell-checker for stdin</title>
 
   <script src="third-party/platform/platform.js"></script>
+  <!-- TODO(aghassemi) use CJS version of Veyron and provide an ES6 shim -->
+  <script src="third-party/veyron/veyron.js"></script>
+
+  <script src="third-party/traceur-runtime@0.0.49.js"></script>
+  <script src="third-party/system@0.6.js"></script>
+  <script src="config.js"></script>
+  <script src="build.js"></script>
+
   <link rel="import" href="views/page/component.html"/>
 
   <style type="text/css">
@@ -19,13 +27,7 @@
   </style>
 </head>
 <body>
-  <!-- TODO(aghassemi) use CJS version of Veyron and provide an ES6 shim -->
-  <script src="third-party/veyron/veyron.js"></script>
 
-  <script src="third-party/traceur-runtime@0.0.49.js"></script>
-  <script src="third-party/system@0.6.js"></script>
-  <script src="config.js"></script>
-  <script src="build.js"></script>
 
   <script>
     window.addEventListener('polymer-ready', function(e) {
diff --git a/examples/pipetobrowser/browser/bower.json b/examples/pipetobrowser/browser/bower.json
index 6ebd373..0217621 100644
--- a/examples/pipetobrowser/browser/bower.json
+++ b/examples/pipetobrowser/browser/bower.json
@@ -2,8 +2,8 @@
   "name": "pipe-to-browser",
   "version": "0.0.1",
   "dependencies": {
-    "polymer": "Polymer/polymer#~0.3.2",
-    "core-elements": "Polymer/core-elements#~0.3.2",
-    "paper-elements": "Polymer/paper-elements#~0.3.2"
+    "polymer": "Polymer/polymer#~0.3.4",
+    "core-elements": "Polymer/core-elements#~0.3.4",
+    "paper-elements": "Polymer/paper-elements#~0.3.4"
   }
 }
diff --git a/examples/pipetobrowser/browser/config.js b/examples/pipetobrowser/browser/config.js
index 2e7b142..4206c8a 100644
--- a/examples/pipetobrowser/browser/config.js
+++ b/examples/pipetobrowser/browser/config.js
@@ -6,6 +6,8 @@
     "view": "libs/mvc/view.js",
     "logger": "libs/logs/logger.js",
     "stream-helpers": "libs/utils/stream-helpers.js",
+    "web-component-loader": "libs/utils/web-component-loader.js",
+    "formatting": "libs/utils/formatting.js",
     "npm:*": "third-party/npm/*.js",
     "github:*": "third-party/github/*.js"
   }
@@ -13,41 +15,43 @@
 
 System.config({
   "map": {
+    "npm:humanize": "npm:humanize@^0.0.9",
     "npm:event-stream": "npm:event-stream@^3.1.5",
     "nodelibs": "github:jspm/nodelibs@master",
     "npm:event-stream@3.1.5": {
-      "through": "npm:through@^2.3.1",
-      "pause-stream": "npm:pause-stream@0.0.11",
       "from": "npm:from@0",
-      "stream-combiner": "npm:stream-combiner@^0.0.4",
       "map-stream": "npm:map-stream@0.1",
+      "pause-stream": "npm:pause-stream@0.0.11",
       "duplexer": "npm:duplexer@^0.1.1",
-      "split": "npm:split@0.2"
+      "through": "npm:through@^2.3.1",
+      "split": "npm:split@0.2",
+      "stream-combiner": "npm:stream-combiner@^0.0.4"
     },
+    "npm:humanize@0.0.9": {},
+    "npm:from@0.1.3": {},
     "npm:stream-combiner@0.0.4": {
       "duplexer": "npm:duplexer@^0.1.1"
     },
+    "npm:duplexer@0.1.1": {},
+    "npm:map-stream@0.1.0": {},
     "npm:pause-stream@0.0.11": {
       "through": "npm:through@2.3"
     },
-    "npm:from@0.1.3": {},
-    "npm:through@2.3.4": {},
-    "npm:duplexer@0.1.1": {},
     "npm:split@0.2.10": {
       "through": "npm:through@2"
     },
-    "npm:map-stream@0.1.0": {},
+    "npm:through@2.3.4": {},
     "github:jspm/nodelibs@0.0.2": {
-      "base64-js": "npm:base64-js@^0.0.4",
       "ieee754": "npm:ieee754@^1.1.1",
-      "inherits": "npm:inherits@^2.0.1",
+      "base64-js": "npm:base64-js@^0.0.4",
       "Base64": "npm:Base64@0.2",
+      "inherits": "npm:inherits@^2.0.1",
       "json": "github:systemjs/plugin-json@master"
     },
     "npm:base64-js@0.0.4": {},
+    "npm:ieee754@1.1.3": {},
     "npm:Base64@0.2.1": {},
     "npm:inherits@2.0.1": {},
-    "npm:ieee754@1.1.3": {},
     "github:jspm/nodelibs@master": {
       "Base64": "npm:Base64@0.2",
       "base64-js": "npm:base64-js@^0.0.4",
@@ -60,22 +64,23 @@
 
 System.config({
   "versions": {
+    "npm:humanize": "0.0.9",
     "npm:event-stream": "3.1.5",
-    "npm:through": "2.3.4",
-    "npm:pause-stream": "0.0.11",
     "npm:from": "0.1.3",
-    "npm:stream-combiner": "0.0.4",
     "npm:map-stream": "0.1.0",
+    "npm:pause-stream": "0.0.11",
     "npm:duplexer": "0.1.1",
+    "npm:through": "2.3.4",
     "npm:split": "0.2.10",
+    "npm:stream-combiner": "0.0.4",
     "github:jspm/nodelibs": [
       "master",
       "0.0.2"
     ],
-    "npm:base64-js": "0.0.4",
     "npm:ieee754": "1.1.3",
-    "npm:inherits": "2.0.1",
+    "npm:base64-js": "0.0.4",
     "npm:Base64": "0.2.1",
+    "npm:inherits": "2.0.1",
     "github:systemjs/plugin-json": "master"
   }
 });
diff --git a/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/column/renderer.html b/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/column/renderer.html
index cb2f8c9..208ff49 100644
--- a/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/column/renderer.html
+++ b/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/column/renderer.html
@@ -38,9 +38,9 @@
         }
 
         if (this.gridState.sort.ascending) {
-          return this.data.label + ' \u21A7'; // up wedge unicode character
+          return this.data.label + ' \u21A5'; // up wedge unicode character
         } else {
-          return this.data.label + ' \u21A5'; // down wedge unicode character
+          return this.data.label + ' \u21A7'; // down wedge unicode character
         }
       }
     });
diff --git a/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/component.css b/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/component.css
index 87cdff3..b309a31 100644
--- a/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/component.css
+++ b/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/component.css
@@ -26,6 +26,7 @@
 
 .more-icon {
   fill: #0a7e07;
+  color: #0a7e07;
 }
 
 paper-dialog {
@@ -33,11 +34,6 @@
   width: 80vw;
 }
 
-/* hack: wrong z-index in shadow of paper-dialog disables text selection in dialog */
-paper-dialog /deep/ #shadow {
-  z-index: -1;
-}
-
 .more-dialog-content .heading {
   font-size: 1.0em;
   padding-top: 0.2em;
@@ -85,6 +81,10 @@
   display: initial;
 }
 
+.more-dialog-content [gridOnly] {
+  display:none;
+}
+
 .paginator {
   display: inline-block;
   border: solid 1px rgba(0, 0, 0, 0.05);
@@ -97,4 +97,4 @@
 
 .paginator paper-icon-button {
   vertical-align: middle;
-}
\ No newline at end of file
+}
diff --git a/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/component.html b/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/component.html
index a67f7dd..974cc97 100644
--- a/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/component.html
+++ b/examples/pipetobrowser/browser/libs/ui-components/data-grid/grid/component.html
@@ -157,15 +157,15 @@
 
       /*
        * Number if items displayed in each page.
-       * Defaults to 30
+       * Defaults to 20
        * @type {integer}
        */
-      pageSize: 30,
+      pageSize: 20,
 
       showMoreInfo: function(e) {
         var item = e.target.templateInstance.model.item;
         this.selectedItems = [item];
-        this.$.dialog.opened = true;
+        this.$.dialog.toggle();
       },
 
       ready: function() {
@@ -333,7 +333,7 @@
           col.columnData.flex = col.columnData.origFlex;
         }
 
-        if (tableWidth >= minWidth) {
+        if (tableWidth === 0 || tableWidth >= minWidth) {
           return;
         }
 
diff --git a/examples/pipetobrowser/browser/libs/utils/byte-object-stream-adapter.js b/examples/pipetobrowser/browser/libs/utils/byte-object-stream-adapter.js
index d12c998..90593e1 100644
--- a/examples/pipetobrowser/browser/libs/utils/byte-object-stream-adapter.js
+++ b/examples/pipetobrowser/browser/libs/utils/byte-object-stream-adapter.js
@@ -3,9 +3,6 @@
 
 var Transform = Stream.Transform;
 var Buffer = buffer.Buffer;
-// TODO(aghassemi) doesn't look like ES6 and CommonJS modules can use the same
-// syntax to be referenced, but research more, maybe something can be done at
-// built time.
 
 /*
  * Adapts a stream of byte arrays in object mode to a regular stream of Buffer
diff --git a/examples/pipetobrowser/browser/libs/utils/formatting.js b/examples/pipetobrowser/browser/libs/utils/formatting.js
new file mode 100644
index 0000000..26f90e6
--- /dev/null
+++ b/examples/pipetobrowser/browser/libs/utils/formatting.js
@@ -0,0 +1,23 @@
+import { default as humanize } from 'npm:humanize'
+
+export function formatDate(d) {
+  if(d === undefined || d == null) { return; }
+  var naturalDay = humanize.naturalDay(d.getTime() / 1000);
+  var naturalTime = humanize.date('g:i a', d);
+  return naturalDay + ' at ' + naturalTime;
+}
+
+export function formatRelativeTime(d) {
+  if(d === undefined || d == null) { return; }
+  return humanize.relativeTime(d.getTime() / 1000);
+}
+
+export function formatInteger(n) {
+  if(n === undefined || n == null) { return; }
+  return humanize.numberFormat(n, 0);
+}
+
+export function formatBytes(b) {
+  if(b === undefined || b == null) { return; }
+  return humanize.filesize(b);
+}
\ No newline at end of file
diff --git a/examples/pipetobrowser/browser/libs/utils/stream-copy.js b/examples/pipetobrowser/browser/libs/utils/stream-copy.js
index 4b7e386..c940213 100644
--- a/examples/pipetobrowser/browser/libs/utils/stream-copy.js
+++ b/examples/pipetobrowser/browser/libs/utils/stream-copy.js
@@ -17,6 +17,13 @@
     // TODO(aghassemi) make this a FIFO buffer with reasonable max-size
     this.buffer = [];
     this.copies = [];
+    var self = this;
+    this.on('end', () => {
+      self.ended = true;
+      for (var i=0; i < self.copies.length; i++) {
+        self.copies[i].end();
+      }
+    });
   }
 
   _transform(chunk, encoding, cb) {
@@ -42,7 +49,12 @@
         copy.push(this.buffer[i]);
       }
     }
-    this.copies.push(copy);
+    if (this.ended) {
+      copy.push(null);
+    } else {
+      this.copies.push(copy);
+    }
+
     return copy;
   }
 }
diff --git a/examples/pipetobrowser/browser/libs/utils/time.js b/examples/pipetobrowser/browser/libs/utils/time.js
deleted file mode 100644
index be9b42d..0000000
--- a/examples/pipetobrowser/browser/libs/utils/time.js
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Given a time duration in seconds, formats it as h' hours, m' minutes, s' seconds
- * in EN-US.
- * @param {integer} durationInSeconds Time period in seconds
- * @return {string} EN-US formatted time period.
- */
-export function formatDuration(durationInSeconds) {
-  var hours = Math.floor(durationInSeconds/3600);
-  var minutes = Math.floor((durationInSeconds - (hours*3600))/60);
-  var seconds = durationInSeconds - (hours*3600) - (minutes*60);
-
-  return _pluralize('hour', hours, true) +
-  _pluralize('minute', minutes, true) +
-  _pluralize('second', seconds, false);
-}
-
-function _pluralize(name, value, returnEmptyIfZero) {
-  if(value == 0 && returnEmptyIfZero) {
-    return '';
-  }
-  if(value != 1) {
-    name = name + 's';
-  }
-  return value + ' ' + name + ' ';
-}
\ No newline at end of file
diff --git a/examples/pipetobrowser/browser/libs/utils/web-component-loader.js b/examples/pipetobrowser/browser/libs/utils/web-component-loader.js
new file mode 100644
index 0000000..bb37e0c
--- /dev/null
+++ b/examples/pipetobrowser/browser/libs/utils/web-component-loader.js
@@ -0,0 +1,11 @@
+export function importComponent(path) {
+	return new Promise((resolve, reject) => {
+		var link = document.createElement('link');
+		link.setAttribute('rel', 'import');
+		link.setAttribute('href', path);
+		link.onload = function() {
+		  resolve();
+		};
+		document.body.appendChild(link);
+	});
+}
\ No newline at end of file
diff --git a/examples/pipetobrowser/browser/package.json b/examples/pipetobrowser/browser/package.json
index 773f16f..93fe433 100644
--- a/examples/pipetobrowser/browser/package.json
+++ b/examples/pipetobrowser/browser/package.json
@@ -6,6 +6,7 @@
   },
   "dependencies": {
     "npm:event-stream": "^3.1.5",
+    "npm:humanize": "^0.0.9",
     "nodelibs": "master"
   }
 }
\ No newline at end of file
diff --git a/examples/pipetobrowser/browser/pipe-viewers/builtin/console/component.css b/examples/pipetobrowser/browser/pipe-viewers/builtin/console/component.css
index 2091d34..d1ef530 100644
--- a/examples/pipetobrowser/browser/pipe-viewers/builtin/console/component.css
+++ b/examples/pipetobrowser/browser/pipe-viewers/builtin/console/component.css
@@ -15,3 +15,12 @@
 
   color: #ffffff;
 }
+
+.auto-scroll {
+  position: fixed;
+  right: 40px;
+  bottom: 0;
+  opacity: 0.8;
+  padding: 0.8em;
+  background-color: #ffeb3b;
+}
diff --git a/examples/pipetobrowser/browser/pipe-viewers/builtin/console/component.html b/examples/pipetobrowser/browser/pipe-viewers/builtin/console/component.html
index d816aed..5505f9b 100644
--- a/examples/pipetobrowser/browser/pipe-viewers/builtin/console/component.html
+++ b/examples/pipetobrowser/browser/pipe-viewers/builtin/console/component.html
@@ -12,8 +12,11 @@
   </template>
   <script>
     Polymer('p2b-plugin-console', {
-      autoScroll: true,
-      textBuffer: [],
+      ready: function() {
+        this.textBuffer = [];
+        this.autoScroll = true;
+      },
+
       attached: function() {
         this.renderLoop();
       },
diff --git a/examples/pipetobrowser/browser/pipe-viewers/builtin/git/status/component.css b/examples/pipetobrowser/browser/pipe-viewers/builtin/git/status/component.css
index 9b266fe..99617a1 100644
--- a/examples/pipetobrowser/browser/pipe-viewers/builtin/git/status/component.css
+++ b/examples/pipetobrowser/browser/pipe-viewers/builtin/git/status/component.css
@@ -7,7 +7,8 @@
 }
 
 ::shadow /deep/ .state-icon.conflicted {
-  fill: #e51c23;
+  fill: #bf360c;
+  -webkit-animation: blink 0.6s infinite alternate;
 }
 
 ::shadow /deep/ .state-icon.untracked {
@@ -15,7 +16,7 @@
 }
 
 ::shadow /deep/ .state-icon.ignored {
-  fill: #bf360c;
+  fill: #689f38;
 }
 
 ::shadow /deep/ .action-icon {
diff --git a/examples/pipetobrowser/browser/pipe-viewers/builtin/git/status/plugin.js b/examples/pipetobrowser/browser/pipe-viewers/builtin/git/status/plugin.js
index 7ac8c63..11583c5 100644
--- a/examples/pipetobrowser/browser/pipe-viewers/builtin/git/status/plugin.js
+++ b/examples/pipetobrowser/browser/pipe-viewers/builtin/git/status/plugin.js
@@ -91,10 +91,10 @@
       iconName = 'warning';
       break;
     case 'conflicted':
-      iconName = 'error';
+      iconName = 'block';
       break;
     case 'untracked':
-      iconName = 'report';
+      iconName = 'error';
       break;
     case 'ignored':
       iconName = 'visibility-off';
@@ -122,7 +122,7 @@
       iconName = 'translate';
       break;
     case 'renamed':
-      iconName = 'sync';
+      iconName = 'swap-horiz';
       break;
     case 'copied':
       iconName = 'content-copy';
diff --git a/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/component.html b/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/component.html
index 59f4f28..3a70a5c 100644
--- a/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/component.html
+++ b/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/component.html
@@ -12,7 +12,7 @@
     <link rel="stylesheet" href="../../../libs/css/common-style.css">
     <link rel="stylesheet" href="component.css">
 
-    <p2b-grid id="grid" defaultSortKey="date" defaultSortAscending dataSource="{{ dataSource }}" summary="Data Grid displaying veyron log items in a tabular format with filters and search options.">
+    <p2b-grid id="grid" defaultSortKey="date" dataSource="{{ dataSource }}" summary="Data Grid displaying veyron log items in a tabular format with filters and search options.">
 
       <!-- Search -->
       <p2b-grid-search label="Search Logs"></p2b-grid-search>
@@ -48,8 +48,12 @@
       <p2b-grid-column label="Message" key="message" primary flex="8" minFlex="5" priority="1" >
         <template><div class="message-text">{{ item.message }}</div></template>
       </p2b-grid-column>
-      <p2b-grid-column label="Date" key="date" sortable flex="6" minFlex="3" priority="3">
-        <template><span class="smaller-text">{{ item.date }}</span></template>
+      <p2b-grid-column label="Date" key="date" sortable flex="4" minFlex="3" priority="3">
+        <template>
+          <abbr gridOnly title="{{item.date}}">{{ item.formattedDate }}</abbr>
+          <span moreInfoOnly>{{item.date}}</span>
+        </template>
+
       </p2b-grid-column>
       <p2b-grid-column label="Threadid" key="threadid" sortable flex="0" priority="5">
         <template>{{ item.threadId }}</template>
diff --git a/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/plugin.js b/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/plugin.js
index 76d52a2..db749ff 100644
--- a/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/plugin.js
+++ b/examples/pipetobrowser/browser/pipe-viewers/builtin/vlog/plugin.js
@@ -9,6 +9,7 @@
 import { View } from 'view';
 import { PipeViewer } from 'pipe-viewer';
 import { streamUtil } from 'stream-helpers';
+import { formatDate } from 'formatting';
 import { Logger } from 'logger'
 import { vLogDataSource } from './data-source';
 
@@ -51,6 +52,7 @@
  */
 function addAdditionalUIProperties(item) {
   addIconProperty(item);
+  addFormattedDate(item);
 }
 
 /*
@@ -78,4 +80,12 @@
   item.icon = iconName;
 }
 
+/*
+ * Adds a human friendly date field
+ * @private
+ */
+function addFormattedDate(item) {
+  item.formattedDate = formatDate(item.date);
+}
+
 export default vLogPipeViewer;
\ No newline at end of file
diff --git a/examples/pipetobrowser/browser/pipe-viewers/manager.js b/examples/pipetobrowser/browser/pipe-viewers/manager.js
index d135741..4c7dea0 100644
--- a/examples/pipetobrowser/browser/pipe-viewers/manager.js
+++ b/examples/pipetobrowser/browser/pipe-viewers/manager.js
@@ -82,7 +82,9 @@
  */
 function getPath(name) {
   if(isAbsoulteUrl(name)) {
-    return name;
+    var encodedName = encodeURIComponent(name);
+    System.paths[encodedName] = name;
+    return encodedName;
   } else {
     return 'pipe-viewers/builtin/' + name + '/plugin';
   }
diff --git a/examples/pipetobrowser/browser/services/pipe-to-browser-server.js b/examples/pipetobrowser/browser/services/pipe-to-browser-server.js
index cf04487..ac4e0f4 100644
--- a/examples/pipetobrowser/browser/services/pipe-to-browser-server.js
+++ b/examples/pipetobrowser/browser/services/pipe-to-browser-server.js
@@ -69,19 +69,32 @@
         var stream = streamCopier.pipe(bufferStream).pipe(streamByteCounter);
         stream.copier = streamCopier;
 
-        bufferStream.on('end', () => {
+        streamByteCounter.on('end', () => {
           log.debug('end of stream');
           // send total number of bytes received for this call as final result
-          resolve(numBytesForThisCall);
+          resolve();
         });
 
-        bufferStream.on('error', (e) => {
+        stream.on('error', (e) => {
           log.debug('stream error', e);
-          reject(e);
+          // TODO(aghassemi) envyor issue #50
+          // we want to reject but because of #50 we can't
+          // reject('Browser P2B threw an exception. Please see browser console for details.');
+          // reject(e);
+          resolve();
         });
 
         state.numPipes++;
-        pipeRequestHandler($suffix, stream);
+        try {
+          pipeRequestHandler($suffix, stream);
+        } catch(e) {
+          // TODO(aghassemi) envyor issue #50
+          // we want to reject but because of #50 we can't
+          // reject('Browser P2B threw an exception. Please see browser console for details.');
+          log.debug('pipeRequestHandler error', e);
+          resolve();
+        }
+
       });
     }
   };
diff --git a/examples/pipetobrowser/browser/views/help/component.css b/examples/pipetobrowser/browser/views/help/component.css
index faf1990..1e2e557 100644
--- a/examples/pipetobrowser/browser/views/help/component.css
+++ b/examples/pipetobrowser/browser/views/help/component.css
@@ -29,4 +29,5 @@
 a {
   color: #5677fc;
   text-decoration: none;
+  cursor: pointer;
 }
\ No newline at end of file
diff --git a/examples/pipetobrowser/browser/views/help/component.html b/examples/pipetobrowser/browser/views/help/component.html
index 46e1d2c..ba5112c 100644
--- a/examples/pipetobrowser/browser/views/help/component.html
+++ b/examples/pipetobrowser/browser/views/help/component.html
@@ -1,4 +1,4 @@
-<link rel="import" href="/libs/vendor/polymer/polymer/polymer.html">
+<link rel="import" href="../../third-party/polymer/polymer.html">
 
 <polymer-element name="p2b-help">
 <template>
@@ -40,10 +40,13 @@
   <pre class="code">cat /dev/urandom | p2b {{publishedName}}/dev/null</pre>
 
   <h3>Remote Viewers</h3>
-  <p>In addition to built-in viewers, ad-hoc remote viewers can be hosted anywhere and used with P2B. Remote viewers are referenced by their Url without the .js extension at the end og the plug-in JavaScript file</p>
-  <pre class="code">echo "Hello World" | p2b {{publishedName}}/http://googledrive.com/host/0BzmT5cnKdCAKa3hzNEVCU2tnd3c/helloworld</pre>
+  <p>In addition to built-in viewers, ad-hoc remote viewers can be hosted anywhere and used with P2B. Remote viewers are referenced by the Url of the plug-in JavaScript file</p>
+  <pre class="code">echo "Hello World" | p2b {{publishedName}}/http://googledrive.com/host/0BzmT5cnKdCAKa3hzNEVCU2tnd3c/helloworld.js</pre>
   <p>Writing remote viewers is not different than writing built-in ones and basic plug-ins are pretty straight forward to write.</p>
   <p>At high level, plug-ins are expected to implement a <span class="mono">PipeViewer</span> interface which has a <span class="mono">play(stream)</span> method. A <span class="mono">view</span> (which is a wrapper for a DOM element) is expected to be returned from <span class="mono">play(stream)</span>. You can look at the hello world remote plug-in <a href="http://googledrive.com/host/0BzmT5cnKdCAKa3hzNEVCU2tnd3c/helloworld.js" target="_blank">code on Google drive</a> to get started on writing new remote plug-ins</p>
+  <p>It is also possible to write the UI layer of your plug-in in HTML and CSS as a Web Component to avoid mixing logic and layout/styling in a single file.</p>
+  <p>Grumpy cat meme plug-in takes that approach. You can look at the <a href="http://googledrive.com/host/0BzmT5cnKdCAKV1p6Q0pjak5Kams/meme.js" target="_blank">JavaScript</a> and <a onClick="window.open('view-source:' + 'http://googledrive.com/host/0BzmT5cnKdCAKV1p6Q0pjak5Kams/meme.html');">HTML Web Component</a> source files.</p>
+  <pre class="code">echo "I take stuff from stdin, and send them to /dev/null" | p2b {{publishedName}}/http://googledrive.com/host/0BzmT5cnKdCAKV1p6Q0pjak5Kams/meme.js</pre>
 </template>
 <script>
   Polymer('p2b-help', {
diff --git a/examples/pipetobrowser/browser/views/page/component.css b/examples/pipetobrowser/browser/views/page/component.css
index 787f3bb..793659b 100644
--- a/examples/pipetobrowser/browser/views/page/component.css
+++ b/examples/pipetobrowser/browser/views/page/component.css
@@ -11,12 +11,10 @@
 
 [sidebar] {
   padding: 0.5em;
-  fill: #9e9e9e;
 }
 
 [sidebar] .core-selected {
   color: #00bcd4;
-  fill: #212121;
 }
 
 [main] {
diff --git a/examples/pipetobrowser/browser/views/page/component.html b/examples/pipetobrowser/browser/views/page/component.html
index 0993f2a..f1ab37c 100644
--- a/examples/pipetobrowser/browser/views/page/component.html
+++ b/examples/pipetobrowser/browser/views/page/component.html
@@ -15,6 +15,7 @@
 <link rel="import" href="../../views/publish/component.html"/>
 <link rel="import" href="../../views/status/component.html"/>
 <link rel="import" href="../../views/error/component.html"/>
+<link rel="import" href="../../views/help/component.html"/>
 <link rel="import" href="../../views/loading/component.html"/>
 <link rel="import" href="../../views/pipes/component.html"/>
 <link rel="import" href="../../views/redirect-pipe-dialog/component.html"/>
diff --git a/examples/pipetobrowser/browser/views/pipes/component.html b/examples/pipetobrowser/browser/views/pipes/component.html
index 9558d16..c877519 100644
--- a/examples/pipetobrowser/browser/views/pipes/component.html
+++ b/examples/pipetobrowser/browser/views/pipes/component.html
@@ -61,8 +61,9 @@
        * @param {string} key Key of the tab to add
        * @param {string} name Name of the tab to add
        * @param {DOMElement} el Content of the tab
+       * @param {function} onClose Optional onClose callback.
        */
-      addTab: function(key, name, el) {
+      addTab: function(key, name, el, onClose) {
         var self = this;
 
         // Create a tab thumb
@@ -75,6 +76,9 @@
         tabToolbar.title = name;
         tabToolbar.addEventListener('close-action', function() {
           self.removeTab(key);
+          if (onClose) {
+            onClose();
+          }
         });
         tabToolbar.addEventListener('fullscreen-action', function() {
           var tabContent = self.pipeTabs[key].tabContent;
@@ -88,7 +92,6 @@
         tabContent.appendChild(el);
 
         this.$.tabPages.appendChild(tabContent);
-        this.$.tabs.appendChild(tab);
 
         // Add the tab to our list.
         this.pipeTabs[key] = {
@@ -99,6 +102,9 @@
         };
 
         this.selectedTabKey = key;
+        requestAnimationFrame(function() {
+          self.$.tabs.appendChild(tab);
+        });
       },
 
       /*
@@ -108,6 +114,9 @@
        * @param onClick {function} event handler for the action
        */
       addToolbarAction: function(tabKey, icon, onClick) {
+        if (!this.pipeTabs[tabKey]) {
+          return;
+        }
         var toolbar = this.pipeTabs[tabKey].tabToolbar;
         toolbar.add(icon, onClick);
       },
@@ -117,6 +126,9 @@
        * @param {string} key Key of the tab to remove
        */
       removeTab: function(key) {
+        if (!this.pipeTabs[key]) {
+          return;
+        }
         // Remove tab thumb and content
         var tab = this.pipeTabs[key].tab;
         tab.remove();
@@ -144,6 +156,9 @@
        * @param {DOMElement} el New content of the tab
        */
       replaceTabContent: function(key, newName, newEl) {
+        if (!this.pipeTabs[key]) {
+          return;
+        }
         var tabContent = this.pipeTabs[key].tabContent;
         tabContent.replaceTabContent(newEl);
         if (newName) {
diff --git a/examples/pipetobrowser/browser/views/pipes/view.js b/examples/pipetobrowser/browser/views/pipes/view.js
index 8c9e2b4..78fcd20 100644
--- a/examples/pipetobrowser/browser/views/pipes/view.js
+++ b/examples/pipetobrowser/browser/views/pipes/view.js
@@ -18,9 +18,10 @@
   * @param {string} name A short name for the tab that will be displayed as
   * the tab title
   * @param {View} view View to show inside the tab.
+  * @param {function} onClose Optional onClose callback.
   */
-  addTab(key, name, view) {
-    this.element.addTab(key, name, view.element);
+  addTab(key, name, view, onClose) {
+    this.element.addTab(key, name, view.element, onClose);
   }
 
   /*
diff --git a/examples/pipetobrowser/browser/views/redirect-pipe-dialog/component.css b/examples/pipetobrowser/browser/views/redirect-pipe-dialog/component.css
index a58b136..b1bdca0 100644
--- a/examples/pipetobrowser/browser/views/redirect-pipe-dialog/component.css
+++ b/examples/pipetobrowser/browser/views/redirect-pipe-dialog/component.css
@@ -11,11 +11,6 @@
   width: 80vw;
 }
 
-/* hack: wrong z-index in shadow of paper-dialog disables text selection in dialog */
-paper-dialog /deep/ #shadow {
-  z-index: -1 !important;
-}
-
 paper-button[affirmative] {
   color: #4285f4;
 }
diff --git a/examples/pipetobrowser/browser/views/status/component.html b/examples/pipetobrowser/browser/views/status/component.html
index ca511a2..8362866 100644
--- a/examples/pipetobrowser/browser/views/status/component.html
+++ b/examples/pipetobrowser/browser/views/status/component.html
@@ -1,4 +1,5 @@
 <link rel="import" href="../../third-party/polymer/polymer.html">
+<link rel="import" href="../../third-party/paper-button/paper-button.html">
 
 <polymer-element name="p2b-status" attributes="status">
 
@@ -6,118 +7,83 @@
     <link rel="stylesheet" href="../common/common.css">
     <link rel="stylesheet" href="component.css">
     <h3>Status</h3>
-    <p>{{ statusText }}</p>
-    <div class="{{ {hidden : !serviceState.published} | tokenList}}">
+    <p>{{ serviceState | formatServiceState }}</p>
+    <div class="{{ {hidden : !serviceState.published} | tokenList }}">
       <h3>Name</h3>
       <p>{{ serviceState.fullServiceName }}</p>
 
       <h3>Published on</h3>
-      <p>{{ publishDate }}</p>
+      <p>{{ serviceState.date | formatDate }}</p>
 
-      <h3>Uptime</h3>
-      <p>{{ uptime }}</p>
+      <h3>Running Since</h3>
+      <p>{{ runningSince }}</p>
 
       <h3>Number of pipe requests</h3>
-      <p>{{ numPipes }}</p>
+      <p>{{ serviceState.numPipes | formatInteger }}</p>
 
       <h3>Total bytes received</h3>
-      <p>{{ numBytes }}</p>
+      <p>{{ serviceState.numBytes | formatBytes }}</p>
     </div>
-    <paper-button class="paper colored red" inkColor="#A9352C" on-click="{{ stopAction }}">Stop Service</paper-button>
+    <paper-button class="paper colored red" inkColor="#A9352C" on-click="{{ stopAction }}">Stop</paper-button>
   </template>
   <script>
-    Polymer('p2b-status', {
+    System.import('libs/utils/formatting').then(function(formatter) {
+      Polymer('p2b-status', {
 
-      /*
-       * Dynamic binding for the state of publishing p2b service.
-       * Any changes to this object will be reflected in the UI automatically
-       */
-      serviceState: null,
+        /*
+         * Dynamic binding for the state of publishing p2b service.
+         * Any changes to this object will be reflected in the UI automatically
+         */
+        serviceState: null,
 
-      /*
-       * A function that can format time duration
-       * @private
-       * @type {function}
-       */
-      formatDuration: null,
+        /*
+         * Human friendly formatting functions. Because polymer filter expressions
+         * don't accept obj.func we wrap them here
+         * @private
+         */
+        formatDate: formatter.formatDate,
+        formatInteger: formatter.formatInteger,
+        formatBytes: formatter.formatBytes,
 
-      /*
-       * Status text
-       * @private
-       * @type {string}
-       */
-      get statusText() {
-        if (!this.serviceState) {
-          return '';
+        /*
+         * Auto-updating Uptime text
+         * @private
+         * @type {string}
+         */
+        get runningSince() {
+          if (!this.serviceState) { return; }
+          return formatter.formatRelativeTime(this.serviceState.date);
+        },
+
+        /*
+         * Status text
+         * @private
+         * @type {string}
+         */
+        formatServiceState: function(serviceState) {
+          if (!serviceState) {
+            return '';
+          }
+          if (serviceState.published) {
+            return 'Published';
+          } else if(serviceState.publishing) {
+            return 'Publishing';
+          } else if(serviceState.stopping) {
+            return 'Stopping';
+          }  else {
+            return 'Stopped';
+          }
+        },
+
+        /*
+         * Stop action. Fires when user decides to stop the p2b service.
+         * @event
+         */
+        stopAction: function() {
+          this.fire('stop');
         }
-        if (this.serviceState.published) {
-          return 'Published';
-        } else if(this.serviceState.publishing) {
-          return 'Publishing';
-        } else if(this.serviceState.stopping) {
-          return 'Stopping';
-        }  else {
-          return 'Stopped';
-        }
-      },
 
-      /*
-       * Formatted publish date
-       * @private
-       * @type {string}
-       */
-      get publishDate() {
-        if (!this.serviceState || !this.serviceState.published) {
-          return '';
-        }
-        return this.serviceState.date.toString();
-      },
-
-      /*
-       * Formatted uptime
-       * @private
-       * @type {string}
-       */
-      get uptime() {
-        if (!this.serviceState || !this.serviceState.published) {
-          return '';
-        }
-        var elapsedSeconds = Math.floor((new Date() - this.serviceState.date) / 1000);
-        return this.formatDuration(elapsedSeconds);
-      },
-
-      /*
-       * Formatted number of pipes
-       * @private
-       * @type {string}
-       */
-      get numPipes() {
-        if (!this.serviceState || !this.serviceState.published) {
-          return '';
-        }
-        return this.serviceState.numPipes.toString();
-      },
-
-      /*
-       * Formatted number of bytes
-       * @private
-       * @type {string}
-       */
-      get numBytes() {
-        if (!this.serviceState || !this.serviceState.published) {
-          return '';
-        }
-        return this.serviceState.numBytes.toString();
-      },
-
-      /*
-       * Stop action. Fires when user decides to stop the p2b service.
-       * @event
-       */
-      stopAction: function() {
-        this.fire('stop');
-      }
-
+      });
     });
   </script>
 </polymer-element>
\ No newline at end of file
diff --git a/examples/pipetobrowser/browser/views/status/view.js b/examples/pipetobrowser/browser/views/status/view.js
index 0f15358..8792b8c 100644
--- a/examples/pipetobrowser/browser/views/status/view.js
+++ b/examples/pipetobrowser/browser/views/status/view.js
@@ -1,5 +1,4 @@
 import { View } from 'libs/mvc/view'
-import { formatDuration } from 'libs/utils/time'
 
 /*
  * View representing the state and interaction for publishing the p2b service.
@@ -10,11 +9,6 @@
 	constructor(serviceState) {
 		var el = document.createElement('p2b-status');
 		el.serviceState = serviceState;
-
-    // TODO(aghassemi) ES6 import syntax doesn't seem to work inside Polymer
-    // script tag. Test again when compiling server-side with Traceur.
-    el.formatDuration = formatDuration;
-
 		super(el);
 	}
 
diff --git a/examples/pipetobrowser/p2b.vdl b/examples/pipetobrowser/p2b.vdl
index eff991e..3a20904 100644
--- a/examples/pipetobrowser/p2b.vdl
+++ b/examples/pipetobrowser/p2b.vdl
@@ -3,5 +3,5 @@
 // Viewer allows clients to stream data to it and to request a particular viewer to format and display the data.
 type Viewer interface {
   // Pipe creates a bidirectional pipe between client and viewer service, returns total number of bytes received by the service after streaming ends
-  Pipe() stream<[]byte, _> (int64, error)
+  Pipe() stream<[]byte, _> (any, error)
 }
\ No newline at end of file
diff --git a/examples/pipetobrowser/p2b.vdl.go b/examples/pipetobrowser/p2b.vdl.go
index 422123b..bfa0f47 100644
--- a/examples/pipetobrowser/p2b.vdl.go
+++ b/examples/pipetobrowser/p2b.vdl.go
@@ -31,7 +31,7 @@
 type ViewerService interface {
 
 	// Pipe creates a bidirectional pipe between client and viewer service, returns total number of bytes received by the service after streaming ends
-	Pipe(context _gen_ipc.ServerContext, stream ViewerServicePipeStream) (reply int64, err error)
+	Pipe(context _gen_ipc.ServerContext, stream ViewerServicePipeStream) (reply _gen_vdlutil.Any, err error)
 }
 
 // ViewerPipeStream is the interface for streaming responses of the method
@@ -50,7 +50,7 @@
 
 	// Finish closes the stream and returns the positional return values for
 	// call.
-	Finish() (reply int64, err error)
+	Finish() (reply _gen_vdlutil.Any, err error)
 
 	// Cancel cancels the RPC, notifying the server to stop processing.
 	Cancel()
@@ -69,7 +69,7 @@
 	return c.clientCall.CloseSend()
 }
 
-func (c *implViewerPipeStream) Finish() (reply int64, err error) {
+func (c *implViewerPipeStream) Finish() (reply _gen_vdlutil.Any, err error) {
 	if ierr := c.clientCall.Finish(&reply, &err); ierr != nil {
 		err = ierr
 	}
@@ -208,14 +208,14 @@
 	result.Methods["Pipe"] = _gen_ipc.MethodSignature{
 		InArgs: []_gen_ipc.MethodArgument{},
 		OutArgs: []_gen_ipc.MethodArgument{
-			{Name: "", Type: 37},
 			{Name: "", Type: 65},
+			{Name: "", Type: 66},
 		},
-		InStream: 67,
+		InStream: 68,
 	}
 
 	result.TypeDefs = []_gen_vdlutil.Any{
-		_gen_wiretype.NamedPrimitiveType{Type: 0x1, Name: "error", Tags: []string(nil)}, _gen_wiretype.NamedPrimitiveType{Type: 0x32, Name: "byte", Tags: []string(nil)}, _gen_wiretype.SliceType{Elem: 0x42, Name: "", Tags: []string(nil)}}
+		_gen_wiretype.NamedPrimitiveType{Type: 0x1, Name: "anydata", Tags: []string(nil)}, _gen_wiretype.NamedPrimitiveType{Type: 0x1, Name: "error", Tags: []string(nil)}, _gen_wiretype.NamedPrimitiveType{Type: 0x32, Name: "byte", Tags: []string(nil)}, _gen_wiretype.SliceType{Elem: 0x43, Name: "", Tags: []string(nil)}}
 
 	return result, nil
 }
@@ -238,7 +238,7 @@
 	return
 }
 
-func (__gen_s *ServerStubViewer) Pipe(call _gen_ipc.ServerCall) (reply int64, err error) {
+func (__gen_s *ServerStubViewer) Pipe(call _gen_ipc.ServerCall) (reply _gen_vdlutil.Any, err error) {
 	stream := &implViewerServicePipeStream{serverCall: call}
 	reply, err = __gen_s.service.Pipe(call, stream)
 	return
diff --git a/examples/pipetobrowser/cli/main.go b/examples/pipetobrowser/p2b/main.go
similarity index 85%
rename from examples/pipetobrowser/cli/main.go
rename to examples/pipetobrowser/p2b/main.go
index 5ecef74..71c2cfb 100644
--- a/examples/pipetobrowser/cli/main.go
+++ b/examples/pipetobrowser/p2b/main.go
@@ -8,6 +8,8 @@
 	"io"
 	"os"
 
+	"veyron2"
+	"veyron2/ipc"
 	"veyron2/rt"
 
 	"veyron/examples/pipetobrowser"
@@ -73,7 +75,7 @@
 		return
 	}
 
-	stream, err := s.Pipe(runtime.NewContext())
+	stream, err := s.Pipe(runtime.NewContext(), veyron2.CallTimeout(ipc.NoTimeout))
 	if err != nil {
 		log.Errorf("failed to pipe to '%s' please ensure p2b service is running in the browser and name is correct.\nERR:%v", name, err)
 		return
@@ -83,24 +85,17 @@
 		stream,
 	}
 
-	numBytes, err := io.Copy(w, os.Stdin)
+	_, err = io.Copy(w, os.Stdin)
 	if err != nil {
 		log.Errorf("failed to copy the stdin pipe to the outgoing stream\nERR:%v", err)
 		return
 	}
 
-	stream.CloseSend()
-	result, err := stream.Finish()
+	_, err = stream.Finish()
 	if err != nil {
 		log.Errorf("error finishing stream: %v", err)
 		return
 	}
 
-	if numBytes != result {
-		log.Infof("*** number of bytes sent and received do NOT match ***")
-	}
-	log.Infof("%d bytes were piped to browser", numBytes)
-	log.Infof("%d bytes were received by browser", result)
-
 	fmt.Println("Finished piping to browser! Thanks for using p2b.")
 }