diff --git a/ui (gen4)/lib/features.js b/ui (gen4)/lib/features.js
index 8a80b951..2bd39548 100755
--- a/ui (gen4)/lib/features.js	
+++ b/ui (gen4)/lib/features.js	
@@ -105,7 +105,7 @@ module.FeatureProto = {
 		if(this.config != null 
 				|| (this.actions != null 
 					&& this.actions.config != null)){
-			var config = this.config || this.actions.config
+			var config = this.config = this.config || this.actions.config
 
 			if(actions.config == null){
 				actions.config = {}
diff --git a/ui (gen4)/lib/widget/browse.js b/ui (gen4)/lib/widget/browse.js
index afe87327..297c928a 100755
--- a/ui (gen4)/lib/widget/browse.js	
+++ b/ui (gen4)/lib/widget/browse.js	
@@ -599,6 +599,18 @@ var BrowserPrototype = {
 	// 		path due to an error, we need to be able to render the new
 	// 		path both in the path and list sections...
 	// 		NOTE: current behaviour is not wrong, it just not too flexible...
+	//
+	// XXX one use-case here would be to pass this a custom lister or a full
+	// 		browser, need to make this work correctly for full set of 
+	// 		events...
+	// 			- custom lister -- handle all sub-paths in some way...
+	// 			- full browser -- handle all sub-paths by the nested 
+	// 								browser...
+	// 		one way to handle nested browsers is to implement a browser 
+	// 		stack which if not empty the top browser handles all the 
+	// 		sub-paths
+	// 		...this will also need to indicate a way to split the path 
+	// 		and when to 'pop' the sub browser...
 	update: function(path, list){
 		path = path || this.path
 		var browser = this.dom
@@ -743,7 +755,7 @@ var BrowserPrototype = {
 						that.action()
 					}))
 			}
-			// push action...
+			// push...
 			if(traversable && that.options.pushButton){
 				res.append($('
')
 					.addClass('button')
diff --git a/ui (gen4)/viewer.js b/ui (gen4)/viewer.js
index 247a250a..89ba060b 100755
--- a/ui (gen4)/viewer.js	
+++ b/ui (gen4)/viewer.js	
@@ -794,11 +794,6 @@ module.Base = ImageGridFeatures.Feature({
 
 	tag: 'base',
 
-	config: {
-		// see .direction for details...
-		'steps-to-change-direction': 3,
-	},
-
 	actions: BaseActions,
 })
 
@@ -809,6 +804,28 @@ module.Base = ImageGridFeatures.Feature({
 var ViewerActions = 
 module.ViewerActions = 
 actions.Actions({
+	config: {
+		// The maximum screen width allowed when zooming...
+		'max-screen-images': 30,
+
+		// A step (multiplier) used by .zoomIn()/.zoomOut() actions.
+		// NOTE: this is rounded to the nearest whole screen width in images
+		// 		and current fit-overflow added.
+		'zoom-step': 1.2,
+
+		// added to odd number of images to fit to indicate scroll ability...
+		// ...this effectively sets the closest distance an image can be from
+		// the viewer edge...
+		'fit-overflow': 0.2,
+
+		
+		// limit key repeat to one per N milliseconds.
+		//
+		// Set this to -1 or null to run keys without any limitations.
+		// XXX at this point the keyboard is setup in ui.js, need to 
+		// 		move to a more logical spot...
+		'max-key-repeat-rate': 0,
+	},
 
 	// Images...
 	// XXX this seems like a hack...
@@ -1316,29 +1333,6 @@ module.Viewer = ImageGridFeatures.Feature({
 
 	depends: ['base'],
 
-	config: {
-		// The maximum screen width allowed when zooming...
-		'max-screen-images': 30,
-
-		// A step (multiplier) used by .zoomIn()/.zoomOut() actions.
-		// NOTE: this is rounded to the nearest whole screen width in images
-		// 		and current fit-overflow added.
-		'zoom-step': 1.2,
-
-		// added to odd number of images to fit to indicate scroll ability...
-		// ...this effectively sets the closest distance an image can be from
-		// the viewer edge...
-		'fit-overflow': 0.2,
-
-		
-		// limit key repeat to one per N milliseconds.
-		//
-		// Set this to -1 or null to run keys without any limitations.
-		// XXX at this point the keyboard is setup in ui.js, need to 
-		// 		move to a more logical spot...
-		'max-key-repeat-rate': 0,
-	},
-
 	actions: ViewerActions,
 
 	// check if we are running in a UI context...
@@ -1465,6 +1459,25 @@ module.Journal = ImageGridFeatures.Feature({
 // XXX try a strategy: load more in the direction of movement by an offset...
 // XXX updateRibbon(..) is not signature compatible with data.updateRibbon(..)
 var PartialRibbonsActions = actions.Actions({
+	config: {
+		// number of screen widths to load...
+		'ribbon-size-screens': 7,
+
+		// number of screen widths to edge to trigger reload...
+		'ribbon-resize-threshold': 1.5,
+
+		// timeout before a non-forced ribbon size update happens after
+		// the action...
+		// NOTE: if set to null, the update will be sync...
+		'ribbon-update-timeout': 120,
+
+		// how many non-adjacent images to preload...
+		'preload-radius': 5,
+
+		// sources to preload...
+		'preload-sources': ['bookmark', 'selected'],
+	},
+
 	// NOTE: this will not work from chrome when loading from a local fs...
 	// XXX experimental...
 	startCacheWorker: ['Interface/',
@@ -1794,25 +1807,6 @@ module.PartialRibbons = ImageGridFeatures.Feature({
 
 	actions: PartialRibbonsActions,
 
-	config: {
-		// number of screen widths to load...
-		'ribbon-size-screens': 7,
-
-		// number of screen widths to edge to trigger reload...
-		'ribbon-resize-threshold': 1.5,
-
-		// timeout before a non-forced ribbon size update happens after
-		// the action...
-		// NOTE: if set to null, the update will be sync...
-		'ribbon-update-timeout': 120,
-
-		// how many non-adjacent images to preload...
-		'preload-radius': 5,
-
-		// sources to preload...
-		'preload-sources': ['bookmark', 'selected'],
-	},
-
 	handlers: [
 		['focusImage.pre centerImage.pre', 
 			function(target, list){
@@ -1857,6 +1851,11 @@ module.PartialRibbons = ImageGridFeatures.Feature({
 //---------------------------------------------------------------------
 
 var SingleImageActions = actions.Actions({
+	config: {
+		'single-image-scale': null,
+		'ribbon-scale': null,
+	},
+
 	toggleSingleImage: ['Interface/Toggle single image view', 
 		// XXX this is wrong!!!
 		CSSClassToggler(
@@ -1944,11 +1943,6 @@ module.SingleImageView = ImageGridFeatures.Feature({
 	tag: 'ui-single-image-view',
 	depends: ['ui'],
 
-	config: {
-		'single-image-scale': null,
-		'ribbon-scale': null,
-	},
-
 	actions: SingleImageActions,
 
 	handlers:[
@@ -2193,6 +2187,25 @@ module.BoundsIndicators = ImageGridFeatures.Feature({
 //---------------------------------------------------------------------
 
 var CurrentImageIndicatorActions = actions.Actions({
+	config: {
+		'current-image-border': 3,
+		'current-image-min-border': 2,
+
+		'current-image-border-timeout': 200,
+		'current-image-shift-timeout': 200,
+
+		'current-image-indicator-fadein': 500,
+
+		'current-image-indicator-hide-timeout': 250,
+
+		// this can be:
+		// 	'hide'			- simply hide on next/prev screen action
+		// 					  and show on focus image.
+		// 	'hide-show'		- hide on fast scroll through screens and 
+		// 					  show when slowing down.
+		'current-image-indicator-screen-nav-mode': 'hide',
+	},
+
 	updateCurrentImageIndicator: ['Interface/Update current image indicator',
 		function(target, update_border){
 			var ribbon_set = this.ribbons.getRibbonSet()
@@ -2282,25 +2295,6 @@ module.CurrentImageIndicator = ImageGridFeatures.Feature({
 	tag: 'ui-current-image-indicator',
 	depends: ['ui'],
 
-	config: {
-		'current-image-border': 3,
-		'current-image-min-border': 2,
-
-		'current-image-border-timeout': 200,
-		'current-image-shift-timeout': 200,
-
-		'current-image-indicator-fadein': 500,
-
-		'current-image-indicator-hide-timeout': 250,
-
-		// this can be:
-		// 	'hide'			- simply hide on next/prev screen action
-		// 					  and show on focus image.
-		// 	'hide-show'		- hide on fast scroll through screens and 
-		// 					  show when slowing down.
-		'current-image-indicator-screen-nav-mode': 'hide',
-	},
-
 	actions: CurrentImageIndicatorActions,
 
 	handlers: [
@@ -2651,6 +2645,14 @@ var makeActionLister = function(list, filter, pre_order){
 }
 
 var ActionTreeActions = actions.Actions({
+	config: {
+		'action-category-order': [
+			'File',
+			'Edit',
+			'Navigate',
+		],
+	},
+
 	// XXX move this to a generic modal overlay feature...
 	getOverlay: ['Interface/Get overlay object',
 		function(o){
@@ -2869,14 +2871,6 @@ module.ActionTree = ImageGridFeatures.Feature({
 	tag: 'ui-action-tree',
 	depends: ['ui'],
 
-	config: {
-		'action-category-order': [
-			'File',
-			'Edit',
-			'Navigate',
-		],
-	},
-
 	actions: ActionTreeActions,
 })
 
@@ -3193,6 +3187,7 @@ module.AppControl = ImageGridFeatures.Feature({
 // XXX at this point this is a stub...
 if(window.nodejs != null){
 	var file = requirejs('./file')
+	var glob = requirejs('glob')
 }
 
 
@@ -3202,7 +3197,7 @@ var FileSystemLoaderActions = actions.Actions({
 	// 		...and how should this be handled when merging indexes or
 	//		viewing multiple/clustered indexes???
 	// XXX look inside...
-	loadPath: ['File/Load path',
+	loadIndex: ['File/Load index',
 		function(path, logger){
 			var that = this
 
@@ -3290,6 +3285,27 @@ var FileSystemLoaderActions = actions.Actions({
 					that.load(index)
 				})
 		}],
+	// XXX use the logger...
+	// XXX add a recursive option...
+	// 		...might also be nice to add sub-dirs to ribbons...
+	loadImages: ['File/Load images',
+		function(path, logger){
+			if(path == null){
+				return
+			}
+
+			var that = this
+
+			return glob(path + '/*+(jpg|png)')
+				.on('end', function(lst){ 
+					that.loadURLs(lst)
+				})
+		}],
+
+	// XXX auto-detect format or let the user chose...
+	loadPath: ['File/Load path',
+		function(){
+		}],
 })
 
 
@@ -3312,28 +3328,44 @@ module.FileSystemLoader = ImageGridFeatures.Feature({
 
 //---------------------------------------------------------------------
 
+var makeProwseProxy = function(action){
+	return function(path, logger){
+		var that = this
+		if(path == null){
+			// XXX should we set a start path here to current???
+			return this.browsePath(path, 
+				function(path){ 
+					return that[action](path, logger) 
+				})
+		}
+	}
+}
+
+
 var FileSystemLoaderUIActions = actions.Actions({
-	// NOTE: if no path is passed (null) this behaves just like .browsePath(..)
-	// 		otherwise it will just load the given path (no UI) while
-	// 		.browsePath(..) will load the UI in all cases but will treat 
-	// 		the given path as a base path to start from.
+	config: {
+		'path-loaders': [
+			'loadIndex',
+			'loadImages',
+		],
+	},
+
+	// NOTE: if no path is passed (null) these behave just like .browsePath(..)
+	// 		with the appropriate callback otherwise it will just load 
+	// 		the given path (no UI) while .browsePath(..) will load the 
+	// 		UI in all cases but will treat the given path as a base path 
+	// 		to start from.
 	// XXX should passing no path to this start browsing from the current
 	// 		path or from the root?
-	loadPath: ['File/Load path...',
-		function(path, logger){
-			var that = this
-			if(path == null){
-				// XXX should we set a start path here to current???
-				return this.browsePath(path, 
-					function(path){ 
-						return that.loadPath(path, logger) 
-					})
-			}
-		}],
+	loadIndex: ['File/Load index...',
+		makeProwseProxy('loadIndex')],
+	loadImages: ['File/Load images...',
+		makeProwseProxy('loadImages')],
 
 	// XXX BUG: for some reason this when run from .browseActions(..) or
 	// 		any other Browse, loads incorrectly while when called 
 	// 		directly is OK...
+	// XXX should the loader list be nested or open in overlay (as-is now)???
 	browsePath: ['File/Browse file system...',
 		function(base, callback){
 			var that = this
@@ -3344,18 +3376,46 @@ var FileSystemLoaderUIActions = actions.Actions({
 				require('./lib/widget/browse-walk').makeWalk(null, base, false, false)
 					// path selected...
 					.open(function(evt, path){ 
-						// close self and parent...
-						o.close() 
-						parent 
-							&& parent.close 
-							&& parent.close()
 
-						// pass the selected path on...
-						if(callback){
+						// single loader...
+						if(callback && callback.constructor === Function){
+							// close self and parent...
+							o.close() 
+							parent 
+								&& parent.close 
+								&& parent.close()
+
 							callback(path)
 
+						// list of loaders...
 						} else {
-							that.loadPath(path)
+							// user-provided list...
+							if(callback){
+								var loaders = callback
+
+							// build the loaders list from .config...
+							} else {
+								var loaders = {}
+								that.config['path-loaders'].forEach(function(m){
+									loaders[that[m].doc.split('/').pop()] = function(){ 
+										return that[m](path) 
+									}
+								})
+							}
+
+							// show user the list...
+							var so = overlay.Overlay(that.ribbons.viewer, 
+								browse.makeList(null, loaders)
+									// close self and parent...
+									.open(function(){
+										so.close()
+										o.close() 
+									}))
+									// closed menu...
+									.close(function(){
+										o.focus()
+									})
+							so.client.select(0)
 						}
 					}))
 					// we closed the browser...