anchor.diff:      })
       .then((_) {
         // upload markdown file
  -      final completer = new Completer();
         CartCategory category = categories.find(post.category);
  -      _drive.uploadFile(mdPath, driveId: post.driveId, parents: [category.driveId]).then((GoogleDriveClient.File file) {
  -        post.driveId = file.id; // new post has no drive id, update it
  -        posts.update(post);
  -        completer.complete(file);
  -      });
  -      return completer.future;
  +      if (category == null) {
  +        throw new Exception('[CartModel _uploadPost: category not found: ${post.category}]');
  +      }
  +      return _drive.uploadFile(mdPath, driveId: post.driveId, parents: [category.driveId]);
  +    })
  +    .then((GoogleDriveClient.File file) {
  +      post.driveId = file.id; // new post has no drive id, update it
  +      posts.update(post);
  +      return new Future.value(true);
       })
       .then((_) {
         // upload attachments
  -      final completer = new Completer();
  -      var uploadList = new List<Future>(); // store upload futures
  -      var attachmentList = new List<CartPostAttachment>(); // store upload attachments
         post.attachments.forEach((String attachmentUuid, CartPostAttachment attachment) {
           if (attachment.driveId == null) {
             // need to be uploaded
             attachmentList.add(attachment);
  -          uploadList.add(_drive.uploadFile(LibPath.join(postBaseDir, attachment.title), parents: [post.category]));
  +          attachmentUploadList.add(_drive.uploadFile(LibPath.join(postBaseDir, attachment.title), parents: [post.category]));
           }
         });
  -      Future.wait(uploadList).then((List<GoogleDriveClient.File> responses) {
  -        attachmentList.forEach((CartPostAttachment attachment) {
  -          GoogleDriveClient.File file = responses.removeAt(0);
  -          attachment.driveId = file.id;
  -          post.updateAttachment(attachment);
  -        });
  -        completer.complete();
  +      return Future.wait(attachmentUploadList);
  +    })
  +    .then((List<GoogleDriveClient.File> responses) {
  +      attachmentList.forEach((CartPostAttachment attachment) {
  +        GoogleDriveClient.File file = responses.removeAt(0);
  +        attachment.driveId = file.id;
  +        post.updateAttachment(attachment);
         });
  -      return completer.future;
  +      return new Future.value(true);
       })
       .then((_) {
         // finish process
         completer.complete(true);
  +    })
  +    .catchError((e, trace) {
  +      PinUtility.handleError(e, trace);
  +      completer.completeError(e, trace);
       });
   
       return completer.future;
  
positive.code:   //-* API: TAG
    //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
    Future addTag(String uuid, String name) {
      final completer = new Completer();
  
      // validate uuid
      if (!PinUtility.isUuid(uuid)) {
        PinLogger.instance.shout('[CartModel] addTag: uuid format invalid: ${uuid}}');
        completer.complete(null);
      }
  
      int timestamp = PinTime.getTime();
  
      tags.addNewTag(uuid, name, timestamp: timestamp);
  
      tags.dump().then((_) {
        completer.complete(_);
      });
  
      return completer.future;
    }
  
    Future removeTag(String uuid) {
      final completer = new Completer();
  
      // validate uuid
      if (!PinUtility.isUuid(uuid)) {
        PinLogger.instance.shout('[CartModel] removeTag: uuid format invalid: ${uuid}}');
        completer.complete(null);
      }
  
      tags.remove(uuid, posts);
  
      tags.dump().then((_) {
        completer.complete(_);
      });
  
      return completer.future;
  
negative.code:   //-* API: CATEGORY
    //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
    Future addCategory(String uuid, String name) {
      // validate uuid
      if (!PinUtility.isUuid(uuid)) {
        try {
          throw new Exception('[CartModel] addCategory: uuid format invalid: ${uuid}}');
        } catch (e, trace) {
          PinUtility.handleError(e, trace);
          return new Future.error(e, trace);
        }
      }
  
      final completer = new Completer();
      var timestamp = PinTime.getTime();
  
      categories.addNewCategory(uuid, name, timestamp: timestamp);
  