diff --git a/xtrn/DDMsgReader/DDMsgReader.js b/xtrn/DDMsgReader/DDMsgReader.js
index 5d271d0d47b4318f4d8b8dc0077fd05f0bd2ba36..7c0963b1edc33a3961ba4a526d3df2fa4193047f 100644
--- a/xtrn/DDMsgReader/DDMsgReader.js
+++ b/xtrn/DDMsgReader/DDMsgReader.js
@@ -171,6 +171,15 @@
  *                              sub-board, and whether to show sub-boards with new messages in
  *                              the indexed newscan.
  *                              Releasing this version (1.96).
+ * 2024-11-02 Eric Oulashin     Version 1.96a
+ *                              When changing to another sub-board, the user can now cycle
+ *                              through the sort options with the [ and ] keys. Also,
+ *                              updated the change sub-board help to show in a scrollable
+ *                              window for users with ANSI terminals, rather than simply
+ *                              displaying the help with a pause at the end. Other help
+ *                              screens could potentially be shown this way too.
+ *                              New theme configuration options: helpWinBorderColor and
+ *                              scrollingWinHelpTextColor
  */
 
 "use strict";
@@ -271,13 +280,15 @@ require("attr_conv.js", "convertAttrsToSyncPerSysCfg");
 require("graphic.js", 'Graphic');
 require("smbdefs.js", "SMB_POLL_ANSWER");
 load('822header.js');
+require("frame.js", "Frame");
+require("scrollbar.js", "ScrollBar");
 var ansiterm = require("ansiterm_lib.js", 'expand_ctrl_a');
 var hexdump = load('hexdump_lib.js');
 
 
 // Reader version information
-var READER_VERSION = "1.96";
-var READER_DATE = "2024-10-26";
+var READER_VERSION = "1.96a";
+var READER_DATE = "2024-11-02";
 
 // Keyboard key codes for displaying on the screen
 var UP_ARROW = ascii(24);
@@ -392,6 +403,7 @@ var UPPER_CENTER_BLOCK = "\xDF";
 var LOWER_CENTER_BLOCK = "\xDC";
 
 
+const ERROR_MSG_ATTR_CODES = "\x01y\x01h";
 const ERROR_PAUSE_WAIT_MS = 1500;
 
 // Reader mode definitions:
@@ -457,6 +469,8 @@ const SUB_BOARD_SORT_NONE = 0;
 const SUB_BOARD_SORT_ALPHABETICAL = 1;
 const SUB_BOARD_SORT_LATEST_MSG_DATE_OLDEST_FIRST = 2;
 const SUB_BOARD_SORT_LATEST_MSG_DATE_NEWEST_FIRST = 3;
+// Maximum sort option value (would to be changed if more sort options are added)
+const SUB_BOARD_MAX_SORT_VALUE = SUB_BOARD_SORT_LATEST_MSG_DATE_NEWEST_FIRST;
 
 // Misc. defines
 var ERROR_WAIT_MS = 1500;
@@ -919,7 +933,7 @@ function DigDistMsgReader(pSubBoardCode, pScriptArgs)
 	this.GetUpvoteAndDownvoteInfo = DigDistMsgReader_GetUpvoteAndDownvoteInfo;
 	this.GetMsgBody = DigDistMsgReader_GetMsgBody;
 	this.RefreshMsgHdrInArrays = DigDistMsgReader_RefreshMsgHdrInArrays;
-	this.WriteLightbarKeyHelpErrorMsg = DigDistMsgReader_WriteLightbarKeyHelpErrorMsg;
+	this.WriteLightbarKeyHelpMsg = DigDistMsgReader_WriteLightbarKeyHelpMsg;
 
 	// startMode specifies the mode for the reader to start in - List mode
 	// or reader mode, etc.  This is a setting that is read from the configuration
@@ -1320,6 +1334,10 @@ function DigDistMsgReader(pSubBoardCode, pScriptArgs)
 		}
 	}
 
+	// Message sub-board sort option for changing to a different sub-board, to
+	// persist while the reader is running but not to save to user settings
+	this.subBoardSortOptionWhileRunning = this.userSettings.subBoardChangeSorting;
+
 	// this.tabReplacementText will be the text that tabs will be replaced
 	// with in enhanced reader mode
 	//this.tabReplacementText = format("%" + this.numTabSpaces + "s", "");
@@ -1501,8 +1519,8 @@ function DigDistMsgReader(pSubBoardCode, pScriptArgs)
 	this.WriteMsgGroupLine = DigDistMsgReader_writeMsgGroupLine;
 	this.UpdateMsgAreaPageNumInHeader = DigDistMsgReader_updateMsgAreaPageNumInHeader;
 	this.GetMsgSubBoardLine = DigDistMsgReader_GetMsgSubBoardLine;
-	// Choose Message Area help screen
-	this.ShowChooseMsgAreaHelpScreen = DigDistMsgReader_showChooseMsgAreaHelpScreen;
+	// Choose Message Area help
+	this.ShowChooseMsgAreaHelp = DigDistMsgReader_ShowChooseMsgAreaHelp;
 	// Method to build the sub-board printf information for a message
 	// group
 	this.BuildSubBoardPrintfInfoForGrp = DigDistMsgReader_BuildSubBoardPrintfInfoForGrp;
@@ -1624,7 +1642,7 @@ function DigDistMsgReader(pSubBoardCode, pScriptArgs)
 	// The selected message cursor position for the lightbar message list (initially
 	// null, will be set in the lightbar list message)
 	this.lightbarListCurPos = null;
-
+	
 	// selectedMessages will be an object (indexed by sub-board internal code)
 	// containing objects that contain message indexes (as properties) for the
 	// sub-boards.  Messages can be selected by the user for doing things such
@@ -3920,19 +3938,25 @@ function DigDistMsgReader_ListMessages_Lightbar(pAllowChgSubBoard)
 
 	// This function will be used for displaying the help line at
 	// the bottom of the screen.
-	function DisplayHelpLine(pHelpLineText)
+	function DisplayHelpLine(pHelpLineText, pHelpLineLen)
 	{
 		console.gotoxy(1, console.screen_rows);
 		// Mouse: console.print replaced with console.putmsg for mouse click hotspots
 		//console.print(pHelpLineText);
 		console.putmsg(pHelpLineText); // console.putmsg() can process @-codes, which we use for mouse click tracking
-		console.cleartoeol("\x01n");
+		//console.cleartoeol("\x01n"); // In some cases, this seems to output several extra blank lines
+		if (pHelpLineLen < console.screen_columns - 1)
+		{
+			console.attributes = "N";
+			var diff = console.screen_columns - pHelpLineLen - 1;
+			format("%*s", diff, "");
+		}
 	}
 
 	// Clear the screen and write the header at the top
 	console.clear("\x01n");
 	this.WriteMsgListScreenTopHeader();
-	DisplayHelpLine(this.msgListLightbarModeHelpLine);
+	DisplayHelpLine(this.msgListLightbarModeHelpLine, this.msgListLightbarModeHelpLineLen);
 
 	// If the lightbar message list index & cursor position variables haven't been
 	// set yet, then set them.
@@ -4047,7 +4071,7 @@ function DigDistMsgReader_ListMessages_Lightbar(pAllowChgSubBoard)
 				if (continueOn)
 				{
 					this.WriteMsgListScreenTopHeader();
-					DisplayHelpLine(this.msgListLightbarModeHelpLine);
+					DisplayHelpLine(this.msgListLightbarModeHelpLine, this.msgListLightbarModeHelpLineLen);
 				}
 			}
 		}
@@ -4117,7 +4141,7 @@ function DigDistMsgReader_ListMessages_Lightbar(pAllowChgSubBoard)
 			if (continueOn)
 			{
 				this.WriteMsgListScreenTopHeader();
-				DisplayHelpLine(this.msgListLightbarModeHelpLine);
+				DisplayHelpLine(this.msgListLightbarModeHelpLine, this.msgListLightbarModeHelpLineLen);
 			}
 		}
 		// DEL key: Delete a message
@@ -4143,7 +4167,7 @@ function DigDistMsgReader_ListMessages_Lightbar(pAllowChgSubBoard)
 					// There are still some messages to show, so refresh the screen.
 					// Refresh the header & help line.
 					this.WriteMsgListScreenTopHeader();
-					DisplayHelpLine(this.msgListLightbarModeHelpLine);
+					DisplayHelpLine(this.msgListLightbarModeHelpLine, this.msgListLightbarModeHelpLineLen);
 				}
 			}
 		}
@@ -4168,7 +4192,7 @@ function DigDistMsgReader_ListMessages_Lightbar(pAllowChgSubBoard)
 					var returnObj = this.EditExistingMsg(this.lightbarListSelectedMsgIdx);
 					// Refresh the header & help line
 					this.WriteMsgListScreenTopHeader();
-					DisplayHelpLine(this.msgListLightbarModeHelpLine);
+					DisplayHelpLine(this.msgListLightbarModeHelpLine, this.msgListLightbarModeHelpLineLen);
 				}
 			}
 			else
@@ -4218,7 +4242,7 @@ function DigDistMsgReader_ListMessages_Lightbar(pAllowChgSubBoard)
 
 			// Refresh the header & help lines
 			this.WriteMsgListScreenTopHeader();
-			DisplayHelpLine(this.msgListLightbarModeHelpLine);
+			DisplayHelpLine(this.msgListLightbarModeHelpLine, this.msgListLightbarModeHelpLineLen);
 		}
 		// C: Change to another message area (sub-board)
 		else if (lastUserInputUpper == this.msgListKeys.chgMsgArea)
@@ -4253,7 +4277,7 @@ function DigDistMsgReader_ListMessages_Lightbar(pAllowChgSubBoard)
 					// Adjust the menu indexes to ensure they're correct for the current sub-board
 					this.AdjustLightbarMsgListMenuIdxes(msgListMenu);
 					this.WriteMsgListScreenTopHeader();
-					DisplayHelpLine(this.msgListLightbarModeHelpLine);
+					DisplayHelpLine(this.msgListLightbarModeHelpLine, this.msgListLightbarModeHelpLineLen);
 				}
 			}
 			else
@@ -4266,7 +4290,7 @@ function DigDistMsgReader_ListMessages_Lightbar(pAllowChgSubBoard)
 			// Re-draw the message list header & help line before
 			// the menu is re-drawn
 			this.WriteMsgListScreenTopHeader();
-			DisplayHelpLine(this.msgListLightbarModeHelpLine);
+			DisplayHelpLine(this.msgListLightbarModeHelpLine, this.msgListLightbarModeHelpLineLen);
 		}
 		// Spacebar: Select a message for batch operations (such as batch
 		// delete, etc.)
@@ -4304,7 +4328,7 @@ function DigDistMsgReader_ListMessages_Lightbar(pAllowChgSubBoard)
 				drawMenu = false; // No need to re-draw the menu
 
 			// Refresh the help line
-			DisplayHelpLine(this.msgListLightbarModeHelpLine);
+			DisplayHelpLine(this.msgListLightbarModeHelpLine, this.msgListLightbarModeHelpLineLen);
 		}
 		// Ctrl-D: Batch delete (for selected messages)
 		else if (lastUserInputUpper == this.msgListKeys.batchDelete) // CTRL_D
@@ -4337,7 +4361,7 @@ function DigDistMsgReader_ListMessages_Lightbar(pAllowChgSubBoard)
 					{
 						// There are still messages to list, so refresh the header & help lines
 						this.WriteMsgListScreenTopHeader();
-						DisplayHelpLine(this.msgListLightbarModeHelpLine);
+						DisplayHelpLine(this.msgListLightbarModeHelpLine, this.msgListLightbarModeHelpLineLen);
 					}
 				}
 				else
@@ -4345,7 +4369,7 @@ function DigDistMsgReader_ListMessages_Lightbar(pAllowChgSubBoard)
 					// There are no selected messages
 					writeWithPause(1, console.screen_rows, "\x01n\x01h\x01yThere are no selected messages.", ERROR_PAUSE_WAIT_MS, "\x01n", true);
 					// Refresh the help line
-					DisplayHelpLine(this.msgListLightbarModeHelpLine);
+					DisplayHelpLine(this.msgListLightbarModeHelpLine, this.msgListLightbarModeHelpLineLen);
 				}
 			}
 		}
@@ -4370,12 +4394,12 @@ function DigDistMsgReader_ListMessages_Lightbar(pAllowChgSubBoard)
 
 				// Refresh the header & help line.
 				this.WriteMsgListScreenTopHeader();
-				DisplayHelpLine(this.msgListLightbarModeHelpLine);
+				DisplayHelpLine(this.msgListLightbarModeHelpLine, this.msgListLightbarModeHelpLineLen);
 			}
 		}
 		else if (lastUserInputUpper == this.msgListKeys.userSettings)
 		{
-			var userSettingsRetObj = this.DoUserSettings_Scrollable(function(pReader) { DisplayHelpLine(pReader.msgListLightbarModeHelpLine); });
+			var userSettingsRetObj = this.DoUserSettings_Scrollable(function(pReader) { DisplayHelpLine(pReader.msgListLightbarModeHelpLine, pReader.msgListLightbarModeHelpLineLen); });
 			lastUserInputUpper = "";
 			drawMenu = userSettingsRetObj.needWholeScreenRefresh;
 			// In case the user changed their twitlist, re-filter the messages for this sub-board
@@ -4400,7 +4424,7 @@ function DigDistMsgReader_ListMessages_Lightbar(pAllowChgSubBoard)
 			if (userSettingsRetObj.needWholeScreenRefresh)
 			{
 				this.WriteMsgListScreenTopHeader();
-				DisplayHelpLine(this.msgListLightbarModeHelpLine);
+				DisplayHelpLine(this.msgListLightbarModeHelpLine, this.msgListLightbarModeHelpLineLen);
 			}
 			else
 				msgListMenu.DrawPartialAbs(userSettingsRetObj.optionBoxTopLeftX, userSettingsRetObj.optionBoxTopLeftY, userSettingsRetObj.optionBoxWidth, userSettingsRetObj.optionBoxHeight);
@@ -4686,10 +4710,12 @@ function DigDistMsgReader_CreateLightbarMsgGrpMenu()
 //
 // Parameters:
 //  pGrpIdx: The index of the group to list sub-boards for
+//  pSortOption: Optional - An override for the sort option to use.  If not specified (or invalid),
+//               this will use the user's configured sort option (this.userSettings.subBoardChangeSorting)
 //
 // Return value: A DDLightbarMenu object set up to let the user choose a sub-board within the
 //               given message group
-function DigDistMsgReader_CreateLightbarSubBoardMenu(pGrpIdx)
+function DigDistMsgReader_CreateLightbarSubBoardMenu(pGrpIdx, pSortOption)
 {
 	// Start & end indexes for the various items in each sub-board list row
 	// Selected mark, group#, description, # sub-boards
@@ -4734,10 +4760,14 @@ function DigDistMsgReader_CreateLightbarSubBoardMenu(pGrpIdx)
 
 	// Add additional keypresses for quitting the menu's input loop so we can
 	// respond to these keys
-	subBoardMenu.AddAdditionalQuitKeys("nNqQ ?0123456789/" + CTRL_F);
+	subBoardMenu.AddAdditionalQuitKeys("nNqQ ?0123456789/[]" + CTRL_F);
 
-	// Add the sub-board items to the menu
-	if (this.userSettings.subBoardChangeSorting == SUB_BOARD_SORT_ALPHABETICAL)
+	// Add the sub-board items to the menu. Sort according to the user's
+	// sorting option.
+	var sortOption = this.userSettings.subBoardChangeSorting;
+	if (typeof(pSortOption) === "number" && pSortOption >= 0 && pSortOption <= SUB_BOARD_MAX_SORT_VALUE)
+		sortOption = pSortOption;
+	if (sortOption == SUB_BOARD_SORT_ALPHABETICAL)
 	{
 		var sortedSubs = [];
 		for (var subIdx = 0; subIdx < msg_area.grp_list[pGrpIdx].sub_list.length; ++subIdx)
@@ -4762,13 +4792,13 @@ function DigDistMsgReader_CreateLightbarSubBoardMenu(pGrpIdx)
 			subBoardMenu.Add(strip_ctrl(itemText), sortedSubs[subsI].subIdx);
 		}
 	}
-	else if (this.userSettings.subBoardChangeSorting == SUB_BOARD_SORT_LATEST_MSG_DATE_OLDEST_FIRST ||
-	         this.userSettings.subBoardChangeSorting == SUB_BOARD_SORT_LATEST_MSG_DATE_NEWEST_FIRST)
+	else if (sortOption == SUB_BOARD_SORT_LATEST_MSG_DATE_OLDEST_FIRST ||
+	         sortOption == SUB_BOARD_SORT_LATEST_MSG_DATE_NEWEST_FIRST)
 	{
 		var sortedSubs = [];
 		for (var subIdx = 0; subIdx < msg_area.grp_list[pGrpIdx].sub_list.length; ++subIdx)
 			sortedSubs.push(getSubBoardInfo(pGrpIdx, subIdx, this.msgAreaList_lastImportedMsg_showImportTime));
-		if (this.userSettings.subBoardChangeSorting == SUB_BOARD_SORT_LATEST_MSG_DATE_OLDEST_FIRST)
+		if (sortOption == SUB_BOARD_SORT_LATEST_MSG_DATE_OLDEST_FIRST)
 		{
 			sortedSubs.sort(function(pA, pB)
 			{
@@ -4780,7 +4810,7 @@ function DigDistMsgReader_CreateLightbarSubBoardMenu(pGrpIdx)
 					return 1;
 			});
 		}
-		else if (this.userSettings.subBoardChangeSorting == SUB_BOARD_SORT_LATEST_MSG_DATE_NEWEST_FIRST)
+		else if (sortOption == SUB_BOARD_SORT_LATEST_MSG_DATE_NEWEST_FIRST)
 		{
 			sortedSubs.sort(function(pA, pB)
 			{
@@ -4835,7 +4865,7 @@ function DigDistMsgReader_CreateLightbarSubBoardMenu(pGrpIdx)
 	{
 		// If no sorting is being used, then simply set the current selected
 		// index to the sub-board index.
-		if (this.userSettings.subBoardChangeSorting == SUB_BOARD_SORT_NONE)
+		if (sortOption == SUB_BOARD_SORT_NONE)
 			subBoardMenu.SetSelectedItemIdx(msg_area.sub[this.subBoardCode].index);
 		else
 		{
@@ -9438,14 +9468,21 @@ function DigDistMsgReader_SetMsgListPauseTextAndLightbarHelpLine()
 		var numLeft = Math.floor(numChars / 2);
 		var numRight = numChars - numLeft;
 		for (var i = 0; i < numLeft; ++i)
+		{
 			this.msgListLightbarModeHelpLine = " " + this.msgListLightbarModeHelpLine;
+			++lbHelpLineLen;
+		}
 		this.msgListLightbarModeHelpLine = "\x01n"
 		                             + this.colors.lightbarMsgListHelpLineBkgColor
 		                             + this.msgListLightbarModeHelpLine;
 		this.msgListLightbarModeHelpLine += "\x01n" + this.colors.lightbarMsgListHelpLineBkgColor;
 		for (var i = 0; i < numRight; ++i)
+		{
 			this.msgListLightbarModeHelpLine += ' ';
+			++lbHelpLineLen;
+		}
 	}
+	this.msgListLightbarModeHelpLineLen = lbHelpLineLen;
 }
 // For the DigDistMsgReader Class: Sets the hotkey help line for the enhanced
 // reader mode
@@ -13015,7 +13052,7 @@ function DigDistMsgReader_SelectMsgArea_Lightbar(pMsgGrp, pGrpIdx)
 					}
 					else
 					{
-						this.WriteLightbarKeyHelpErrorMsg("Not found");
+						this.WriteLightbarKeyHelpMsg("Not found", ERROR_MSG_ATTR_CODES, ERROR_WAIT_MS);
 						drawMenu = false;
 					}
 				}
@@ -13091,7 +13128,7 @@ function DigDistMsgReader_SelectMsgArea_Lightbar(pMsgGrp, pGrpIdx)
 					}
 					else
 					{
-						this.WriteLightbarKeyHelpErrorMsg("Not found");
+						this.WriteLightbarKeyHelpMsg("Not found", ERROR_MSG_ATTR_CODES, ERROR_WAIT_MS);
 						drawMenu = false;
 						this.WriteChgMsgAreaKeysHelpLine();
 					}
@@ -13099,14 +13136,18 @@ function DigDistMsgReader_SelectMsgArea_Lightbar(pMsgGrp, pGrpIdx)
 			}
 			else
 			{
-				this.WriteLightbarKeyHelpErrorMsg("There is no previous search", REFRESH_MSG_AREA_CHG_LIGHTBAR_HELP_LINE);
+				this.WriteLightbarKeyHelpMsg("There is no previous search", ERROR_MSG_ATTR_CODES, ERROR_WAIT_MS, REFRESH_MSG_AREA_CHG_LIGHTBAR_HELP_LINE);
 				drawMenu = false;
 				this.WriteChgMsgAreaKeysHelpLine();
 			}
 		}
 		else if (lastUserInputUpper == "?") // Show help
 		{
-			this.ShowChooseMsgAreaHelpScreen(true, true);
+			var screenInfo = this.ShowChooseMsgAreaHelp(!chooseMsgGrp, true, true);
+			msgAreaMenu.DrawPartialAbs(screenInfo.topLeftX, screenInfo.topLeftY, screenInfo.width, screenInfo.height);
+			drawMenu = false;
+			/*
+			this.ShowChooseMsgAreaHelp(!chooseMsgGrp, true, true);
 			console.pause();
 			// Refresh the screen
 			console.clear("\x01n");
@@ -13114,6 +13155,48 @@ function DigDistMsgReader_SelectMsgArea_Lightbar(pMsgGrp, pGrpIdx)
 			this.DisplayAreaChgHdr(1);
 			displayListHdrLines(this.areaChangeHdrLines.length+1, chooseMsgGrp, this);
 			this.WriteChgMsgAreaKeysHelpLine();
+			drawMenu = true;
+			*/
+		}
+		else if (lastUserInputUpper == "]")
+		{
+			// Cycle through sort options (higher). This key should only be returned
+			// from the sub-board menu, but check chooseMsgGrp anyway just to be safe.
+			if (!chooseMsgGrp)
+			{
+				// The sort option numeric values are from 0 to
+				// SUB_BOARD_MAX_SORT_VALUE, inclusive
+				if (this.subBoardSortOptionWhileRunning == SUB_BOARD_MAX_SORT_VALUE)
+					this.subBoardSortOptionWhileRunning = 0;
+				else
+					++this.subBoardSortOptionWhileRunning;
+				// Re-create the sub-board menu, which will use the new sort option
+				var msgAreaMenu = this.CreateLightbarSubBoardMenu(pGrpIdx, this.subBoardSortOptionWhileRunning);
+				// Write the new sorting option momentarily
+				var sortOptStr = subBoardSortOptionToStr(this.subBoardSortOptionWhileRunning);
+				this.WriteLightbarKeyHelpMsg("New sorting: " + sortOptStr, "\x01n\x01c\x01h", 1000, REFRESH_MSG_AREA_CHG_LIGHTBAR_HELP_LINE);
+				drawMenu = true;
+			}
+		}
+		else if (lastUserInputUpper == "[")
+		{
+			// Cycle through sort options (lower). This key should only be returned
+			// from the sub-board menu, but check chooseMsgGrp anyway just to be safe.
+			if (!chooseMsgGrp)
+			{
+				// The sort option numeric values are from 0 to
+				// SUB_BOARD_MAX_SORT_VALUE, inclusive
+				if (this.subBoardSortOptionWhileRunning == 0)
+					this.subBoardSortOptionWhileRunning = SUB_BOARD_MAX_SORT_VALUE;
+				else
+					--this.subBoardSortOptionWhileRunning;
+				// Re-create the sub-board menu, which will use the new sort option
+				var msgAreaMenu = this.CreateLightbarSubBoardMenu(pGrpIdx, this.subBoardSortOptionWhileRunning);
+				// Write the new sorting option momentarily
+				var sortOptStr = subBoardSortOptionToStr(this.subBoardSortOptionWhileRunning);
+				this.WriteLightbarKeyHelpMsg("New sorting: " + sortOptStr, "\x01n\x01c\x01h", 1000, REFRESH_MSG_AREA_CHG_LIGHTBAR_HELP_LINE);
+				drawMenu = true;
+			}
 		}
 		// If the user entered a numeric digit, then treat it as
 		// the start of the message group number.
@@ -13279,29 +13362,34 @@ function DigDistMsgReader_SelectMsgArea_Traditional()
 				*/
 
 				var subSearchText = "";
+				// Format string for the prompt text, with a %d for the sub-board number
+				var promptTextFormatStr = format("\x01n\x01b\x01h%s \x01n\x01cWhich, \x01h/\x01n\x01c or "
+				                        + "\x01hCTRL-F\x01n\x01c, \x01h[\x01n\x01c, \x01h]\x01n\x01c, \x01hQ\x01n\x01cuit, "
+				                        + "or [\x01h%%d\x01n\x01c]: \x01h", TALL_UPPER_MID_BLOCK);
 				var continueChoosingSubBoard = true;
 				while (continueChoosingSubBoard)
 				{
+					console.line_counter = 0; // To avoid a screen pause
 					console.clear("\x01n");
 					this.DisplayAreaChgHdr();
-					var subIndexes = this.ListSubBoardsInMsgGroup(selectedGrp-1, defaultSubBoard-1, this.userSettings.subBoardChangeSorting, subSearchText);
+					var subIndexes = this.ListSubBoardsInMsgGroup(selectedGrp-1, defaultSubBoard-1, this.subBoardSortOptionWhileRunning, subSearchText);
 					console.crlf();
-					console.print("\x01n\x01b\x01h" + TALL_UPPER_MID_BLOCK + " \x01n\x01cWhich, \x01h/\x01n\x01c or \x01hCTRL-F\x01n\x01c, \x01hQ\x01n\x01cuit, or [\x01h" +
-					              defaultSubBoard + "\x01n\x01c]: \x01h");
+					printf(promptTextFormatStr, defaultSubBoard);
 					// Accept Q (quit), / or CTRL_F (Search) or a sub-board number
-					selectedSubBoard = console.getkeys("Q/" + CTRL_F, msg_area.grp_list[selectedGrp - 1].sub_list.length);
+					selectedSubBoard = console.getkeys("Q/[]" + CTRL_F, msg_area.grp_list[selectedGrp - 1].sub_list.length);
+					var userChoiceAsStr = selectedSubBoard.toString();
 
 					// If the user just pressed enter (selectedSubBoard would be blank),
 					// default the selected directory.
-					if (selectedSubBoard.toString() == "")
+					if (userChoiceAsStr == "")
 						selectedSubBoard = defaultSubBoard;
 
 					// If the user chose to quit out of the sub-board list, then
 					// return to the message group list.
-					if (selectedSubBoard.toString() == "Q")
+					if (userChoiceAsStr == "Q")
 						continueChoosingSubBoard = false;
 					// / or CTRL-F: Search
-					else if ((selectedSubBoard.toString() == "/") || (selectedSubBoard.toString() == CTRL_F))
+					else if (userChoiceAsStr == "/" || userChoiceAsStr == CTRL_F)
 					{
 						console.crlf();
 						var searchPromptText = "\x01n\x01c\x01hSearch\x01g: \x01n";
@@ -13310,6 +13398,34 @@ function DigDistMsgReader_SelectMsgArea_Traditional()
 						if (searchText.length > 0)
 							subSearchText = searchText;
 					}
+					else if (userChoiceAsStr == "]")
+					{
+						// Cycle through sort options (higher)
+						// The sort option numeric values are from 0 to
+						// SUB_BOARD_MAX_SORT_VALUE, inclusive
+						if (this.subBoardSortOptionWhileRunning == SUB_BOARD_MAX_SORT_VALUE)
+							this.subBoardSortOptionWhileRunning = 0;
+						else
+							++this.subBoardSortOptionWhileRunning;
+						var sortOptStr = subBoardSortOptionToStr(this.subBoardSortOptionWhileRunning);
+						console.print("\x01n\x01c\x01hNew sorting: " + sortOptStr + "\x01;\x01;");
+						console.line_counter = 0; // To avoid a screen pause
+						console.crlf();
+					}
+					else if (userChoiceAsStr == "[")
+					{
+						// Cycle through sort options (lower)
+						// The sort option numeric values are from 0 to
+						// SUB_BOARD_MAX_SORT_VALUE, inclusive
+						if (this.subBoardSortOptionWhileRunning == 0)
+							this.subBoardSortOptionWhileRunning = SUB_BOARD_MAX_SORT_VALUE;
+						else
+							--this.subBoardSortOptionWhileRunning;
+						var sortOptStr = subBoardSortOptionToStr(this.subBoardSortOptionWhileRunning);
+						console.print("\x01n\x01c\x01hNew sorting: " + sortOptStr + "\x01;\x01;");
+						console.line_counter = 0; // To avoid a screen pause
+						console.crlf();
+					}
 					// If the user chose a message sub-board, then validate the user's
 					// sub-board choice; if that succeeds, then change the user's
 					// sub-board to that and quit out of the chooser loops.
@@ -13710,67 +13826,144 @@ function DigDistMsgReader_GetMsgSubBoardLine(pGrpIndex, pSubIndex, pHighlight, p
 // Other functions for the msg. area chooser //
 ///////////////////////////////////////////////
 
-// For the DigDistMsgReader class: Shows the help screen
+// For the DigDistMsgReader class: Shows help for choosing a message area.
+// If the user's terminal supports ANSI, displays the help in a scrollable
+// window. Returns an object with X, Y, width, and height properties for the
+// scrollable window that was used; if a scrollable window wasn't used, the
+// values of the returned object's propreties will all be 0.
 //
 // Parameters:
-//  pLightbar: Boolean - Whether or not to show lightbar help.  If
-//             false, then this function will show regular help.
+//  pChoosingSubBoard: Boolean - Whether or not the user is choosing a sub-board.
+//                     If false, the user is choosing a message group.
+//  pLightbar: Boolean - Whether or not to show lightbar help along with
+//             regular help.
 //  pClearScreen: Boolean - Whether or not to clear the screen first
-function DigDistMsgReader_showChooseMsgAreaHelpScreen(pLightbar, pClearScreen)
+//
+// Return value: An object containing the following properties:
+//               topLeftX: The X coordinate of the top-left corner of the help box (if used; will be 0 if not)
+//               topLeftY: The Y coordinate of the top-left corner of the help box (if used; will be 0 if not)
+//               width: The width of the help box (if used; will be 0 if not)
+//               height: The height of the help box (if used; will be 0 if not)
+function DigDistMsgReader_ShowChooseMsgAreaHelp(pChoosingSubBoard, pLightbar, pClearScreen)
 {
-	if (pClearScreen && console.term_supports(USER_ANSI))
-		console.clear("\x01n");
-	else
-		console.attributes = "N";
-	DisplayProgramInfo();
-	console.crlf();
-	console.print("\x01n\x01c\x01hMessage area (sub-board) chooser");
-	console.crlf();
-	console.print("\x01k" + charStr(HORIZONTAL_SINGLE, 32) + "\x01n");
-	console.crlf();
-	console.print("\x01cFirst, a listing of message groups is displayed.  One can be chosen by typing");
-	console.crlf();
-	console.print("its number.  Then, a listing of sub-boards within that message group will be");
-	console.crlf();
-	console.print("shown, and one can be chosen by typing its number.");
-	console.crlf();
+	// A return object with box information, if used
+	var retObj = {
+		topLeftX: 0,
+		topLeftY: 0,
+		width: 0,
+		height: 0
+	};
 
-	console.crlf();
-	console.print("Keyboard commands:");
-	console.crlf();
-	console.print("\x01k\x01h" + charStr(HORIZONTAL_SINGLE, 18) + "\x01n");
-	console.crlf();
-	console.print("\x01n\x01c\x01h/\x01n\x01c or \x01hCTRL-F\x01n\x01c: Find group/sub-board");
-	console.crlf();
-	console.print("\x01n\x01c\x01h?\x01n\x01c: Show this help screen");
-	console.crlf();
-	console.print("\x01hQ\x01n\x01c: Quit");
-	console.crlf();
+	// If the user's terminal supports ANSI, show the help in a scrollable window
+	if (console.term_supports(USER_ANSI))
+	{
+		// Frame dimensions
+		var frameUpperLeftX = 3;
+		var frameUpperLeftY = 3;
+		var frameWidth = console.screen_columns - (frameUpperLeftX*2);
+		var frameHeight = console.screen_rows - (frameUpperLeftY*2);
+		var frameInnerWidth = frameWidth - 3; // 3 due to the use of a scrollbar
+		retObj.topLeftX = frameUpperLeftX;
+		retObj.topLeftY = frameUpperLeftY;
+		retObj.width = frameWidth;
+		retObj.height = frameHeight;
+
+		var normalColor = "\x01n" + this.colors.scrollingWinHelpTextColor;
+		var highColor = "\x01n" + this.colors.scrollingWinHelpTextColor + "\x01h";
+
+		// Center a title in the window
+		var helpStr = "Message area (sub-board) chooser";
+		var width = Math.floor((frameInnerWidth/2)-(helpStr.length/2));
+		var helpText = "\x01n" + format("%*s", width, "") + normalColor + helpStr + "\r\n";
+		helpText += "\x01k" + format("%*s", width, "") + charStr(HORIZONTAL_SINGLE, helpStr.length) + "\x01n\r\n";
+		// Help text
+		helpStr = format("\x01n%sFirst, a listing of message groups is displayed.  One can be chosen by\r\n", normalColor);
+		helpStr += "typing its number.  Then, a listing of sub-boards within that message\r\n";
+		helpStr += "group will be shown, and one can be chosen by typing its number.";
+		helpText += lfexpand(word_wrap(helpStr), frameInnerWidth, null, false);
+		// Not wrapping properly:
+		/*
+		helpStr = "\x01cFirst, a listing of message groups is displayed.  One can be chosen by typing ";
+		helpStr += "its number.  Then, a listing of sub-boards within that message group will be ";
+		helpStr += "shown, and one can be chosen by typing its number.";
+		helpText += lfexpand(word_wrap(helpStr), frameInnerWidth, null, false);
+		*/
 
-	if (pLightbar)
+		helpText += "\r\n";
+		helpText += "Keyboard commands:\r\n";
+		helpText += "\x01k\x01h" + charStr(HORIZONTAL_SINGLE, frameInnerWidth) + "\x01n\r\n";
+		helpText += format("%s/%s or %sCTRL-F%s: Find group/sub-board\r\n", highColor, normalColor, highColor, normalColor);
+		if (pChoosingSubBoard)
+			helpText += format("%s[%s or %s]%s: Cycle through sub-board sort options\r\n", highColor, normalColor, highColor, normalColor);
+		helpText += format("%s?%s: Show this help screen\r\n", highColor, normalColor);
+		helpText += format("%sQ%s: Quit\r\n", highColor, normalColor);
+
+		if (pLightbar)
+		{
+			helpText += "\r\n";
+			helpText += format("%sThe lightbar interface also allows up & down navigation through the\r\n", normalColor);
+			helpText += "lists:\r\n";
+			helpText += "\x01n\x01k\x01h" + charStr(HORIZONTAL_SINGLE, frameInnerWidth) + "\r\n";
+			helpText += format("%sUp\x01n%s/%sdown arrow\x01n%s: Move the cursor up/down one line\r\n", highColor, normalColor, highColor, normalColor);
+			helpText += format("%sPageUp%s/%sPageDown%s: Move up/down a page\r\n", highColor, normalColor, highColor, normalColor);
+			helpText += format("%sENTER%s: Select the current group/sub-board\r\n", highColor, normalColor);
+			helpText += format("%sHOME%s: Go to the first item on the screen\r\n", highColor, normalColor);
+			helpText += format("%sEND%s: Go to the last item on the screen\r\n", highColor, normalColor);
+			helpText += format("%sF%s: Go to the first page\r\n", highColor, normalColor);
+			helpText += format("%sL%s: Go to the last page\r\n", highColor, normalColor);
+			helpText += format("%sN%s: Next search result\r\n", highColor, normalColor);
+		}
+
+		// Construct & draw a frame with the file information & do the input loop
+		// for the frame until the user closes the frame.
+		var frameTitle = "Change Sub-board Help";
+		displayBorderedFrameAndDoInputLoop(frameUpperLeftX, frameUpperLeftY, frameWidth, frameHeight,
+										   this.colors.helpWinBorderColor, frameTitle,
+										   "Change Sub-Board Help", helpText);
+	}
+	else
 	{
+		// The user's terminal doesn't support ANSI - Don't use a scrollable window
+		if (pClearScreen && console.term_supports(USER_ANSI))
+			console.clear("\x01n");
+		else
+			console.attributes = "N";
+		DisplayProgramInfo();
 		console.crlf();
-		console.print("\x01n\x01cThe lightbar interface also allows up & down navigation through the lists:");
-		console.crlf();
-		console.print("\x01k\x01h" + charStr(HORIZONTAL_SINGLE, 74));
-		console.crlf();
-		console.print("\x01n\x01c\x01hUp\x01n\x01c/\x01hdown arrow\x01n\x01c: Move the cursor up/down one line");
-		console.crlf();
-		console.print("\x01hPageUp\x01n\x01c/\x01hPageDown\x01n\x01c: Move up/down a page");
-		console.crlf();
-		console.print("\x01hENTER\x01n\x01c: Select the current group/sub-board");
-		console.crlf();
-		console.print("\x01hHOME\x01n\x01c: Go to the first item on the screen");
-		console.crlf();
-		console.print("\x01hEND\x01n\x01c: Go to the last item on the screen");
-		console.crlf();
-		console.print("\x01hF\x01n\x01c: Go to the first page");
-		console.crlf();
-		console.print("\x01hL\x01n\x01c: Go to the last page");
-		console.crlf();
-		console.print("\x01hN\x01n\x01c: Next search result");
+		var normalColor = "\x01n" + this.colors.tradInterfaceHelpScreenColor;
+		var highColor = "\x01n" + this.colors.tradInterfaceHelpScreenColor + "\x01h";
+		printf("%sMessage area (sub-board) chooser\r\n", normalColor);
+		console.print("\x01n\x01k\x01h" + charStr(HORIZONTAL_SINGLE, 32) + "\x01n\r\n");
+		var helpStr = format("%sFirst, a listing of message groups is displayed.  One can be chosen by typing ", normalColor);
+		helpStr += "its number.  Then, a listing of sub-boards within that message group will be ";
+		helpStr += "shown, and one can be chosen by typing its number.";
+		console.print(lfexpand(word_wrap(helpStr)));
+
 		console.crlf();
+		console.print("Keyboard commands:\r\n");
+		console.print("\x01n\x01k\x01h" + charStr(HORIZONTAL_SINGLE, 18) + "\x01n\r\n");
+		printf("%s/%s or %sCTRL-F%s: Find group/sub-board\r\n", highColor, normalColor, highColor, normalColor);
+		printf("%s[%s or %s]%sc: Cycle through sub-board sort options\r\n", highColor, normalColor, highColor, normalColor);
+		printf("%s?%s Show this help screen\r\n", highColor, normalColor);
+		printf("%sQ%s: Quit\r\n", highColor, normalColor);
+
+		if (pLightbar)
+		{
+			console.crlf();
+			printf("%sThe lightbar interface also allows up & down navigation through the lists:\r\n", normalColor);
+			printf("\x01n\x01k\x01h" + charStr(HORIZONTAL_SINGLE, 74) + "\r\n");
+			printf("%sUp%s/%sdown arrow%s: Move the cursor up/down one line\r\n", highColor, normalColor, highColor, normalColor);
+			printf("%sPageUp%s/%sPageDown%s: Move up/down a page\r\n", highColor, normalColor, highColor, normalColor);
+			printf("%sENTER%s: Select the current group/sub-board\r\n", highColor, normalColor);
+			printf("%sHOME%s: Go to the first item on the screen\r\n", highColor, normalColor);
+			printf("%sEND%s: Go to the last item on the screen\r\n", highColor, normalColor);
+			printf("%sF%s: Go to the first page\r\n", highColor, normalColor);
+			printf("%sL%s: Go to the last page\r\n", highColor, normalColor);
+			printf("%sN%s: Next search result\r\n", highColor, normalColor);
+		}
 	}
+
+	return retObj;
 }
 
 // Builds sub-board printf format information for a message group.
@@ -18770,21 +18963,27 @@ function DigDistMsgReader_RecalcMsgListWidthsAndFormatStrs(pMsgNumLen)
 		this.sMsgListHdrFormatStr += "\r\n";
 }
 
-// For the DigDistMessageReader class: Writes a temporary error message at the key help line
-// for lightbar mode.
+// For the DigDistMessageReader class: Writes a temporary message at the key help line which is
+// used for lightbar mode.
 //
 // Parameters:
-//  pErrorMsg: The error message to write
+//  pMsg: The message to write
+//  pAttrStr: A string of attribute codes to use for the string
+//  pWaitTimeMS: Optional - The amount of time (in milliseconds) to show the message.
+//               Defaults to ERROR_WAIT_MS.
 //  pHelpLineRefreshDef: Optional - Specifies which help line to refresh on the screen
 //                       (i.e., REFRESH_MSG_AREA_CHG_LIGHTBAR_HELP_LINE)
-function DigDistMsgReader_WriteLightbarKeyHelpErrorMsg(pErrorMsg, pLineRefreshDef)
+function DigDistMsgReader_WriteLightbarKeyHelpMsg(pMsg, pAttrStr, pWaitTimeMS, pLineRefreshDef)
 {
 	console.gotoxy(1, console.screen_rows);
 	console.cleartoeol("\x01n");
 	console.gotoxy(1, console.screen_rows);
-	console.print("\x01y\x01h" + pErrorMsg + "\x01n");
-	mswait(ERROR_WAIT_MS);
-	var helpLineRefreshDef = (typeof(pHelpLineRefreshDef) == "number" ? pHelpLineRefreshDef : -1);
+	console.print(pAttrStr + pMsg + "\x01n");
+	var msgDisplayTime = ERROR_WAIT_MS;
+	if (typeof(pWaitTimeMS) === "number" && pWaitTimeMS > 0)
+		msgDisplayTime = pWaitTimeMS;
+	mswait(msgDisplayTime);
+	var helpLineRefreshDef = (typeof(pLineRefreshDef) == "number" ? pLineRefreshDef : -1);
 	if (helpLineRefreshDef == REFRESH_MSG_AREA_CHG_LIGHTBAR_HELP_LINE)
 		this.WriteChgMsgAreaKeysHelpLine();
 }
@@ -18989,8 +19188,14 @@ function getDefaultColors()
 		// Prompt for continuing to list messages after reading a message
 		afterReadMsg_ListMorePromptColor: "\x01n\x01c",
 
-		// Help screen text color
-		tradInterfaceHelpScreenColor: "\x01n\x01h\x01w",
+		// Traditional interface help screen text color
+		tradInterfaceHelpScreenColor: "\x01n\x01c",
+
+		// Help window border color (where applicable)
+		helpWinBorderColor: "\x01n\x01r",
+
+		// Scrolling window help text color
+		scrollingWinHelpTextColor: "\x01n\x01c",
 
 		// Colors for choosing a message group & sub-board
 		areaChooserMsgAreaNumColor: "\x01n\x01w\x01h",
@@ -25082,6 +25287,273 @@ function genPathedFilename(pDefaultDir, pFilename)
 	return outFilename;
 }
 
+// Converts one of the sub-board sort options to a descriptive string
+function subBoardSortOptionToStr(pSortOption)
+{
+	var optionStr = "None (as configured in the system)";
+	switch (pSortOption)
+	{
+		case SUB_BOARD_SORT_NONE:
+			optionStr = "None (as configured in the system)";
+			break;
+		case SUB_BOARD_SORT_ALPHABETICAL:
+			optionStr = "Alphabetical";
+			break;
+		case SUB_BOARD_SORT_LATEST_MSG_DATE_OLDEST_FIRST:
+			optionStr = "Latest message date (oldest first)";
+			break;
+		case SUB_BOARD_SORT_LATEST_MSG_DATE_NEWEST_FIRST:
+			optionStr = "Latest message date (newest first)";
+			break;
+	}
+	return optionStr;
+}
+
+
+// Constructs & displays a frame with a border around it, and performs a user input loop
+// until the user quits out of the input loop.
+//
+// Parameters:
+//  pFrameX: The X coordinate of the upper-left corner of the frame (including border)
+//  pFrameY: The Y coordinate of the upper-left corner of the frame (including border)
+//  pFrameWidth: The width of the frame (including border)
+//  pFrameHeight: The height of the frame (including border)
+//  pBorderColor: The attribute codes for the border color
+//  pFrameTitle: The title (text) to use in the frame border
+//  pTitleColor: Optional string - The attribute codes for the color to use for the frame title
+//  pFrameContents: The contents to display in the frame
+//  pAdditionalQuitKeys: Optional - A string containing additional keys to quit the
+//                       input loop.  This is case-sensitive.
+//
+// Return value: The last keypress/input from the user
+function displayBorderedFrameAndDoInputLoop(pFrameX, pFrameY, pFrameWidth, pFrameHeight, pBorderColor, pFrameTitle, pTitleColor, pFrameContents, pAdditionalQuitKeys)
+{
+	if (typeof(pFrameX) !== "number" || typeof(pFrameY) !== "number" || typeof(pFrameWidth) !== "number" || typeof(pFrameHeight) !== "number")
+		return;
+
+	// Display the border for the frame
+	var keyHelpStr = "\x01n\x01c\x01hQ\x01b/\x01cEnter\x01b/\x01cESC\x01y: \x01gClose\x01b";
+	var scrollLoopNavHelp = "\x01c\x01hUp\x01b/\x01cDn\x01b/\x01cHome\x01b/\x01cEnd\x01b/\x01cPgup\x01b/\x01cPgDn\x01y: \x01gNav";
+	if (console.screen_columns >= 80)
+		keyHelpStr += ", " + scrollLoopNavHelp;
+	var borderColor = (typeof(pBorderColor) === "string" ? pBorderColor : "\x01r");
+	drawBorder(pFrameX, pFrameY, pFrameWidth, pFrameHeight, borderColor, "double", pFrameTitle, pTitleColor, keyHelpStr);
+
+	// Construct the frame window for the file info
+	// Create a Frame here with the full filename, extended description, etc.
+	var frameX = pFrameX + 1;
+	var frameY = pFrameY + 1;
+	var frameWidth = pFrameWidth - 2;
+	var frameHeight = pFrameHeight - 2;
+	var frameObj = new Frame(frameX, frameY, frameWidth, frameHeight, BG_BLACK);
+	frameObj.attr &=~ HIGH;
+	frameObj.v_scroll = true;
+	frameObj.h_scroll = false;
+	frameObj.scrollbars = true;
+	var scrollbarObj = new ScrollBar(frameObj, {bg: BG_BLACK, fg: LIGHTGRAY, orientation: "vertical", autohide: false});
+	// Put the file info string in the frame window, then start the
+	// user input loop for the frame
+	frameObj.putmsg(pFrameContents, "\x01n");
+	var lastUserInput = doFrameInputLoop(frameObj, scrollbarObj, pFrameContents, pAdditionalQuitKeys);
+	//infoFrame.bottom();
+
+	return lastUserInput;
+}
+
+// Displays a Frame object and handles the input loop for navigation until
+// the user presses Q, Enter, or ESC To quit the input loop
+//
+// Parameters:
+//  pFrame: The Frame object
+//  pScrollbar: The Scrollbar object for the Frame
+//  pFrameContentStr: The string content that was added to the Frame
+//  pAdditionalQuitKeys: Optional - A string containing additional keys to quit the
+//                       input loop.  This is case-sensitive.
+//
+// Return value: The last keypress/input from the user
+function doFrameInputLoop(pFrame, pScrollbar, pFrameContentStr, pAdditionalQuitKeys)
+{
+	var checkAdditionalQuitKeys = (typeof(pAdditionalQuitKeys) === "string" && pAdditionalQuitKeys.length > 0);
+
+	// Input loop for the frame to let the user scroll it
+	var frameContentTopYOffset = 0;
+	//var maxFrameYOffset = pFrameContentStr.split("\r\n").length - pFrame.height;
+	var maxFrameYOffset = countOccurrencesInStr(pFrameContentStr, "\r\n") - pFrame.height;
+	if (maxFrameYOffset < 0) maxFrameYOffset = 0;
+	var userInput = "";
+	var continueOn = true;
+	do
+	{
+		pFrame.scrollTo(0, frameContentTopYOffset);
+		pFrame.invalidate();
+		pScrollbar.cycle();
+		pFrame.cycle();
+		pFrame.draw();
+		// Note: getKeyWithESCChars() is defined in dd_lightbar_menu.js.
+		userInput = getKeyWithESCChars(K_NOECHO|K_NOSPIN|K_NOCRLF, 30000).toUpperCase();
+		if (userInput == KEY_UP)
+		{
+			if (frameContentTopYOffset > 0)
+				--frameContentTopYOffset;
+		}
+		else if (userInput == KEY_DOWN)
+		{
+			if (frameContentTopYOffset < maxFrameYOffset)
+				++frameContentTopYOffset;
+		}
+		else if (userInput == KEY_PAGEUP)
+		{
+			frameContentTopYOffset -= pFrame.height;
+			if (frameContentTopYOffset < 0)
+				frameContentTopYOffset = 0;
+		}
+		else if (userInput == KEY_PAGEDN)
+		{
+			frameContentTopYOffset += pFrame.height;
+			if (frameContentTopYOffset > maxFrameYOffset)
+				frameContentTopYOffset = maxFrameYOffset;
+		}
+		else if (userInput == KEY_HOME)
+			frameContentTopYOffset = 0;
+		else if (userInput == KEY_END)
+			frameContentTopYOffset = maxFrameYOffset;
+
+		// Check for whether to continue the input loop
+		continueOn = (userInput != "Q" && userInput != KEY_ENTER && userInput != KEY_ESC);
+		// If the additional quit keys does not contain the user's keypress, then continue
+		// the input loop.
+		// In other words, if the additional quit keys includes the user's keypress, then
+		// don't continue.
+		if (continueOn && checkAdditionalQuitKeys)
+			continueOn = (pAdditionalQuitKeys.indexOf(userInput) < 0);
+	} while (continueOn);
+
+	return userInput;
+}
+
+// Draws a border
+//
+// Parameters:
+//  pX: The X location of the upper left corner
+//  pY: The Y location of the upper left corner
+//  pWidth: The width of the box
+//  pHeight: The height of the box
+//  pColor: A string containing color/attribute codes for the border characters
+//  pLineStyle: A string specifying the border character style, either "single" or "double"
+//  pTitle: Optional - A string specifying title text for the top border
+//  pTitleColor: Optional - Attribute codes for the color to use for the title text
+//  pBottomBorderText: Optional - A string specifying text to include in the bottom border
+function drawBorder(pX, pY, pWidth, pHeight, pColor, pLineStyle, pTitle, pTitleColor, pBottomBorderText)
+{
+	if (typeof(pX) !== "number" || typeof(pY) !== "number" || typeof(pWidth) !== "number" || typeof(pHeight) !== "number")
+		return;
+	if (typeof(pColor) !== "string")
+		return;
+
+	var borderChars = {
+		UL: UPPER_LEFT_SINGLE,
+		UR: UPPER_RIGHT_SINGLE,
+		LL: LOWER_LEFT_SINGLE,
+		LR: LOWER_RIGHT_SINGLE,
+		preText: RIGHT_T_SINGLE,
+		postText: LEFT_T_SINGLE,
+		horiz: HORIZONTAL_SINGLE,
+		vert: VERTICAL_SINGLE
+	};
+	if (typeof(pLineStyle) === "string" && pLineStyle.toUpperCase() == "DOUBLE")
+	{
+		borderChars.UL = UPPER_LEFT_DOUBLE;
+		borderChars.UR = UPPER_RIGHT_DOUBLE;
+		borderChars.LL = LOWER_LEFT_DOUBLE;
+		borderChars.LR = LOWER_RIGHT_DOUBLE;
+		borderChars.preText = RIGHT_T_DOUBLE;
+		borderChars.postText = LEFT_T_DOUBLE
+		borderChars.horiz = HORIZONTAL_DOUBLE;
+		borderChars.vert = VERTICAL_DOUBLE;
+	}
+
+	// Top border
+	console.gotoxy(pX, pY);
+	console.print("\x01n" + pColor);
+	console.print(borderChars.UL);
+	var innerWidth = pWidth - 2;
+	// Include the title text in the top border, if there is any specified
+	var titleLen = console.strlen(pTitle);
+	if (typeof(pTitle) === "string" && titleLen > 0)
+	{
+		if (titleLen > pWidth - 4)
+			titleLen = pWidth - 4;
+		innerWidth -= titleLen;
+		innerWidth -= 2; // ?? Correctional
+		var titleWithoutAttrs = strip_ctrl(pTitle);
+		// Note: substrWithAttrCodes() is defined in dd_lightbar_menu.js
+		var titleText = titleWithoutAttrs;
+		if (typeof(pTitleColor) === "string")
+			titleText = "\x01n" + pTitleColor + titleWithoutAttrs;
+		console.print(borderChars.preText + "\x01n" + titleWithoutAttrs.substr(0, titleLen) +
+		              "\x01n" + pColor + borderChars.postText);
+		if (innerWidth > 0)
+			console.print(pColor);
+	}
+	for (var i = 0; i < innerWidth; ++i)
+		console.print(borderChars.horiz);
+	console.print(borderChars.UR);
+	// Side borders
+	var rightCol = pX + pWidth - 1;
+	var endScreenRow = pY + pHeight - 1;
+	for (var screenRow = pY + 1; screenRow < endScreenRow; ++screenRow)
+	{
+		console.gotoxy(pX, screenRow);
+		console.print(borderChars.vert);
+		console.gotoxy(rightCol, screenRow);
+		console.print(borderChars.vert);
+	}
+	// Bottom border
+	console.gotoxy(pX, endScreenRow);
+	console.print(borderChars.LL);
+	innerWidth = pWidth - 2;
+	// Include the bottom border text in the top border, if there is any specified
+	if (typeof(pBottomBorderText) === "string" && pBottomBorderText.length > 0)
+	{
+		var textLen = strip_ctrl(pBottomBorderText).length;
+		if (textLen > pWidth - 4)
+			textLen = pWidth - 4;
+		innerWidth -= textLen;
+		innerWidth -= 2; // ?? Correctional
+		// Note: substrWithAttrCodes() is defined in dd_lightbar_menu.js
+		console.print(borderChars.preText + "\x01n" + substrWithAttrCodes(pBottomBorderText, 0, textLen) +
+		              "\x01n" + pColor + borderChars.postText);
+		if (innerWidth > 0)
+			console.print(pColor);
+	}
+	for (var i = 0; i < innerWidth; ++i)
+		console.print(borderChars.horiz);
+	console.print(borderChars.LR);
+}
+
+// Counts the number of occurrences of a substring within a string
+//
+// Parameters:
+//  pStr: The string to count occurences in
+//  pSubstr: The string to look for within pStr
+//
+// Return value: The number of occurrences of pSubstr found in pStr
+function countOccurrencesInStr(pStr, pSubstr)
+{
+	if (typeof(pStr) !== "string" || typeof(pSubstr) !== "string") return 0;
+	if (pStr.length == 0 || pSubstr.length == 0) return 0;
+
+	var count = 0;
+	var strIdx = pStr.indexOf(pSubstr);
+	while (strIdx > -1 && strIdx < pStr.length)
+	{
+		++count;
+		strIdx = pStr.indexOf(pSubstr, strIdx+1);
+	}
+	return count;
+}
+
+
 ///////////////////////////////////////////////////////////////////////////////////
 
 // For debugging: Writes some text on the screen at a given location with a given pause.
diff --git a/xtrn/DDMsgReader/DefaultTheme.cfg b/xtrn/DDMsgReader/DefaultTheme.cfg
index 2e82a147d34c5b9f1c0ef6d0ec7ce0552104a0c1..70dd7633a07654ac46cac0287afcbc81e58997f8 100644
--- a/xtrn/DDMsgReader/DefaultTheme.cfg
+++ b/xtrn/DDMsgReader/DefaultTheme.cfg
@@ -274,6 +274,12 @@ editMsgNumPromptText=\x01n\x01cNumber of the message to be edited (or \x01hENTER
 ; Traditional/lightbar interface help screen text color
 tradInterfaceHelpScreenColor=nc
 
+; Scrolling help window border color
+helpWinBorderColor=nr
+
+; Scrolling help window text color
+scrollingWinHelpTextColor=nc
+
 ; -------- Colors for message header/kludge lines --------
 hdrLineLabelColor=nc
 hdrLineValueColor=nbh
diff --git a/xtrn/DDMsgReader/ddmr_cfg.js b/xtrn/DDMsgReader/ddmr_cfg.js
index ee0c55b50df744bbd83492cf28881eb633b564b5..0bc9dae169c96f3e5811e0fbba569cf786ad950d 100644
--- a/xtrn/DDMsgReader/ddmr_cfg.js
+++ b/xtrn/DDMsgReader/ddmr_cfg.js
@@ -5,7 +5,7 @@
 // If you have DDMsgReader in a directory other than xtrn/DDMsgReader, then the changes to
 // DDMsgReader.cfg will be saved in that directory (assuming you're running ddmr_cfg.js from
 // that same directory).
-// Currently for DDMsgReader 1.96.
+// Currently for DDMsgReader 1.96a.
 //
 // If you're running DDMsgReader from xtrn/DDMsgReader (the standard location) and you want
 // to save the configuration file there (rather than sbbs/mods), you can use one of the
@@ -18,7 +18,7 @@ require("sbbsdefs.js", "P_NONE");
 require("uifcdefs.js", "UIFC_INMSG");
 
 
-if (!uifc.init("DigDist. Message Reader 1.96 Configurator"))
+if (!uifc.init("DigDist. Message Reader 1.96a Configurator"))
 {
 	print("Failed to initialize uifc");
 	exit(1);
diff --git a/xtrn/DDMsgReader/readme.txt b/xtrn/DDMsgReader/readme.txt
index 2863d078e0199ab7f180adc82915e784e1a35d27..e4f580a30e93a513f1588c9344aca8a5f0cbeb24 100644
--- a/xtrn/DDMsgReader/readme.txt
+++ b/xtrn/DDMsgReader/readme.txt
@@ -1,6 +1,6 @@
                       Digital Distortion Message Reader
-                                 Version 1.96
-                           Release date: 2024-10-26
+                                 Version 1.96a
+                           Release date: 2024-11-02
 
                                      by
 
@@ -1044,7 +1044,13 @@ afterReadMsg_ListMorePromptColor     Color for the text used for asking the user
 tradInterfaceHelpScreenColor         Color for the text used in the traditional
                                      user interface message list help screen
 
-areaChooserMsgAreaNumColor            Color for the message area numbers when
+helpWinBorderColor                   Color for scrolling help windows (where
+                                     applicable)
+
+scrollingWinHelpTextColor            Color for help text in scrolling windows
+                                     (where applicable)
+
+areaChooserMsgAreaNumColor           Color for the message area numbers when
                                      choosing a different message area
 
 areaChooserMsgAreaDescColor          Color for the message area descriptions
diff --git a/xtrn/DDMsgReader/revision_history.txt b/xtrn/DDMsgReader/revision_history.txt
index 4467c3411ef89bf769eacb38d0f7147f84f3c818..8a8e703ae786ec237e1ea4d5d672573c63e23920 100644
--- a/xtrn/DDMsgReader/revision_history.txt
+++ b/xtrn/DDMsgReader/revision_history.txt
@@ -5,6 +5,14 @@ Revision History (change log)
 =============================
 Version  Date         Description
 -------  ----         -----------
+1.96a    2024-11-02   When changing to another sub-board, the user can now cycle
+                      through the sort options with the [ and ] keys. Also,
+                      updated the change sub-board help to show in a scrollable
+                      window for users with ANSI terminals, rather than simply
+                      displaying the help with a pause at the end. Other help
+                      screens could potentially be shown this way too.
+                      New theme configuration options: helpWinBorderColor and
+                      scrollingWinHelpTextColor
 1.96     2024-10-26   New feature: sub-board sorting for changing sub-boards
                       (and users can change their sorting option).