diff --git a/ui (gen4)/data.js b/ui (gen4)/data.js
index 62ff8c8a..ecd21e64 100755
--- a/ui (gen4)/data.js	
+++ b/ui (gen4)/data.js	
@@ -379,7 +379,7 @@ module.DataPrototype = {
 		}
 
 		// normalize target...
-		if(target in this.ribbons || target.constructor.name == 'Array'){
+		if(target in this.ribbons || target.constructor === Array){
 			list = target
 			target = this.current
 		} else if(['before', 'after', 'next', 'prev'].indexOf(target) >= 0){
@@ -885,6 +885,7 @@ module.DataPrototype = {
 	// NOTE: this depends on setting length of an array, it works in 
 	// 		Chrome but will it work the same in other systems???
 	reverseImages: function(){
+		var ribbons = this.ribbons
 		this.order.reverse()
 		var l = this.order.length
 		for(k in ribbons){
@@ -1004,7 +1005,17 @@ module.DataPrototype = {
 
 				// update ribbons...
 				for(k in ribbons){
-					ribbons[k].splice(t+i, 0, ribbons[k].splice(f, 1)[0])
+					var e = ribbons[k].splice(f, 1)[0]
+					ribbons[k].splice(t+i, 0, e)
+					// remove the null/undefined if it was just inserted...
+					// NOTE: this needs to be done as splice inserts the 3'rd
+					// 		argument explicitly regardless of it's value,
+					// 		this if not done we'll end up with undefined 
+					// 		inserted into a sparse ribbon which will be
+					// 		considered as an element...
+					if(e == null){
+						delete ribbons[k][t+i]
+					}
 				}
 			}
 		}
diff --git a/ui (gen4)/index.html b/ui (gen4)/index.html
index 2883b352..a6ef8d77 100755
--- a/ui (gen4)/index.html	
+++ b/ui (gen4)/index.html	
@@ -5,6 +5,49 @@
 
 
 
+
+
+
 
diff --git a/ui (gen4)/lib/actions.js b/ui (gen4)/lib/actions.js
index aa0591ff..1504c91e 100755
--- a/ui (gen4)/lib/actions.js	
+++ b/ui (gen4)/lib/actions.js	
@@ -101,8 +101,10 @@ define(function(require){ var module = {}
 // helpers...
 
 // XXX
-function args2array(args){
-	return Array.apply(null, args)
+if(typeof(args2array) != 'function'){
+	function args2array(args){
+		return [].slice.call(args)
+	}
 }
 
 
@@ -209,7 +211,7 @@ function Action(name, doc, ldoc, func){
 			.map(function(h){ return h.apply(that, args) })
 
 		// NOTE: this action will get included and called by the code 
-		// 		above and below...
+		// 		above and below, so no need to explicitly call func...
 
 		// call handlers -- post phase...
 		// NOTE: post handlers need to get called last run pre first run post...
@@ -519,12 +521,18 @@ function test(){
 		// NOTE: this looks like an action and feels like an action but 
 		// 		actually this is a callback as an action with this name 
 		// 		already exists...
-		testActionGen1: [function(){
-			console.log('  pre callback!')
-			return function(){
-				console.log('  post callback!')
-			}
-		}],
+		testActionGen1: [
+			function(){
+				console.log('  pre callback!')
+				return function(){
+					console.log('  post callback!')
+				}
+			}],
+
+		testAction2: ['this is an action',
+			function(){
+				console.log('testAction2 args:', arguments)
+			}],
 
 	})
 
diff --git a/ui (gen4)/lib/jli.js b/ui (gen4)/lib/jli.js
index 84e6a6c7..b3104555 100755
--- a/ui (gen4)/lib/jli.js	
+++ b/ui (gen4)/lib/jli.js	
@@ -1325,7 +1325,8 @@ Array.prototype.len = function(){
 
 // convert JS arguments to Array...
 function args2array(args){
-	return Array.apply(null, args)
+	//return Array.apply(null, args)
+	return [].slice.call(args)
 }
 
 
diff --git a/ui (gen4)/ribbons.js b/ui (gen4)/ribbons.js
index 07d48740..40197249 100755
--- a/ui (gen4)/ribbons.js	
+++ b/ui (gen4)/ribbons.js	
@@ -169,6 +169,30 @@ module.RibbonsPrototype = {
 
 	// Helpers...
 
+	// XXX
+	preventTransitions: function(){
+		this.viewer.addClass('no-transitions')
+	},
+	restoreTransitions: function(now){
+		// sync...
+		if(now){
+			this.viewer.removeClass('no-transitions')
+
+		// on next exec frame...
+		} else {
+			var that = this
+			setTimeout(function(){
+				that.viewer.removeClass('no-transitions')}, 0)
+		}
+	},
+
+	noTransitions: function(func){
+		this.preventTransitions()
+		func.apply(this, args2array(arguments).slice(1))
+		this.restoreTransitions()
+	},
+
+
 	// Get visible image tile size...
 	//
 	//	.getVisibleImageSize()
@@ -187,11 +211,10 @@ module.RibbonsPrototype = {
 	//
 	// XXX try and make image size the product of vmin and scale...
 	// XXX is this the right place for this???
-	// XXX uses jli.js getElementScale(..)
 	getVisibleImageSize: function(dim){
 		dim = dim == null ? 'width' : dim
 		var img = this.viewer.find('.image')
-		var scale = getElementScale(this.viewer.find('.ribbon-set'))
+		var scale = this.getScale()
 		if(dim == 'height'){
 			return img.outerHeight(true) * scale
 		} else if(dim == 'width'){
@@ -203,6 +226,24 @@ module.RibbonsPrototype = {
 		}
 	},
 
+	getScreenWidthImages: function(scale){
+		var scale = scale == null ? 1 : scale/this.getScale()
+
+		var W = this.viewer.width()
+		var w = this.getVisibleImageSize('width')*scale
+
+		return W/w
+	},
+
+	// XXX uses jli.js getElementScale(..) / setElementScale(..)
+	getScale: function(){
+		return getElementScale(this.viewer.find('.ribbon-set'))
+	},
+	setScale: function(scale){
+		setElementScale(this.viewer.find('.ribbon-set'), scale)
+		return this
+	},
+
 
 	// Contextual getters...
 	
@@ -1202,10 +1243,13 @@ module.RibbonsPrototype = {
 	// NOTE: image_offset is applicable ONLY when both vertical and 
 	// 		horizontal are set to 'center', either explicitly or 
 	// 		implicitly (i.e. the default)
-	_getOffset: function(target, vertical, horizontal, image_offset){
+	// NOTE: this will get absolute results relative to screen, view 
+	// 		scaling will have no effect...
+	_getOffset: function(target, vertical, horizontal, image_offset, scale){
 		vertical = vertical == null ? 'center' : vertical
 		horizontal = horizontal == null ? 'center' : horizontal
 		image_offset = image_offset == null ? 'center' : image_offset
+		scale = scale == null ? this.getScale() : scale
 
 		if(vertical == 'before' || vertical == 'after'){
 			image_offset = vertical
@@ -1227,8 +1271,8 @@ module.RibbonsPrototype = {
 
 		var W = viewer.width()
 		var H = viewer.height()
-		var w = image.width()
-		var h = image.height()
+		var w = image.width() * scale
+		var h = image.height() * scale
 
 		image_offset = image_offset == 'before' ? w/2
 			: image_offset == 'after' ? -w/2
@@ -1258,9 +1302,11 @@ module.RibbonsPrototype = {
 		}
 	},
 	// center a ribbon vertically...
-	// XXX
-	centerRibbon: function(target, offset){
-		offset = offset == null ? this._getOffset(target) : offset
+	// 
+	centerRibbon: function(target, offset, scale){
+		offset = offset == null 
+			? this._getOffset(target, null, null, null, scale) 
+			: offset
 
 		// vertical offset...
 		this.viewer.find('.ribbon-set')
@@ -1271,22 +1317,34 @@ module.RibbonsPrototype = {
 		return this
 	},
 	// center an image horizontally...
-	// XXX
-	centerImage: function(target, mode, offset){
-		offset = offset == null ? this._getOffset(target, 'center', 'center', mode) : offset
+	// 
+	centerImage: function(target, mode, offset, scale){
+		scale = scale == null ? this.getScale() : scale
+		offset = offset == null 
+			? this._getOffset(target, 'center', 'center', mode, scale) 
+			: offset
 
 		// horizontal offset, current ribbon...
 		this.getRibbon(target)
 			.css({
-				left: offset.left
+				left: offset.left / scale,
 			})
 
 		return this
 	},
 
 	// XXX
-	fitNImages: function(n){
-		// XXX
+	fitImage: function(n){
+		n = n == null ? 1 : n
+
+		var scale = this.getScreenWidthImages(1) / n
+
+		this
+			.setScale(scale)
+			//.centerRibbon(null, null, scale)
+			//.centerImage(null, null, null, scale)
+		
+		return this
 	},
 
 
diff --git a/ui (gen4)/ui.js b/ui (gen4)/ui.js
index 06a2ce66..848a52bc 100755
--- a/ui (gen4)/ui.js	
+++ b/ui (gen4)/ui.js	
@@ -88,6 +88,12 @@ module.GLOBAL_KEYBOARD = {
 				}),
 			*/
 			'ctrl+shift': 'F5',
+
+			// XXX testing...
+			ctrl: function(){ 
+				event.preventDefault()
+				a.reverseImages() 
+			},
 		},
 		P: {
 			'ctrl+shift': 'F12',
@@ -104,20 +110,33 @@ module.GLOBAL_KEYBOARD = {
 		},
 
 		// XXX testing...
-		Home: doc('', function(){ a.firstImage() }),
-		End: doc('', function(){ a.lastImage() }),
-		Left: doc('', function(){ a.prevImage() }),
-		Right: doc('', function(){ a.nextImage() }),
+		Home: function(){ a.firstImage() },
+		End: function(){ a.lastImage() },
+		Left: {
+			default: function(){ a.prevImage() },
+			alt: function(){ a.shiftImageLeft() },
+		},
+		Right: {
+			default: function(){ a.nextImage() },
+			alt: function(){ a.shiftImageRight() },
+		},
 		Up: {
-			default: doc('', function(){ a.prevRibbon() }),
-			shift: doc('', function(){ a.shiftImageUp() }),
-			'ctrl+shift': doc('', function(){ a.shiftImageUpNewRibbon() }),
+			default: function(){ a.prevRibbon() },
+			shift: function(){ a.shiftImageUp() },
+			'ctrl+shift': function(){ a.shiftImageUpNewRibbon() },
 		},
 		Down: {
-			default: doc('', function(){ a.nextRibbon() }),
-			shift: doc('', function(){ a.shiftImageDown() }),
-			'ctrl+shift': doc('', function(){ a.shiftImageDownNewRibbon() }),
-		}
+			default: function(){ a.nextRibbon() },
+			shift: function(){ a.shiftImageDown() },
+			'ctrl+shift': function(){ a.shiftImageDownNewRibbon() },
+		},
+		'#0': function(){ a.fitImage(20) },
+		'#1': function(){ a.fitOrig() },
+		'#2': function(){ a.fitTwo() },
+		'#3': function(){ a.fitThree() },
+		'#4': function(){ a.fitFour() },
+		'#5': function(){ a.fitFive() },
+		
 
 	},
 }	
diff --git a/ui (gen4)/viewer.js b/ui (gen4)/viewer.js
index 5f20e78e..65cbdfc4 100755
--- a/ui (gen4)/viewer.js	
+++ b/ui (gen4)/viewer.js	
@@ -132,9 +132,21 @@ actions.Actions({
 		function(target){
 			var data = this.data
 			var r = data.getRibbon(target)
-			var t = data.getImage('current', r)
-			// XXX is there a 'last' special case???
-			t = t == null ? data.getImage('first', r) : t
+			if(r == null){
+				return
+			}
+			var c = data.getRibbonOrder()
+			var i = data.getRibbonOrder(r)
+
+			// NOTE: we are not changing the direction here based on 
+			// 		this.direction as swap will confuse the user...
+			var direction = c < i ? 'before' : 'after'
+
+			var t = data.getImage(r, direction)
+
+			// if there are no images in the requied direction, try the 
+			// other way...
+			t = t == null ? data.getImage(r, direction == 'before' ? 'after' : 'before') : t
 
 			this.focusImage(t, r)
 		}],
@@ -183,6 +195,9 @@ actions.Actions({
 			+'will shift to the next or previous image in the current '
 			+'ribbon depending on current direction.',
 		function(target){ 
+			// stop transitions...
+			this.ribbons.preventTransitions()
+
 			// by default we need to update the current position...
 			if(target == null){
 				var direction = this.direction == 'right' ? 'next' : 'prev'
@@ -200,12 +215,20 @@ actions.Actions({
 			} else {
 				this.data.shiftImageUp(target)
 			}
+
+			// restore transitions...
+			return function(){
+				this.ribbons.restoreTransitions()
+			}
 		}],
 	shiftImageDown: ['Shift image down',
 		'If implicitly shifting current image (i.e. no arguments), focus '
 			+'will shift to the next or previous image in the current '
 			+'ribbon depending on current direction.',
 		function(target){ 
+			// stop transitions...
+			this.ribbons.preventTransitions()
+
 			// by default we need to update the current position...
 			if(target == null){
 				var direction = this.direction == 'right' ? 'next' : 'prev'
@@ -223,6 +246,11 @@ actions.Actions({
 			} else {
 				this.data.shiftImageDown(target)
 			}
+		
+			// restore transitions...
+			return function(){
+				this.ribbons.restoreTransitions()
+			}
 		}],
 	shiftImageUpNewRibbon: ['Shift image up to a new empty ribbon',
 		function(target){
@@ -269,9 +297,8 @@ actions.Actions({
 	// XXX
 	sortImages: [
 		function(){  }],
-	// XXX
 	reverseImages: [
-		function(){  }],
+		function(){ this.data.reverseImages() }],
 
 
 	// basic image editing...
@@ -288,6 +315,9 @@ actions.Actions({
 
 
 	// crop...
+	//
+	// XXX
+
 })
 
 
@@ -400,6 +430,51 @@ actions.Actions(Client, {
 			// XXX
 		}],
 
+	// zooming...
+	// XXX
+	zoomIn: ['Zoom in',
+		function(){  }],
+	zoomOut: ['Zoom out',
+		function(){  }],
+
+	fitOrig: ['Fit to original scale',
+		function(){ this.ribbons.setScale(1) }],
+
+	// NOTE: if this gets a count argument it will fit count images, 
+	// 		default is one.
+	// XXX animation broken for this...
+	fitImage: ['Fit image',
+		function(count){
+			this.ribbons.fitImage(count)
+			this.ribbons.updateImage('*')
+			//this.focusImage()
+		}],
+
+	// XXX should these be relative to screen rather than actual image counts?
+	fitTwo: ['Fit two images', function(){ this.fitImage(2) }],
+	fitThree: ['Fit three images', function(){ this.fitImage(3) }],
+	fitFour: ['Fit four images', function(){ this.fitImage(4) }],
+	fitFive: ['Fit five images', function(){ this.fitImage(5) }],
+	fitSix: ['Fit six images', function(){ this.fitImage(6) }],
+	fitSeven: ['Fit seven images', function(){ this.fitImage(7) }],
+	fitEight: ['Fit eight images', function(){ this.fitImage(8) }],
+	fitNine: ['Fit nine images', function(){ this.fitImage(9) }],
+
+	// XXX
+	fitMax: ['Fit the maximum number of images',
+		function(){  }],
+
+	// XXX
+	fitSmall: ['Show small image',
+		function(){  }],
+	// XXX
+	fitNormal: ['Show normal image',
+		function(){  }],
+	// XXX
+	fitScreen: ['Fit image to screen',
+		function(){  }],
+
+
 	// XXX
 	shiftImageUp: [
 		function(target){
@@ -415,16 +490,11 @@ actions.Actions(Client, {
 				this.reload()
 			}
 		}],
-	/* XXX these are not needed when reloading in .shiftImageUp(..) / .shiftImageDown(..)...
-	shiftImageUpNewRibbon: [
-		function(target){
-			// XXX only create a new ribbon...
-		}],
-	shiftImageDownNewRibbon: [
-		function(target){
-			// XXX only create a new ribbon...
-		}],
-	*/
+
+	// NOTE: .shiftImageDownNewRibbon(..) and .shiftImageUpNewRibbon(..)
+	// 		are not needed here when doing a reload on vertical 
+	// 		shifting...
+
 	shiftImageLeft: [
 		function(target){
 			this.ribbons.placeImage(target, -1)
@@ -442,6 +512,17 @@ actions.Actions(Client, {
 		function(target){
 			// XXX
 		}],
+
+	reverseImages: [
+		function(){ 
+			this.ribbons.preventTransitions()
+			return function(){ 
+				this.reload() 
+				this.ribbons.restoreTransitions()
+			}
+		}],
+
+
 })