http/js/api/topobject.js
function changeOnPath(local, op, edit, syncId, editId){
	_.assertInt(op)
	//console.log('path: ' + JSON.stringify(path))
	//_.assertArray(path)
	
	_.assertInt(editId)
	
	/*
	We need:
	- an index of every existing inner object by id
	*/
	
	//console.log('GAR: ' + editNames[op] + ' ' + JSON.stringify(edit))
	
	//console.log('inputObject: ' + local.inputObject)
	//console.log(JSON.stringify(local.edits))
	/*local.edits.forEach(function(e, index){
		console.log(index + ' ' + editNames[e.op] + ' ' + JSON.stringify(e))
	})*/
	//console.log('self: ' + local.id())
	_.assertInt(local.inputProperty)

	//console.log('own: ' + local.objectId + ' ' + local.inputObject + ' ')// + local._forkedObject._internalId())
	
	var ch;
	if(!local.inputObject || local.inputObject === local.objectId || local.inputObject === -1){//TODO handle case where inputObject is the forked object
		ch = local
	}else if(local.lastSelf === local.inputObject){
		ch = local
	}else{
		/*if(editId > 0 && editId !== local.getEditingId() && local.inputObject < 0){
			_.errout('invalid inputObject: ' + local.inputObject + '\t' + editId + ' !== ' + local.getEditingId())
		}*/
		
		if(local.objectApiCache){
			ch = local.objectApiCache[local.inputObject]
		}
		
		/*if(local.inputObject < 0){
			var v = local.parent.temporaryCache[local.inputObject]
			if(v){
				ch = v.n
				//console.log('looked up temporary ' + local.inputObject + ' got ' + Object.keys(ch))
			}
		}*/
		/*if(local.inputObject < 0){
			ch = local.temporary
		}*/
		if(!ch){
			console.log('WARNING: not found ' + local.inputObject + ', only got: ' + JSON.stringify(Object.keys(local.objectApiCache||{})) + ' in ' + local.objectId + ' --- ' + editId)
			console.log(JSON.stringify(local.edits, null, 2))
			throw new Error('here')
			return
		}
		_.assertObject(ch)
	}
	
	var property = ch.typeSchema.propertiesByCode[local.inputProperty]
	if(property === undefined){
		var candidates = ' ' + JSON.stringify(_.map(Object.keys(ch.typeSchema.propertiesByCode),function(p){
			return ch.typeSchema.propertiesByCode[p].name + ':'+ch.typeSchema.propertiesByCode[p].code}))
		if(TopObjectHandle.prototype.errorOnWarn){
			_.errout('WARNING: logic error or removed property, ' + ch.typeSchema.name + ' cannot find property with code: ' + local.inputProperty + candidates)
		}else{
			console.log('WARNING: logic error or removed property, ' + ch.typeSchema.name + ' cannot find property with code: ' + local.inputProperty + candidates)
		}
		return
	}	

	//console.log('modifying property: ' + property.name)
	
	if(op === editCodes.setObject || op === editCodes.setViewObject){
		//_.assertInt(descentCode)
		//var ps = this.typeSchema.propertiesByCode[descentCode];

		//_.assertObject(ps)
		if(op === editCodes.setObject){
			if(property.type.type !== 'object'){
				console.log(JSON.stringify(local.edits, null, 2))
				_.errout('(' + editId + ') setObject called on non object type, type is: ' + JSON.stringify(property.type))
			}
			//_.assertEqual(ps.type.type, 'object')
		}

		var subj
		if(local.inputObject === local.objectId || local.inputObject === local.lastSelf){
			subj = local
		}else{
			subj = local.inputObject!==undefined?local.objectApiCache[local.inputObject]:local
		}

		var pv = subj[property.name]//.cachedProperties[ps.name]
		if(pv && pv.objectId === edit.id){
			//already done
			//console.log('setObject redundant (local view object?), skipping: ' + pv.objectId + ' ' + edit.id)
		}else{
			//this.log('set to: ' + edit.id + ' ' + descentCode + ' ' + this.objectId + ' ' + ps.name)
			var setObj = local.getObjectApi(edit.id)
			if(setObj === undefined){
				local.log.warn('object not found, may have been del\'ed: ' + edit.id)
				local.log(new Error().stack)
				return
			}
			
			if(setObj.isDestroyed()) return

			//subj.obj[local.inputProperty] = setObj;
			setObj.prepare()
			subj[property.name] = setObj
			//console.log('EMITTING SET OBJECT ' + property.name)
			subj.emit(edit, 'set', property.name, setObj)			
		}
	}else if(op === editCodes.clearObject || op === editCodes.clearProperty){

		var subj = local.inputObject!==undefined?local.objectApiCache[local.inputObject]:local

		var ps = subj.typeSchema.propertiesByCode[local.inputProperty];
		
		var chp = ch.property(ps.name)
		
		if(ps.type.type === 'set' || ps.type.type === 'list'){
			chp.obj = []
		}else if(ps.type.type === 'map'){
			chp.obj = {}
		}else{
			chp.obj = undefined
		}
		//subj[ps.name] = undefined
		//subj.obj[local.inputProperty] = undefined
	}else{
		
		//console.log(chp.constructor+'')
		var chp = ch.property(property.name)//[property.name]

		_.assertEqual(ch[property.name], chp)
		
		if(!chp){
			_.errout('property value missing: ' + property.name)
		}
		
		if(!chp.changeListener){
			//console.dir(chp)
			_.errout('cannot determine current edit target(' + property.name+' in ' + ch.typeSchema.name + ' ' + ch.prepared + ' - ' + chp.constructor + ') failed to run edit: ' + JSON.stringify([editNames[op], edit, syncId, editId]))
			return
		}
		//console.log('HERERERERER')
		//console.log('editing ' + editNames[op] + ' ' + (chp.rally = Math.random()))
		
		//console.log(typeof(chp.changeListener))
		
		chp.changeListener(local.inputSubObject, local.inputKey, op, edit, syncId, editId)
	}
}
function changeOnPath(local, op, edit, syncId, editId){
	_.assertInt(op)
	//console.log('path: ' + JSON.stringify(path))
	//_.assertArray(path)
	
	_.assertInt(editId)
	
	/*
	We need:
	- an index of every existing inner object by id
	*/
	
	//console.log('GAR: ' + editNames[op] + ' ' + JSON.stringify(edit))
	
	//console.log('inputObject: ' + local.inputObject)
	//console.log(JSON.stringify(local.edits))
	/*local.edits.forEach(function(e, index){
		console.log(index + ' ' + editNames[e.op] + ' ' + JSON.stringify(e))
	})*/
	//console.log('self: ' + local.id())
	_.assertInt(local.inputProperty)

	//console.log('own: ' + local.objectId + ' ' + local.inputObject + ' ')// + local._forkedObject._internalId())
	
	var ch;
	if(!local.inputObject || local.inputObject === local.objectId || local.inputObject === -1){//TODO handle case where inputObject is the forked object
		ch = local
	}else if(local.lastSelf === local.inputObject){
		ch = local
	}else{
		/*if(editId > 0 && editId !== local.getEditingId() && local.inputObject < 0){
			_.errout('invalid inputObject: ' + local.inputObject + '\t' + editId + ' !== ' + local.getEditingId())
		}*/
		
		if(local.objectApiCache){
			ch = local.objectApiCache[local.inputObject]
		}
		
		/*if(local.inputObject < 0){
			var v = local.parent.temporaryCache[local.inputObject]
			if(v){
				ch = v.n
				//console.log('looked up temporary ' + local.inputObject + ' got ' + Object.keys(ch))
			}
		}*/
		/*if(local.inputObject < 0){
			ch = local.temporary
		}*/
		if(!ch){
			console.log('WARNING: not found ' + local.inputObject + ', only got: ' + JSON.stringify(Object.keys(local.objectApiCache||{})) + ' in ' + local.objectId + ' --- ' + editId)
			console.log(JSON.stringify(local.edits, null, 2))
			throw new Error('here')
			return
		}
		_.assertObject(ch)
	}
	
	var property = ch.typeSchema.propertiesByCode[local.inputProperty]
	if(property === undefined){
		var candidates = ' ' + JSON.stringify(_.map(Object.keys(ch.typeSchema.propertiesByCode),function(p){
			return ch.typeSchema.propertiesByCode[p].name + ':'+ch.typeSchema.propertiesByCode[p].code}))
		if(TopObjectHandle.prototype.errorOnWarn){
			_.errout('WARNING: logic error or removed property, ' + ch.typeSchema.name + ' cannot find property with code: ' + local.inputProperty + candidates)
		}else{
			console.log('WARNING: logic error or removed property, ' + ch.typeSchema.name + ' cannot find property with code: ' + local.inputProperty + candidates)
		}
		return
	}	

	//console.log('modifying property: ' + property.name)
	
	if(op === editCodes.setObject || op === editCodes.setViewObject){
		//_.assertInt(descentCode)
		//var ps = this.typeSchema.propertiesByCode[descentCode];

		//_.assertObject(ps)
		if(op === editCodes.setObject){
			if(property.type.type !== 'object'){
				console.log(JSON.stringify(local.edits, null, 2))
				_.errout('(' + editId + ') setObject called on non object type, type is: ' + JSON.stringify(property.type))
			}
			//_.assertEqual(ps.type.type, 'object')
		}

		var subj
		if(local.inputObject === local.objectId || local.inputObject === local.lastSelf){
			subj = local
		}else{
			subj = local.inputObject!==undefined?local.objectApiCache[local.inputObject]:local
		}

		var pv = subj[property.name]//.cachedProperties[ps.name]
		if(pv && pv.objectId === edit.id){
			//already done
			//console.log('setObject redundant (local view object?), skipping: ' + pv.objectId + ' ' + edit.id)
		}else{
			//this.log('set to: ' + edit.id + ' ' + descentCode + ' ' + this.objectId + ' ' + ps.name)
			var setObj = local.getObjectApi(edit.id)
			if(setObj === undefined){
				local.log.warn('object not found, may have been del\'ed: ' + edit.id)
				local.log(new Error().stack)
				return
			}
			
			if(setObj.isDestroyed()) return

			//subj.obj[local.inputProperty] = setObj;
			setObj.prepare()
			subj[property.name] = setObj
			//console.log('EMITTING SET OBJECT ' + property.name)
			subj.emit(edit, 'set', property.name, setObj)			
		}
	}else if(op === editCodes.clearObject || op === editCodes.clearProperty){

		var subj = local.inputObject!==undefined?local.objectApiCache[local.inputObject]:local

		var ps = subj.typeSchema.propertiesByCode[local.inputProperty];
		
		var chp = ch.property(ps.name)
		
		if(ps.type.type === 'set' || ps.type.type === 'list'){
			chp.obj = []
		}else if(ps.type.type === 'map'){
			chp.obj = {}
		}else{
			chp.obj = undefined
		}
		//subj[ps.name] = undefined
		//subj.obj[local.inputProperty] = undefined
	}else{
		
		//console.log(chp.constructor+'')
		var chp = ch.property(property.name)//[property.name]

		_.assertEqual(ch[property.name], chp)
		
		if(!chp){
			_.errout('property value missing: ' + property.name)
		}
		
		if(!chp.changeListener){
			//console.dir(chp)
			_.errout('cannot determine current edit target(' + property.name+' in ' + ch.typeSchema.name + ' ' + ch.prepared + ' - ' + chp.constructor + ') failed to run edit: ' + JSON.stringify([editNames[op], edit, syncId, editId]))
			return
		}
		//console.log('HERERERERER')
		//console.log('editing ' + editNames[op] + ' ' + (chp.rally = Math.random()))
		
		//console.log(typeof(chp.changeListener))
		
		chp.changeListener(local.inputSubObject, local.inputKey, op, edit, syncId, editId)
	}
}
/*
TopObjectHandle.prototype._acceptForkChange = function(subObj, key, op, edit, syncId, editId){//, path){

console.log('MAINTAINING FORK: ' + op + ' ' + JSON.stringify(edit) + ' on ' + this.objectId + ' ' + syncId + ' ' + this.getEditingId())

var manyToRemove = 0
for(var i=0;i<this.edits.length;++i){
var e = this.edits[i]
if((e.op === editCodes.made || e.op === editCodes.madeFork) && e.edit.sourceId === this._forkedObject._internalId()){
break;
}
++manyToRemove
}
this.edits = this.edits.slice(manyToRemove)
this.edits = [].concat(this._forkedObject._getEdits()).concat(this.edits)

this._rebuild()
}