diff --git a/ui (gen4)/images.js b/ui (gen4)/images.js
index 1f1ffbe5..3913db66 100755
--- a/ui (gen4)/images.js	
+++ b/ui (gen4)/images.js	
@@ -345,18 +345,26 @@ module.ImagesPrototype = {
 		//gid = gid == null ? getImageGID(): gid
 		//size = size == null ? getVisibleImageSize('max') : size
 		img_data = img_data == null ? this[gid] : img_data
+
+		// XXX if no usable images are available use STUB data...
+		if((img_data.preview == null 
+					|| Object.keys(img_data.preview).length == 0)
+				&& img_data.path == null){
+			img_data = STUB_IMAGE_DATA
+		}
+
 		var s
 		var url = img_data.path
 		var preview_size = 'Original'
 		var p = Infinity
 		var previews = img_data.preview || {}
 
-		for(var k in img_data.preview){
+		for(var k in previews){
 			s = parseInt(k)
 			if(s < p && s > size){
 				preview_size = k
 				p = s
-				url = img_data.preview[k]
+				url = previews[k]
 			}
 		}
 		return {
@@ -454,6 +462,9 @@ module.ImagesPrototype = {
 		var that = this
 		gids.forEach(function(key){
 			var img = that[key]
+			if(img == null){
+				img = that[key] = {}
+			}
 			var o = direction == 'cw' || direction == 'ccw' 
 				? module.calcRelativeRotation(img.orientation, direction) 
 				: direction*1
@@ -480,9 +491,13 @@ module.ImagesPrototype = {
 	// XXX add reference support...
 	flipImage: function(gids, direction, reference){
 		gids = gids.constructor !== Array ? [gids] : gids
+		reference = reference || 'image'
 		var that = this
 		gids.forEach(function(key){
 			var img = that[key]
+			if(img == null){
+				img = that[key] = {}
+			}
 			var state = img.flipped
 			state = state == null ? [] : state
 			// toggle the specific state...
diff --git a/ui (gen4)/ribbons.js b/ui (gen4)/ribbons.js
index 111d3f3f..f7367c87 100755
--- a/ui (gen4)/ribbons.js	
+++ b/ui (gen4)/ribbons.js	
@@ -23,6 +23,10 @@ var IMAGE_UPDATERS =
 module.IMAGE_UPDATERS = []
 
 
+var IMAGE = '.image:not(.clone)'
+var RIBBON = '.ribbon:not(.clone)'
+
+
 /*********************************************************************/
 //
 // This expects the following HTML structure...
@@ -211,7 +215,7 @@ module.RibbonsPrototype = {
 	getVisibleImageSize: function(dim, scale){
 		scale = scale || this.getScale()
 		dim = dim == null ? 'width' : dim
-		var img = this.viewer.find('.image')
+		var img = this.viewer.find(IMAGE)
 
 		return dim == 'height' ? img.outerHeight(true) * scale
 			: dim == 'width' ? img.outerWidth(true) * scale
@@ -435,7 +439,7 @@ module.RibbonsPrototype = {
 			// ...we need to scale it to the current scale...
 			var shadow = setElementScale(
 				$('
')
-					.addClass('shadow')
+					.addClass('shadow ribbon')
 					.attr({
 						gid: gid,
 						ticket: ticket,
@@ -444,6 +448,7 @@ module.RibbonsPrototype = {
 						// clone the target into the shadow..
 						img
 							.clone()
+							.addClass('clone')
 							.removeClass('current')
 							.attr('gid', null)),
 					s)
@@ -566,7 +571,7 @@ module.RibbonsPrototype = {
 
 		// we got a collection...
 		if(img == null){
-			return $(target).filter('.image')
+			return $(target).filter(IMAGE)
 		}
 
 		// get the offset...
@@ -577,7 +582,7 @@ module.RibbonsPrototype = {
 				: offset
 			var list = offset > 0 ? 'nextAll' : 'prevAll'
 			offset = Math.abs(offset)-1
-			var res = img[list]('.image')
+			var res = img[list](IMAGE)
 			// handle overflow...
 			res = res.eq(Math.min(offset, res.length-1))
 			img = res.length == 0 ? img : res
@@ -655,13 +660,13 @@ module.RibbonsPrototype = {
 		} else if(target == 'base'){
 			var r = this.viewer.find('.base.ribbon').first()
 			if(r.length == 0){
-				return this.viewer.find('.ribbon').first()
+				return this.viewer.find(RIBBON).first()
 			}
 			return r
 
 		// index...
 		} else if(typeof(target) == typeof(123)){
-			return this.viewer.find('.ribbon').eq(target)
+			return this.viewer.find(RIBBON).eq(target)
 
 		// gid...
 		} else if(typeof(target) == typeof('str')){
@@ -672,12 +677,12 @@ module.RibbonsPrototype = {
 				? this.getImage(target).parents('.ribbon').first()
 				: r
 		}
-		return $(target).filter('.ribbon')
+		return $(target).filter(RIBBON)
 	},
 	// Like .getRibbon(..) but returns ribbon index instead of the actual 
 	// ribbon object...
 	getRibbonOrder: function(target){
-		return this.viewer.find('.ribbon').index(this.getRibbon(target))
+		return this.viewer.find(RIBBON).index(this.getRibbon(target))
 	},
 
 
@@ -721,7 +726,7 @@ module.RibbonsPrototype = {
 		ribbon = ribbon.length == 0 ? this.createRibbon(target) : ribbon
 		var ribbon_set = this.getRibbonSet(true) 
 
-		var ribbons = this.viewer.find('.ribbon')
+		var ribbons = this.viewer.find(RIBBON)
 
 		// normalize the position...
 		if(typeof(position) == typeof(123)){
@@ -804,7 +809,7 @@ module.RibbonsPrototype = {
 			}
 			var i = to
 			var images = img[i > 0 ? 'last' : 'first']()
-				[i > 0 ? 'nextAll' : 'prevAll']('.image')
+				[i > 0 ? 'nextAll' : 'prevAll'](IMAGE)
 			to = images.length > 0 
 				? images.eq(Math.min(Math.abs(i), images.length)-1) 
 				: img
@@ -826,7 +831,7 @@ module.RibbonsPrototype = {
 			if(to[0] == img[0]){
 				return img
 			}
-			var images = to[mode]('.image')
+			var images = to[mode](IMAGE)
 		}
 
 		// place the image...
@@ -836,9 +841,9 @@ module.RibbonsPrototype = {
 		// after...
 		} else if(i > 0){
 			// XXX this stumbles on non-images...
-			//to.next('.image')
+			//to.next(IMAGE)
 			// XXX is this fast enough??
-			to.nextAll('.image').first()
+			to.nextAll(IMAGE).first()
 				.before(img)
 		// before...
 		} else {
@@ -904,7 +909,7 @@ module.RibbonsPrototype = {
 	// XXX this depends on .images...
 	// 		...a good candidate to move to images, but not yet sure...
 	updateImage: function(image, gid, size, sync){
-		image = (image == '*' ? this.viewer.find('.image')
+		image = (image == '*' ? this.viewer.find(IMAGE)
 			: image == null 
 				|| typeof(image) == typeof('str') ? this.getImage(image)
 			: $(image))
@@ -1050,7 +1055,7 @@ module.RibbonsPrototype = {
 			r = this.placeRibbon(ribbon, this.viewer.find('.ribbon').length)
 		}
 
-		var loaded = r.find('.image')
+		var loaded = r.find(IMAGE)
 
 		// compensate for new/removed images...
 		if(reference != null){
@@ -1216,7 +1221,7 @@ module.RibbonsPrototype = {
 					: data.ribbons != null ? Object.keys(data.ribbons)
 					: []
 
-				that.viewer.find('.ribbon').each(function(){
+				that.viewer.find(RIBBON).each(function(){
 					var r = $(this)
 					if(ribbons.indexOf(that.getElemGID(r)) < 0){
 						r.remove()
diff --git a/ui (gen4)/viewer.js b/ui (gen4)/viewer.js
index 3d41a0e2..310cc1a8 100755
--- a/ui (gen4)/viewer.js	
+++ b/ui (gen4)/viewer.js	
@@ -410,17 +410,31 @@ actions.Actions({
 
 	// basic image editing...
 	//
-	// XXX these are not data stuff... should this be split into a 
-	// 		separate images block???
 	// XXX should we have .rotate(..) and .flip(..) generic actions???
 	rotateCW: [ 
-		function(){  }],
+		function(target){ 
+			if(this.images != null){
+				this.images.rotateImage(this.data.getImage(target), 'cw')
+			}
+		}],
 	rotateCCW: [ 
-		function(){  }],
+		function(target){ 
+			if(this.images != null){
+				this.images.rotateImage(this.data.getImage(target), 'ccw')
+			}
+		}],
 	flipVertical: [ 
-		function(){  }],
+		function(target){ 
+			if(this.images != null){
+				this.images.flipImage(this.data.getImage(target), 'vertical')
+			}
+		}],
 	flipHorizontal: [
-		function(){  }],
+		function(target){ 
+			if(this.images != null){
+				this.images.flipImage(this.data.getImage(target), 'horizontal')
+			}
+		}],
 
 
 	// crop...
@@ -551,7 +565,7 @@ actions.Actions(Client, {
 					: viewer
 
 				if(this.ribbons == null){
-					this.ribbons = ribbons.Ribbons(viewer, data.images)
+					this.ribbons = ribbons.Ribbons(viewer, this.images)
 				}
 
 				this.reload()
@@ -653,8 +667,14 @@ actions.Actions(Client, {
 				.centerImage(gid)
 				.centerRibbon(gid)
 
+			// if we are going fast we might skip an update... 
+			if(this._align_timeout != null){
+				clearTimeout(this._align_timeout)
+				this._align_timeout = null
+			}
 			var that = this
-			//setTimeout(function(){
+			this._align_timeout = setTimeout(function(){
+				this._align_timeout = null
 				// align other ribbons...
 				var ribbon = data.getRibbon(gid)
 				for(var r in data.ribbons){
@@ -663,7 +683,7 @@ actions.Actions(Client, {
 						continue
 					}
 
-					// XXX skip off-screen ribbons...
+					// XXX skip off-screen ribbons... (???)
 
 					// center...
 					// XXX is there a 'last' special case here???
@@ -679,7 +699,7 @@ actions.Actions(Client, {
 						that.centerImage(t, 'after')
 					}
 				}
-			//}, 10)
+			}, 50)
 		}],
 	// XXX these should also affect up/down navigation...
 	// 		...navigate by proximity (closest to center) rather than by
@@ -1075,8 +1095,8 @@ var PartialRibbonsActions = actions.Actions({
 				|| 1) * w
 
 			// next/prev loaded... 
-			var nl = this.ribbons.getImage(target).nextAll('.image').length
-			var pl = this.ribbons.getImage(target).prevAll('.image').length
+			var nl = this.ribbons.getImage(target).nextAll('.image:not(.clone)').length
+			var pl = this.ribbons.getImage(target).prevAll('.image:not(.clone)').length
 
 			// next/prev available...
 			var na = this.data.getImages(target, size/2, 'after').length - 1 
@@ -1287,7 +1307,7 @@ module.SingleImageView = Feature({
 
 				// ribbon mode -- restore original image size...
 				} else {
-					this.ribbons.viewer.find('.image').css({
+					this.ribbons.viewer.find('.image:not(.clone)').css({
 						width: '',
 						height: ''
 					})
@@ -1519,7 +1539,7 @@ var CurrentImageIndicatorActions = actions.Actions({
 				// get marker globally...
 				marker = this.ribbons.viewer.find('.current-marker')
 
-				// create a marker...
+				// no marker exists -- create a marker...
 				if(marker.length == 0){
 					var marker = $('
')
 						.addClass('current-marker '+ this.tag)
@@ -1610,7 +1630,8 @@ module.CurrentImageIndicator = Feature({
 		['resizeRibbon.post',
 			function(target, s){
 				var m = this.ribbons.viewer.find('.current-marker')
-				if(m.length != 0){
+				// only update if marker exists and we are in current ribbon...
+				if(m.length != 0 && this.currentRibbon == this.data.getRibbon(target)){
 					this.ribbons.preventTransitions(m)
 					this.updateCurrentImageIndicator(target, false)
 					this.ribbons.restoreTransitions(m, true)