 			for change in remove_changes
 				@_removeChange change
 			
-			if moved_changes.length > 0
-				@emit "changes:moved", moved_changes
+			for change in moved_changes
+				@_markAsDirty change, "change", "moved"
 		
 		applyDeleteToChanges: (op, metadata) ->
 			op_start = op.p

				@addComment(op, metadata)
			else
				throw new Error("unknown op type")
			
		addComment: (op, metadata) ->
			# TODO: Don't allow overlapping comments?
			@comments.push comment = {
				id: op.t or @newId()
				op: # Copy because we'll modify in place
					c: op.c
					p: op.p
					t: op.t
				metadata
			}
			@emit "comment:added", comment
			return comment
		
		applyInsertToComments: (op) ->
			for comment in @comments
				if op.p <= comment.op.p
					comment.op.p += op.i.length
					@emit "comment:moved", comment
				else if op.p < comment.op.p + comment.op.c.length
					offset = op.p - comment.op.p
					comment.op.c = comment.op.c[0..(offset-1)] + op.i + comment.op.c[offset...]
					@emit "comment:moved", comment

		applyDeleteToComments: (op) ->
			op_start = op.p

			change = @getChange(change_id)
			return if !change?
			@_removeChange(change)
		
		validate: (text) ->
			for change in @changes
				if change.op.i?
					content = text.slice(change.op.p, change.op.p + change.op.i.length)
					if content != change.op.i
						throw new Error("Change (#{JSON.stringify(change)}) doesn't match text (#{JSON.stringify(content)})")
			for comment in @comments
				content = text.slice(comment.op.p, comment.op.p + comment.op.c.length)
				if content != comment.op.c
					throw new Error("Comment (#{JSON.stringify(comment)}) doesn't match text (#{JSON.stringify(content)})")
			return true

		applyOp: (op, metadata = {}) ->
			metadata.ts ?= new Date()
