todosapp: various tweaks and fixes for demo

- handle concurrent delete and read-modify-write
- tweak instructions to use hostname/ip (not localhost)
  so that the mount table name (and thus SG names) are
  accessible to peers
- make "make node_modules" do "make node_modules" in
  javascript/syncbase repo (to link vanadium there)
- make 'change' event handler update todos for all lists
  if the change arrived via sync (since in that case the
  change might not be for the current list)

Change-Id: I1834b02aa42f1a2d24b2abe94330f1689116bc85
diff --git a/Makefile b/Makefile
index 810b922..3156e47 100644
--- a/Makefile
+++ b/Makefile
@@ -50,18 +50,19 @@
 node_modules: package.json $(shell $(FIND) $(V23_ROOT)/roadmap/javascript/syncbase/{package.json,src} $(V23_ROOT)/release/javascript/core/{package.json,src})
 	npm prune
 	npm install
-	touch $@
 # Link the vanadium and syncbase modules from V23_ROOT.
 	rm -rf ./node_modules/{vanadium,syncbase}
 	cd "$(V23_ROOT)/release/javascript/core" && npm link
 	npm link vanadium
-	cd "$(V23_ROOT)/roadmap/javascript/syncbase" && npm link
+# Note, we run "make node_modules" in the JS syncbase repo to ensure that the
+# vanadium module is linked there.
+	cd "$(V23_ROOT)/roadmap/javascript/syncbase" && make node_modules && npm link
 	npm link syncbase
 # Note, browserify 10.2.5 and up will share the vanadium module instance between
 # todosapp and syncbase, since their node_modules symlinks point to a common
 # location.
 # https://github.com/substack/node-browserify/issues/1063
-	touch node_modules
+	touch $@
 
 public/bundle.min.css: $(shell find stylesheets) node_modules
 	lessc -sm=on stylesheets/index.less | postcss -u autoprefixer -u cssnano > $@
@@ -83,6 +84,7 @@
 .PHONY: clean
 clean:
 	rm -rf bin node_modules public/bundle.*
+	v23 goext distclean
 
 .PHONY: lint
 lint:
diff --git a/browser/index.js b/browser/index.js
index b4d8453..f916a9e 100644
--- a/browser/index.js
+++ b/browser/index.js
@@ -649,12 +649,19 @@
 
     // TODO(sadovsky): Only read (and only redraw) what's needed based on what
     // changed.
-    disp.on('change', function() {
+    disp.on('change', function(fromSync) {
       var onChangeDone = util.logFn('onChange', alertOnError);
       that.updateLists_(function(err) {
         alertOnError(err);
-        var listId = getListId();
-        that.updateTodos_(listId, onChangeDone);
+        if (fromSync) {
+          var listIds = _.pluck(that.state.lists.items, '_id');
+          async.each(listIds, function(listId, cb) {
+            that.updateTodos_(listId, cb);
+          }, onChangeDone);
+        } else {
+          var listId = getListId();
+          that.updateTodos_(listId, onChangeDone);
+        }
       });
     });
 
diff --git a/browser/syncbase_dispatcher.js b/browser/syncbase_dispatcher.js
index 2bea08c..e83d01b 100644
--- a/browser/syncbase_dispatcher.js
+++ b/browser/syncbase_dispatcher.js
@@ -23,7 +23,7 @@
 var syncbase = require('syncbase');
 var nosql = syncbase.nosql;
 var vanadium = require('vanadium');
-var vtrace = vanadium.vtrace;
+var verror = vanadium.verror, vtrace = vanadium.vtrace;
 
 var Dispatcher = require('./dispatcher');
 var util = require('./util');
@@ -332,7 +332,7 @@
   syncPriority: 8
 });
 
-define('createSyncGroup', function(ctx, sgName, blessings, mt, cb) {
+define('createSyncGroup', function(ctx, sgName, blessings, mtName, cb) {
   var sg = this.db_.syncGroup(sgName);
   var spec = new nosql.SyncGroupSpec({
     // TODO(sadovsky): Maybe make perms more restrictive.
@@ -345,7 +345,7 @@
     ]),
     // TODO(sadovsky): Update this once we switch to {table, prefix} tuples.
     prefixes: ['tb:' + this.sgNameToListId(sgName)],
-    mountTables: [vanadium.naming.join(mt, 'rendezvous')]
+    mountTables: [vanadium.naming.join(mtName, 'rendezvous')]
   });
   sg.create(ctx, spec, MEMBER_INFO, this.maybeEmit_(cb));
 });
@@ -452,7 +452,7 @@
       console.log('checkForChanges_ failed: ' + err);
     } else if (changed) {
       console.log('checkForChanges_ detected a change');
-      that.emit('change');
+      that.emit('change', true);
     }
     window.setTimeout(that.watchLoop_.bind(that), 500);
   });
@@ -510,7 +510,17 @@
   nosql.runInBatch(ctx, this.db_, opts, function(db, cb) {
     var tb = db.table('tb');
     tb.get(wn(ctx, 'get:' + key), key, function(err, value) {
-      if (err) return cb(err);
+      if (err) {
+        if (err instanceof verror.NoExistError) {
+          // Concurrent delete, likely from a remote peer. Pretend this update
+          // never happened.
+          // TODO(sadovsky): Maybe make it so client transactions take priority
+          // over sync transactions, so that app developers don't have to worry
+          // about this concurrency scenario.
+          return cb();
+        }
+        return cb(err);
+      }
       var newValue = marshal(updateFn(unmarshal(value)));
       tb.put(wn(ctx, 'put:' + key), key, newValue, cb);
     });
diff --git a/demo.md b/demo.md
index 61ce4be..a37030e 100644
--- a/demo.md
+++ b/demo.md
@@ -1,10 +1,7 @@
 # Demo setup
 
 This page describes how to set things up for a demo.
-For detailed explanations of the setup steps, see [README.md](README.md).
-
-FIXME: Currently, once anything is deleted, outgoing sync permanently stops
-working.
+For detailed explanations of the app setup steps, see [README.md](README.md).
 
 ## Single-machine setup
 
@@ -23,20 +20,8 @@
 
 Open these urls:
 
-    http://localhost:5000/?d=syncbase // Alice
-    http://localhost:5100/?d=syncbase // Bob
-
-### Syncing a list
-
-1. In Alice's window, create list "Groceries".
-2. Add todo items and tags (as desired).
-3. Click the status button, then type in Bob's email address.
-4. Copy the `/share/...` part of the url to the clipboard.
-5. Switch to Bob's window.
-6. Replace everything after `localhost:5100` with the copied path, hit enter.
-7. After a second, Bob should see the synced "Groceries" list.
-8. Add, edit, and remove todos and tags to your heart's content and watch sync
-   do its magic.
+    http://<hostname>:5000/?d=syncbase // Alice
+    http://<hostname>:5100/?d=syncbase // Bob
 
 ## Two-machine setup
 
@@ -55,14 +40,25 @@
 
 Open this url:
 
-    http://localhost:5000/?d=syncbase
+    http://<hostname>:5000/?d=syncbase
 
-### Syncing a list
+## Syncing a list
 
 1. In Alice's window, create list "Groceries".
 2. Add todo items and tags (as desired).
 3. Click the status button, then type in Bob's email address.
-4. Send Bob the entire url, and have him open that url.
+4. Copy the `/share/...` part of the url. The hex suffix encodes the syncgroup
+   name, which includes Alice's mount table name.
+5. In Bob's window, replace everything after `<hostname>:5000` with the copied
+   `/share/...` path.
 5. After a second, Bob should see the synced "Groceries" list.
 6. Add, edit, and remove todos and tags to your heart's content and watch sync
    do its magic.
+
+Note, it's important to use `<hostname>` urls rather than `localhost` urls
+because the web app parses the url from which it was loaded and adds 1 to the
+port number to determine the local mount table name, which it uses as a prefix
+for all syncgroup names that it creates. If the host is `localhost:5000`, the
+app will use `/localhost:5001` as the mount table name, and remote peers will
+not be able to contact the syncgroup. If we switch to a "predefined, global
+mount table" model, this will no longer be an issue.
diff --git a/tools/start_services.sh b/tools/start_services.sh
index 6d6ac70..b302f62 100755
--- a/tools/start_services.sh
+++ b/tools/start_services.sh
@@ -41,6 +41,8 @@
     --v23.credentials=${TMP}/creds &
 
   ./bin/syncbased \
+    --v=5 \
+    --alsologtostderr=false \
     --root-dir=${TMP}/syncbase_${PORT} \
     --name=syncbase \
     --v23.namespace.root=/${MOUNTTABLED_ADDR} \