diff --git a/.eslintrc b/.eslintrc
index 35eecedd9c2b9f0c9f068056eea490bc619fff06..0f0dfcecb95b4c7dd8124960ddba3cae80c58fc8 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -34,6 +34,8 @@
     "createUsers": true,
     "getRandomNumber": true,
     "applyRandomTransactions": true,
-    "CustomType": true
+    "CustomType": true,
+    "window": true,
+    "document": true
   }
 }
diff --git a/Examples/TextBind/index.html b/Examples/TextBind/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..24432ad1edc12bb5959fd8267a421f4d7115aac9
--- /dev/null
+++ b/Examples/TextBind/index.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset=utf-8 />
+  <title>Y Example</title>
+  <script src="../../node_modules/simplewebrtc/simplewebrtc.bundle.js"></script>
+  <script src="../../y.js"></script>
+  <script src="./index.js"></script>
+</head>
+<body>
+<h1 contentEditable> yjs Tutorial</h1>
+<p> Collaborative Json editing with <a href="https://github.com/rwth-acis/yjs/">yjs</a>
+and XMPP Connector. </p>
+
+<textarea style="width:80%;" rows=40 id="textfield"></textarea>
+
+<p> <a href="https://github.com/y-js/yjs/">yjs</a> is a Framework for Real-Time collaboration on arbitrary data types.
+</p>
+</body>
+</html>
diff --git a/Examples/TextBind/index.js b/Examples/TextBind/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..f755f00843c7597a2f8d807f27941f901424fd43
--- /dev/null
+++ b/Examples/TextBind/index.js
@@ -0,0 +1,27 @@
+
+Y({
+  db: {
+    name: "Memory"
+  },
+  connector: {
+    name: "WebRTC",
+    room: "mineeeeeee",
+    debug: true
+  }
+}).then(function(yconfig){
+  window.y = yconfig.root;
+  window.yconfig = yconfig;
+  var textarea = document.getElementById("textfield");
+  yconfig.root.observe(function(events){
+    for (var e in events) {
+      var event = events[e];
+      if (event.name === "text" && (event.type === "add" || event.type === "update")) {
+        event.object.get(event.name).then(function(text){ //eslint-disable-line
+          text.bind(textarea);
+          window.ytext = text;
+        });
+      }
+    }
+  });
+  yconfig.root.set("text", Y.TextBind);
+});
diff --git a/gulpfile.js b/gulpfile.js
index 5bbafb8430a70a2eeea40352642b9bd4edc4e3e1..bbf2445d1befd1ff1f8cbe2a5200b1e4a4127c33 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -70,7 +70,8 @@ var options = minimist(process.argv.slice(2), {
 });
 
 var files = {
-  y: polyfills.concat(["src/y.js", "src/Connector.js", "src/OperationStore.js", "src/Struct.js", "src/**/*.js", "!src/**/*.spec.js"]),
+  y: polyfills.concat(["src/y.js", "src/Connector.js", "src/OperationStore.js", "src/Struct.js", "src/Utils.js",
+    "src/OperationStores/RedBlackTree.js", "src/**/*.js", "!src/**/*.spec.js"]),
   lint: ["src/**/*.js", "gulpfile.js"],
   test: polyfills.concat([options.testfiles]),
   build_test: ["build_test/y.js"]
diff --git a/src/Connector.js b/src/Connector.js
index 77cad2d8f4fa21f0d5826df8ba49ee137b6cedc0..2a7489eecfd3200bc93beb4adc2b8cd383efc8c7 100644
--- a/src/Connector.js
+++ b/src/Connector.js
@@ -25,6 +25,7 @@ class AbstractConnector { //eslint-disable-line no-unused-vars
     this.syncingClients = [];
     this.forwardToSyncingClients = (opts.forwardToSyncingClients === false) ? false : true;
     this.debug = opts.debug ? true : false;
+    this.broadcastedHB = false;
   }
   setUserId (userId) {
     this.userId = userId;
@@ -117,7 +118,8 @@ class AbstractConnector { //eslint-disable-line no-unused-vars
       return;
     }
     if (this.debug) {
-      console.log(`${sender} -> ${this.userId}: ${JSON.stringify(m)}`); //eslint-disable-line
+      console.log(`${sender} -> me: ${m.type}`);//eslint-disable-line
+      console.dir(m); //eslint-disable-line
     }
     if (m.type === "sync step 1") {
       // TODO: make transaction, stream the ops
@@ -148,6 +150,7 @@ class AbstractConnector { //eslint-disable-line no-unused-vars
       this.y.db.requestTransaction(function*(){
         var ops = yield* this.getOperations(m.stateVector);
         if (ops.length > 0) {
+          conn.broadcastedHB = true;
           conn.broadcast({
             type: "update",
             ops: ops
diff --git a/src/Connectors/WebRTC.js b/src/Connectors/WebRTC.js
index 575ee8925998701f2e157f7cd7abee3139cf93db..4a3550d9d65125a2115c8660654506b1e0d8b225 100644
--- a/src/Connectors/WebRTC.js
+++ b/src/Connectors/WebRTC.js
@@ -12,12 +12,12 @@ class WebRTC extends AbstractConnector {
 
     var room = options.room;
 
-    // connect per default to our server
-    if(options.url == null){
-      options.url = "https://yatta.ninja:8888";
-    }
+    var webrtcOptions = {
+      url: options.url || "https://yatta.ninja:8888",
+      room: options.room
+    };
 
-    var swr = new SimpleWebRTC(options); //eslint-disable-line no-undef
+    var swr = new SimpleWebRTC(webrtcOptions); //eslint-disable-line no-undef
     this.swr = swr;
     var self = this;
 
diff --git a/src/OperationStore.js b/src/OperationStore.js
index 60daa8933a99786bd2ec073d5b4d9cdd41102a2a..151fe9de67eaf11c94accd689206c8feba1d8242 100644
--- a/src/OperationStore.js
+++ b/src/OperationStore.js
@@ -21,32 +21,19 @@ class AbstractTransaction { //eslint-disable-line no-unused-vars
     this.store.initializedTypes[sid] = t;
     return t;
   }
-  // returns false if operation is not expected.
-  *addOperation (op) {
-    var state = yield* this.getState(op.id[0]);
-    if (op.id[1] === state.clock){
-      state.clock++;
-      yield* this.setState(state);
-      this.os.add(op);
-      yield* this.store.operationAdded(this, op);
-      return true;
-    } else if (op.id[1] < state.clock) {
-      return false;
-    } else {
-      throw new Error("Operations must arrive in order!");
-    }
-  }
   *applyCreatedOperations (ops) {
     var send = [];
     for (var i = 0; i < ops.length; i++) {
       var op = ops[i];
-      yield* Struct[op.struct].execute.call(this, op);
+      yield* this.store.tryExecute.call(this, op);
       send.push(copyObject(Struct[op.struct].encode(op)));
     }
-    this.store.y.connector.broadcast({
-      type: "update",
-      ops: send
-    });
+    if (this.store.y.connector.broadcastedHB){
+      this.store.y.connector.broadcast({
+        type: "update",
+        ops: send
+      });
+    }
   }
 }
 
@@ -79,10 +66,23 @@ class AbstractOperationStore { //eslint-disable-line no-unused-vars
     // TODO: Use ES7 Weak Maps. This way types that are no longer user,
     // wont be kept in memory.
     this.initializedTypes = {};
+    this.whenUserIdSetListener = null;
+    this.waitingOperations = new RBTree();
   }
   setUserId (userId) {
     this.userId = userId;
     this.opClock = 0;
+    if (this.whenUserIdSetListener != null) {
+      this.whenUserIdSetListener();
+      this.whenUserIdSetListener = null;
+    }
+  }
+  whenUserIdSet (f) {
+    if (this.userId != null) {
+      f();
+    } else {
+      this.whenUserIdSetListener = f;
+    }
   }
   getNextOpId () {
     if (this.userId == null) {
@@ -140,7 +140,7 @@ class AbstractOperationStore { //eslint-disable-line no-unused-vars
 
       for (let key in exeNow) {
         let o = exeNow[key].op;
-        yield* Struct[o.struct].execute.call(this, o);
+        yield* store.tryExecute.call(this, o);
       }
 
       for (var sid in ls){
@@ -153,13 +153,37 @@ class AbstractOperationStore { //eslint-disable-line no-unused-vars
             let listener = l[key];
             let o = listener.op;
             if (--listener.missing === 0){
-              yield* Struct[o.struct].execute.call(this, o);
+              yield* store.tryExecute.call(this, o);
             }
           }
         }
       }
     });
   }
+  *tryExecute (op) {
+    if (op.struct === "Delete") {
+      yield* Struct.Delete.execute.call(this, op);
+    } else {
+      while (op != null) {
+        var state = yield* this.getState(op.id[0]);
+        if (op.id[1] === state.clock){
+          state.clock++;
+          yield* this.setState.call(this, state);
+          yield* Struct[op.struct].execute.call(this, op);
+          yield* this.addOperation(op);
+          yield* this.store.operationAdded(this, op);
+          // find next operation to execute
+          op = this.store.waitingOperations.find([op.id[0], state.clock]);
+        } else {
+          if (op.id[1] > state.clock) {
+            // has to be executed at some point later
+            this.store.waitingOperations.add(op);
+          }
+          op = null;
+        }
+      }
+    }
+  }
   // called by a transaction when an operation is added
   *operationAdded (transaction, op) {
     var sid = JSON.stringify(op.id);
diff --git a/src/OperationStores/IndexedDB.spec.js b/src/OperationStores/IndexedDB.spec.js
index 85ba0d4640c25c72e15177a703e6efbf1c553b12..7d542d5a4fd09a9a0611676abc8c2ae477fdf841 100644
--- a/src/OperationStores/IndexedDB.spec.js
+++ b/src/OperationStores/IndexedDB.spec.js
@@ -4,7 +4,10 @@
 if(typeof window !== "undefined"){
   jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000;
   describe("IndexedDB", function() {
-    var ob = new Y.IndexedDB(null, {namespace: "Test"});
+    var ob;
+    beforeAll(function(){
+      ob = new Y.IndexedDB(null, {namespace: "Test"});
+    });
 
     it("can add and get operation", function(done) {
       ob.requestTransaction(function*(){
diff --git a/src/OperationStores/Memory.js b/src/OperationStores/Memory.js
index ab48773f1b91c2f8971884dd4ae05f4412a72725..61bfb52101f765779d2018451f7e2c0c29d7cd4d 100644
--- a/src/OperationStores/Memory.js
+++ b/src/OperationStores/Memory.js
@@ -33,6 +33,9 @@ Y.Memory = (function(){ //eslint-disable-line no-unused-vars
       n.val = op;
       return op;
     }
+    *addOperation (op) {
+      this.os.add(op);
+    }
     *getOperation (id) {
       if (id == null) {
         throw new Error("You must define id!");
@@ -97,21 +100,18 @@ Y.Memory = (function(){ //eslint-disable-line no-unused-vars
     }
     *makeOperationReady (ss, op) {
       // instead of ss, you could use currSS (a ss that increments when you add an operation)
-      if (op.right == null) {
-        return op;
-      }
       var clock;
       var o = op;
       while (o.right != null){
         // while unknown, go to the right
-        o = yield* this.getOperation(o.right);
-        clock = ss[o.id[0]];
-        if (clock != null && o.id[1] < clock) {
+        clock = ss[o.right[0]];
+        if (clock != null && o.right[1] < clock) {
           break;
         }
+        o = yield* this.getOperation(o.right);
       }
       op = copyObject(op);
-      op.right = (o == null) ? null : o.id;
+      op.right = o.right;
       return op;
     }
   }
diff --git a/src/OperationStores/RedBlackTree.js b/src/OperationStores/RedBlackTree.js
index 593b6b280eb7192872f3c87cb4f5592669e2af9b..c3f01292bf39ca6745e016631709aec37f7d1eba 100644
--- a/src/OperationStores/RedBlackTree.js
+++ b/src/OperationStores/RedBlackTree.js
@@ -1,4 +1,8 @@
 
+function smaller (a, b) {
+  return a[0] < b[0] || (a[0] === b[0] && a[1] < b[1]);
+}
+
 class N {
   // A created node is always red!
   constructor (val) {
@@ -106,6 +110,7 @@ class N {
 class RBTree { //eslint-disable-line no-unused-vars
   constructor () {
     this.root = null;
+    this.length = 0;
   }
   findNodeWithLowerBound (from) {
     var o = this.root;
@@ -113,11 +118,11 @@ class RBTree { //eslint-disable-line no-unused-vars
       return false;
     } else {
       while (true) {
-        if ((from === null || from < o.val.id) && o.left !== null) {
+        if ((from === null || smaller(from, o.val.id)) && o.left !== null) {
           // o is included in the bound
           // try to find an element that is closer to the bound
           o = o.left;
-        } else if (o.val.id < from) {
+        } else if (smaller(o.val.id, from)) {
           // o is not within the bound, maybe one of the right elements is..
           if (o.right !== null) {
             o = o.right;
@@ -134,7 +139,7 @@ class RBTree { //eslint-disable-line no-unused-vars
   }
   iterate (from, to, f) {
     var o = this.findNodeWithLowerBound(from);
-    while ( o !== null && (to === null || o.val.id <= to) ) {
+    while ( o !== null && (to === null || smaller(o.val.id, to) || compareIds(o.val.id, to)) ) {
       f(o.val);
       o = o.next();
     }
@@ -152,9 +157,9 @@ class RBTree { //eslint-disable-line no-unused-vars
         if (o === null) {
           return false;
         }
-        if (id < o.val.id) {
+        if (smaller(id, o.val.id)) {
           o = o.left;
-        } else if (o.val.id < id) {
+        } else if (smaller(o.val.id, id)) {
           o = o.right;
         } else {
           return o;
@@ -164,6 +169,10 @@ class RBTree { //eslint-disable-line no-unused-vars
   }
   delete (id) {
     var d = this.findNode(id);
+    if (d == null) {
+      throw new Error("Element does not exist!");
+    }
+    this.length--;
     if (d.left !== null && d.right !== null) {
       // switch d with the greates element in the left subtree.
       // o should have at most one child.
@@ -302,14 +311,14 @@ class RBTree { //eslint-disable-line no-unused-vars
     if (this.root !== null) {
       var p = this.root; // p abbrev. parent
       while (true) {
-        if (node.val.id < p.val.id) {
+        if (smaller(node.val.id, p.val.id)) {
           if (p.left === null) {
             p.left = node;
             break;
           } else {
             p = p.left;
           }
-        } else if (p.val.id < node.val.id) {
+        } else if (smaller(p.val.id, node.val.id)) {
           if (p.right === null) {
             p.right = node;
             break;
@@ -324,6 +333,7 @@ class RBTree { //eslint-disable-line no-unused-vars
     } else {
       this.root = node;
     }
+    this.length++;
     this.root.blacken();
   }
   _fixInsert (n) {
diff --git a/src/Struct.js b/src/Struct.js
index fd0e0a37a5152e26416384ef662cab1e3fe3e459..04a5ca6b33e1758a9e35123df70862ded2ff5682 100644
--- a/src/Struct.js
+++ b/src/Struct.js
@@ -184,22 +184,14 @@ var Struct = {
       var right = null;
       parent = parent || (yield* this.getOperation(op.parent));
 
-      // NOTE: You you have to call addOperation before you set any other operation!
-
       // reconnect left and set right of op
       if (op.left != null) {
         left = yield* this.getOperation(op.left);
         op.right = left.right;
-        if ((yield* this.addOperation(op)) === false) { // add here
-          return;
-        }
         left.right = op.id;
         yield* this.setOperation(left);
       } else {
         op.right = op.parentSub ? (parent.map[op.parentSub] || null) : parent.start;
-        if ((yield* this.addOperation(op)) === false) { // or here
-          return;
-        }
       }
       // reconnect right
       if (op.right != null) {
@@ -260,9 +252,6 @@ var Struct = {
     execute: function* (op) {
       op.start = null;
       op.end = null;
-      if ((yield* this.addOperation(op)) === false) {
-        return;
-      }
     },
     ref: function* (op : Op, pos : number) : Insert {
       if (op.start == null) {
@@ -324,10 +313,7 @@ var Struct = {
       */
       return [];
     },
-    execute: function* (op) {
-      if ((yield* this.addOperation(op)) === false) {
-        return;
-      }
+    execute: function* () {
     },
     get: function* (op, name) {
       var oid = op.map[name];
@@ -347,3 +333,4 @@ var Struct = {
     }
   }
 };
+Y.Struct = Struct;
diff --git a/src/Types/Map.js b/src/Types/Map.js
index b1337c5e869332759909b57aa8e28b50a7e9e4d8..bfb28a85263ba51aa30f944fbece5a95b46a8fab 100644
--- a/src/Types/Map.js
+++ b/src/Types/Map.js
@@ -149,6 +149,51 @@
     observe (f) {
       this.eventHandler.addUserEventListener(f);
     }
+    unobserve (f) {
+      this.eventHandler.removeUserEventListener(f);
+    }
+    observePath (path, f) {
+      var self = this;
+      if (path.length === 0) {
+        this.observe(f);
+        return Promise.resolve(function(){
+          self.unobserve(f);
+        });
+      } else {
+        var deleteChildObservers;
+        var resetObserverPath = function(){
+          var promise = self.get(path[0]);
+          if (!promise instanceof Promise) {
+            // its either not defined or a premitive value
+            promise = self.set(path[0], Y.Map);
+          }
+          return promise.then(function(map){
+            return map.observePath(path.slice(1), f);
+          }).then(function(_deleteChildObservers){
+            deleteChildObservers = _deleteChildObservers;
+            return Promise.resolve();
+          });
+        };
+        var observer = function(events){
+          for (var e in events) {
+            var event = events[e];
+            if (event.name === path[0]) {
+              deleteChildObservers();
+              if (event.type === "add" || event.type === "update") {
+                resetObserverPath();
+              }
+            }
+          }
+        };
+        self.observe(observer);
+        return resetObserverPath().then(
+          Promise.resolve(function(){
+            deleteChildObservers();
+            self.unobserve(observer);
+          })
+        );
+      }
+    }
     *_changed (transaction, op) {
       if (op.struct === "Delete") {
         op.key = (yield* transaction.getOperation(op.target)).parentSub;
diff --git a/src/Types/TextBind.js b/src/Types/TextBind.js
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..884d904e3ad3baa28fcee711e9a21d8799408c57 100644
--- a/src/Types/TextBind.js
+++ b/src/Types/TextBind.js
@@ -0,0 +1,287 @@
+
+(function(){
+  class YTextBind extends Y.Array.class {
+    constructor (os, _model, idArray, valArray) {
+      super(os, _model, idArray, valArray);
+      this.textfields = [];
+    }
+    toString () {
+      return this.valArray.join("");
+    }
+    insert (pos, content) {
+      super(pos, content.split(""));
+    }
+    bind (textfield, domRoot) {
+        domRoot = domRoot || window; //eslint-disable-line
+        if (domRoot.getSelection == null) {
+          domRoot = window;//eslint-disable-line
+        }
+
+        // don't duplicate!
+        for (var t in this.textfields) {
+          if (this.textfields[t] === textfield) {
+            return;
+          }
+        }
+        var creatorToken = false;
+
+        var word = this;
+        textfield.value = this.toString();
+        this.textfields.push(textfield);
+        var createRange, writeRange, writeContent;
+        if(textfield.selectionStart != null && textfield.setSelectionRange != null) {
+          createRange = function (fix) {
+            var left = textfield.selectionStart;
+            var right = textfield.selectionEnd;
+            if (fix != null) {
+              left = fix(left);
+              right = fix(right);
+            }
+            return {
+              left: left,
+              right: right
+            };
+          };
+          writeRange = function (range) {
+            writeContent(word.toString());
+            textfield.setSelectionRange(range.left, range.right);
+          };
+          writeContent = function (content){
+            textfield.value = content;
+          };
+        } else {
+          createRange = function (fix) {
+            var range = {};
+            var s = domRoot.getSelection();
+            var clength = textfield.textContent.length;
+            range.left = Math.min(s.anchorOffset, clength);
+            range.right = Math.min(s.focusOffset, clength);
+            if(fix != null){
+              range.left = fix(range.left);
+              range.right = fix(range.right);
+            }
+            var editedElement = s.focusNode;
+            if(editedElement === textfield || editedElement === textfield.childNodes[0]){
+              range.isReal = true;
+            } else {
+              range.isReal = false;
+            }
+            return range;
+          };
+
+          writeRange = function (range) {
+            writeContent(word.val());
+            var textnode = textfield.childNodes[0];
+            if(range.isReal && textnode != null) {
+              if(range.left < 0){
+                range.left = 0;
+              }
+              range.right = Math.max(range.left, range.right);
+              if (range.right > textnode.length) {
+                range.right = textnode.length;
+              }
+              range.left = Math.min(range.left, range.right);
+              var r = document.createRange(); //eslint-disable-line
+              r.setStart(textnode, range.left);
+              r.setEnd(textnode, range.right);
+              var s = window.getSelection(); //eslint-disable-line
+              s.removeAllRanges();
+              s.addRange(r);
+            }
+          };
+          writeContent = function (content) {
+            var contentArray = content.replace(new RegExp("\n", 'g')," ").split(" ");//eslint-disable-line
+            textfield.innerText = "";
+            for(var i in contentArray){
+              var c = contentArray[i];
+              textfield.innerText += c;
+              if(i !== contentArray.length - 1){
+                textfield.innerHTML += "&nbsp;";
+              }
+            }
+          };
+        }
+        writeContent(this.toString());
+
+        this.observe(function (events) {
+          for(var e in events) {
+            var event = events[e];
+            if (!creatorToken) {
+              var oPos, fix;
+              if( event.type === "insert") {
+                oPos = event.index;
+                fix = function (cursor) {//eslint-disable-line
+                  if (cursor <= oPos) {
+                    return cursor;
+                  } else {
+                    cursor += 1;
+                    return cursor;
+                  }
+                };
+                var r = createRange(fix);
+                writeRange(r);
+              } else if (event.type === "delete") {
+                oPos = event.index;
+                fix = function (cursor){//eslint-disable-line
+                  if (cursor < oPos) {
+                    return cursor;
+                  } else {
+                    cursor -= 1;
+                    return cursor;
+                  }
+                };
+                r = createRange(fix);
+                writeRange(r);
+              }
+            }
+          }
+        });
+        // consume all text-insert changes.
+        textfield.onkeypress = function (event) {
+          if (word.is_deleted) {
+            // if word is deleted, do not do anything ever again
+            textfield.onkeypress = null;
+            return true;
+          }
+          creatorToken = true;
+          var char;
+          if (event.keyCode === 13) {
+            char = "\n";
+          } else if (event.key != null) {
+            if (event.charCode === 32) {
+              char = " ";
+            } else {
+              char = event.key;
+            }
+          } else {
+            char = window.String.fromCharCode(event.keyCode); //eslint-disable-line
+          }
+          if (char.length > 1) {
+            return true;
+          } else if (char.length > 0) {
+            var r = createRange();
+            var pos = Math.min(r.left, r.right);
+            var diff = Math.abs(r.right - r.left);
+            word.delete(pos, diff);
+            word.insert(pos, char);
+            r.left = pos + char.length;
+            r.right = r.left;
+            writeRange(r);
+          }
+          event.preventDefault();
+          creatorToken = false;
+          return false;
+        };
+        textfield.onpaste = function (event) {
+          if (word.is_deleted) {
+            // if word is deleted, do not do anything ever again
+            textfield.onpaste = null;
+            return true;
+          }
+          event.preventDefault();
+        };
+        textfield.oncut = function (event) {
+          if (word.is_deleted) {
+            // if word is deleted, do not do anything ever again
+            textfield.oncut = null;
+            return true;
+          }
+          event.preventDefault();
+        };
+        //
+        // consume deletes. Note that
+        //   chrome: won't consume deletions on keypress event.
+        //   keyCode is deprecated. BUT: I don't see another way.
+        //     since event.key is not implemented in the current version of chrome.
+        //     Every browser supports keyCode. Let's stick with it for now..
+        //
+        textfield.onkeydown = function (event) {
+          creatorToken = true;
+          if (word.is_deleted) {
+            // if word is deleted, do not do anything ever again
+            textfield.onkeydown = null;
+            return true;
+          }
+          var r = createRange();
+          var pos = Math.min(r.left, r.right, word.toString().length);
+          var diff = Math.abs(r.left - r.right);
+          if (event.keyCode != null && event.keyCode === 8) { // Backspace
+            if (diff > 0) {
+              word.delete(pos, diff);
+              r.left = pos;
+              r.right = pos;
+              writeRange(r);
+            } else {
+              if (event.ctrlKey != null && event.ctrlKey) {
+                var val = word.toString();
+                var newPos = pos;
+                var delLength = 0;
+                if (pos > 0) {
+                  newPos--;
+                  delLength++;
+                }
+                while (newPos > 0 && val[newPos] !== " " && val[newPos] !== "\n") {
+                  newPos--;
+                  delLength++;
+                }
+                word.delete(newPos, pos - newPos);
+                r.left = newPos;
+                r.right = newPos;
+                writeRange(r);
+              } else {
+                if (pos > 0) {
+                  word.delete(pos - 1, 1);
+                  r.left = pos - 1;
+                  r.right = pos - 1;
+                  writeRange(r);
+                }
+              }
+            }
+            event.preventDefault();
+            creatorToken = false;
+            return false;
+          } else if (event.keyCode != null && event.keyCode === 46) { // Delete
+            if (diff > 0) {
+              word.delete(pos, diff);
+              r.left = pos;
+              r.right = pos;
+              writeRange(r);
+            } else {
+              word.delete(pos, 1);
+              r.left = pos;
+              r.right = pos;
+              writeRange(r);
+            }
+            event.preventDefault();
+            creatorToken = false;
+            return false;
+          } else {
+            creatorToken = false;
+            return true;
+          }
+      };
+    }
+  }
+  Y.TextBind = new CustomType({
+    class: YTextBind,
+    createType: function* YTextBindCreator () {
+      var model = {
+        start: null,
+        end: null,
+        struct: "List",
+        type: "TextBind",
+        id: this.store.getNextOpId()
+      };
+      yield* this.applyCreatedOperations([model]);
+      return yield* this.createType(model);
+    },
+    initType: function* YTextBindInitializer(os, model){
+      var valArray = [];
+      var idArray = yield* Y.Struct.List.map.call(this, model, function(c){
+        valArray.push(c.content);
+        return JSON.stringify(c.id);
+      });
+      return new YTextBind(os, model.id, idArray, valArray);
+    }
+  });
+})();
diff --git a/src/y.js b/src/y.js
index 00f5ac1ec8e8c0ecc9c6b5b90891d655bf62b190..fd311f5867852ef39b83521c338d18ff7725851e 100644
--- a/src/y.js
+++ b/src/y.js
@@ -2,8 +2,10 @@
 
 function Y (opts) {
   var def = Promise.defer();
-  new YConfig(opts, function(config){ //eslint-disable-line
-    def.resolve(config);
+  new YConfig(opts, function(yconfig){ //eslint-disable-line
+    yconfig.db.whenUserIdSet(function(){
+      def.resolve(yconfig);
+    });
   });
   return def.promise;
 }
@@ -37,7 +39,3 @@ class YConfig { //eslint-disable-line no-unused-vars
     };
   }
 }
-
-Y.AbstractTransaction = AbstractTransaction;
-Y.AbstractOperationStore = AbstractOperationStore;
-Y.Struct = Struct;