Quantcast

- On Skin color allow border (def) or none

urnati [01-04-26 - 17:27]
- On Skin color allow border (def) or none
- Post twekas and fixes
- UI Add server resets
Filename
Titan/Titan.lua
Titan/TitanConfig.lua
Titan/TitanVariables.lua
TitanPost/TitanPost.lua
TitanUI/Tools.lua
diff --git a/Titan/Titan.lua b/Titan/Titan.lua
index da6ca44..8b051e2 100644
--- a/Titan/Titan.lua
+++ b/Titan/Titan.lua
@@ -920,23 +920,35 @@ SLASH_TitanPanel2 = "/titan";
 ---@param tex table Texture frame to set
 ---@param color table Color - RBGA
 local function Set_Color(frame, tex, color)
+	-- Jan 2026 : Put gorder on option
+	local edge = TitanBarDataVars[frame].color_border
+	local edge_file = ""
+	if edge then
+		edge_file = "Interface\\Glues\\Common\\TextPanel-Border"
+	else
+		edge_file = ""
+	end
+
 	--[[
 print("_Set bar color"
+.." '"..tostring(frame).."'"
 .." "..tostring(TitanBarData[frame].tex_name)..""
 --.." "..tostring(tex:GetName())..""
 .." "..tostring(format("%0.1f", color.r))..""
 .." "..tostring(format("%0.1f", color.g))..""
 .." "..tostring(format("%0.1f", color.b))..""
 .." "..tostring(format("%0.1f", color.alpha))..""
+.." "..tostring(edge)..""
 )
 --]]
+
 	_G[frame]:SetBackdrop({
 		bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
 		--		edgeFile="Interface\\Tooltips\\UI-Tooltip-Border",
 		--		edgeFile="Interface\\DialogFrame\\UI-DialogBox-Gold-Border",
-		edgeFile = "Interface\\Glues\\Common\\TextPanel-Border",
+		edgeFile = edge_file,
 		tile = true,
-		tileEdge = true,
+		tileEdge = edge,
 		--		insets = { left = 1, right = 1, top = 1, bottom = 1 },
 		tileSize = 8,
 		edgeSize = 8,
@@ -946,8 +958,9 @@ print("_Set bar color"
 		TOOLTIP_DEFAULT_COLOR.r,
 		TOOLTIP_DEFAULT_COLOR.g,
 		TOOLTIP_DEFAULT_COLOR.b,
-		color.alpha); -- 2024 AUg : Border will use the color alpha
-	_G[frame]:SetBackdropColor(
+		color.alpha); -- 2024 Aug : Border will use the color alpha
+
+		_G[frame]:SetBackdropColor(
 		color.r,
 		color.g,
 		color.b,
@@ -1037,11 +1050,14 @@ print("_Tex"
 )
 --]]
 	-- Use the texture / skin per user selectable options
+--[[
 	if TitanBarDataVars["Global"].texure == Titan_Global.SKIN then
 		Set_Skin(frame, titanTexture, TitanBarDataVars["Global"].skin) -- tex_path = TitanPanelGetVar("TexturePath")
 	elseif TitanBarDataVars["Global"].texure == Titan_Global.COLOR then
 		Set_Color(frame, titanTexture, TitanBarDataVars["Global"].color)
-	elseif TitanBarDataVars[frame].texure == Titan_Global.SKIN then
+	else
+--]]
+	if TitanBarDataVars[frame].texure == Titan_Global.SKIN then
 		Set_Skin(frame, titanTexture, TitanBarDataVars[frame].skin)
 	elseif TitanBarDataVars[frame].texure == Titan_Global.COLOR then
 		Set_Color(frame, titanTexture, TitanBarDataVars[frame].color)
diff --git a/Titan/TitanConfig.lua b/Titan/TitanConfig.lua
index f0179dc..904b13c 100644
--- a/Titan/TitanConfig.lua
+++ b/Titan/TitanConfig.lua
@@ -403,7 +403,6 @@ local function Format_coord(coord)
 	return (tostring(format("%0.2f", coord)))
 end

-local color_trans = 0
 --[[ local
 NAME: TitanUpdateConfigBars
 DESC: Allow the user to control each Titan bar.
@@ -569,7 +568,7 @@ local function TitanUpdateConfigBars(t, pos)
 				TitanPanelBarButton_DisplayBarsWanted("Bar reset to default position - " .. tostring(info[1]))
 			end,
 		}
-
+--[[
 		-- ======
 		-- Background group
 		position = position + 1 -- global background
@@ -596,13 +595,14 @@ local function TitanUpdateConfigBars(t, pos)
 				},
 			},
 		}
-
+--]]
 		position = position + 1 -- background
 		args[v.name].args.background = {
 			name = BACKGROUND,
 			type = "group",
 			inline = true,
 			order = position,
+			--[[
 			hidden = function(info)
 				local hide = false
 				if TitanBarDataVars["Global"].texure == Titan_Global.NONE then
@@ -612,6 +612,7 @@ local function TitanUpdateConfigBars(t, pos)
 				end
 				return hide
 			end,
+			--]]
 			args = {
 				settextousebar = {
 					name = "",
@@ -649,15 +650,9 @@ local function TitanUpdateConfigBars(t, pos)
 							order = 320,
 							width = "full",
 						},
-						colorspacer = {
-							order = 330,
-							type = "description",
-							width = "full",
-							name = " ",
-						},
 						colorselect = {
 							type = "color",
-							width = "Full",
+							width = "normal",
 							name = L["TITAN_PANEL_BAR_COLOR"],
 							order = 340,
 							--				disabled = (v.vert == TITAN_TOP or v.vert == TITAN_BOTTOM),
@@ -678,9 +673,24 @@ local function TitanUpdateConfigBars(t, pos)
 								TitanBarDataVars[frame_str].color.b     = b
 								TitanBarDataVars[frame_str].color.alpha = a
 								TitanPanel_SetBarTexture(frame_str)
-								color_trans = a
 							end,
 						},
+						showborder = {
+							type = "toggle",
+							width = "normal",
+							name = "Show Border", --L["TITAN_PANEL_MENU_DISPLAY_BAR"],
+							order = 350,
+							get = function(info)
+								local frame_str = TitanVariables_GetFrameName(info[1])
+								return TitanBarDataVars[frame_str].color_border
+							end,
+							set = function(info, val)
+								local frame_str = TitanVariables_GetFrameName(info[1])
+								TitanBarDataVars[frame_str].color_border = not TitanBarDataVars[frame_str].color_border
+								TitanPanel_SetBarTexture(frame_str)
+--								TitanUpdateConfigBars(optionsBars.args, 1000)
+							end,
+						}
 					},
 				},
 				skingroup = {
diff --git a/Titan/TitanVariables.lua b/Titan/TitanVariables.lua
index d1be1c6..93c677c 100644
--- a/Titan/TitanVariables.lua
+++ b/Titan/TitanVariables.lua
@@ -323,6 +323,7 @@ TitanSkinsPathEnd = "\\"
 ---Global is an additional index used if the user wants all Bars to be the same skin or color.

 TitanBarVarsDefaults = {
+	--[[
 	["Global"] = -- holds 'global' user settings; NOT for use in the frame loop!
 	{
 		skin = { path = TitanSkinsDefaultPath, alpha = 0.7 },
@@ -330,12 +331,14 @@ TitanBarVarsDefaults = {
 		texure = Titan_Global.NONE, -- Titan_Global.NONE or Titan_Global.SKIN or Titan_Global.COLOR
 		hide_in_combat = false,
 	},
+	--]]
 	[TITAN_PANEL_DISPLAY_PREFIX .. "Bar"] = {
 		off_x = 0,
 		off_y = 0,
 		off_w = x_max,
 		skin = { path = TitanSkinsDefaultPath, alpha = 0.7 },
 		color = { r = 1.0, g = .5, b = 1.0, alpha = 1.0 },
+		color_border = true,
 		texure = Titan_Global.SKIN, -- or Titan_Global.COLOR
 		show = true,
 		auto_hide = false,
@@ -349,6 +352,7 @@ TitanBarVarsDefaults = {
 		off_w = x_max,
 		skin = { path = TitanSkinsDefaultPath, alpha = 0.7 },
 		color = { r = 1.0, g = .5, b = 1.0, alpha = 1.0 },
+		color_border = true,
 		texure = Titan_Global.SKIN,
 		show = false,
 		auto_hide = false,
@@ -362,6 +366,7 @@ TitanBarVarsDefaults = {
 		off_w = x_max,
 		skin = { path = TitanSkinsDefaultPath, alpha = 0.7 },
 		color = { r = 1.0, g = .5, b = 1.0, alpha = 1.0 },
+		color_border = true,
 		texure = Titan_Global.SKIN,
 		show = false,
 		auto_hide = false,
@@ -375,6 +380,7 @@ TitanBarVarsDefaults = {
 		off_w = x_max,
 		skin = { path = TitanSkinsDefaultPath, alpha = 0.7 },
 		color = { r = 1.0, g = .5, b = 1.0, alpha = 1.0 },
+		color_border = true,
 		texure = Titan_Global.SKIN,
 		show = false,
 		auto_hide = false,
@@ -388,6 +394,7 @@ TitanBarVarsDefaults = {
 		off_w = SHORT_WIDTH,
 		skin = { path = TitanSkinsDefaultPath, alpha = 0.7 },
 		color = { r = 1.0, g = .5, b = 1.0, alpha = 1.0 },
+		color_border = true,
 		texure = Titan_Global.SKIN,
 		show = false,
 		auto_hide = false,
@@ -401,6 +408,7 @@ TitanBarVarsDefaults = {
 		off_w = SHORT_WIDTH,
 		skin = { path = TitanSkinsDefaultPath, alpha = 0.7 },
 		color = { r = 1.0, g = .5, b = 1.0, alpha = 1.0 },
+		color_border = true,
 		texure = Titan_Global.SKIN,
 		show = false,
 		auto_hide = false,
@@ -414,6 +422,7 @@ TitanBarVarsDefaults = {
 		off_w = SHORT_WIDTH,
 		skin = { path = TitanSkinsDefaultPath, alpha = 0.7 },
 		color = { r = 1.0, g = .5, b = 1.0, alpha = 1.0 },
+		color_border = true,
 		texure = Titan_Global.SKIN,
 		show = false,
 		auto_hide = false,
@@ -427,6 +436,7 @@ TitanBarVarsDefaults = {
 		off_w = SHORT_WIDTH,
 		skin = { path = TitanSkinsDefaultPath, alpha = 0.7 },
 		color = { r = 1.0, g = .5, b = 1.0, alpha = 1.0 },
+		color_border = true,
 		texure = Titan_Global.SKIN,
 		show = false,
 		auto_hide = false,
@@ -440,6 +450,7 @@ TitanBarVarsDefaults = {
 		off_w = SHORT_WIDTH,
 		skin = { path = TitanSkinsDefaultPath, alpha = 0.7 },
 		color = { r = 1.0, g = .5, b = 1.0, alpha = 1.0 },
+		color_border = true,
 		texure = Titan_Global.SKIN,
 		show = false,
 		auto_hide = false,
@@ -453,6 +464,7 @@ TitanBarVarsDefaults = {
 		off_w = SHORT_WIDTH,
 		skin = { path = TitanSkinsDefaultPath, alpha = 0.7 },
 		color = { r = 1.0, g = .5, b = 1.0, alpha = 1.0 },
+		color_border = true,
 		texure = Titan_Global.SKIN,
 		show = false,
 		auto_hide = false,
@@ -466,6 +478,7 @@ TitanBarVarsDefaults = {
 		off_w = SHORT_WIDTH,
 		skin = { path = TitanSkinsDefaultPath, alpha = 0.7 },
 		color = { r = 1.0, g = .5, b = 1.0, alpha = 1.0 },
+		color_border = true,
 		texure = Titan_Global.SKIN,
 		show = false,
 		auto_hide = false,
@@ -479,6 +492,7 @@ TitanBarVarsDefaults = {
 		off_w = SHORT_WIDTH,
 		skin = { path = TitanSkinsDefaultPath, alpha = 0.7 },
 		color = { r = 1.0, g = .5, b = 1.0, alpha = 1.0 },
+		color_border = true,
 		texure = Titan_Global.SKIN,
 		show = false,
 		auto_hide = false,
@@ -492,6 +506,7 @@ TitanBarVarsDefaults = {
 		off_w = SHORT_WIDTH,
 		skin = { path = TitanSkinsDefaultPath, alpha = 0.7 },
 		color = { r = 1.0, g = .5, b = 1.0, alpha = 1.0 },
+		color_border = true,
 		texure = Titan_Global.SKIN,
 		show = false,
 		auto_hide = false,
@@ -506,6 +521,7 @@ TitanBarVarsDefaults = {
 		skin = { path = TitanSkinsDefaultPath, alpha = 0.7 },
 		color = { r = 1.0, g = .5, b = 1.0, alpha = 1.0 },
 		texure = Titan_Global.SKIN,
+		color_border = true,
 		show = false,
 		auto_hide = false,
 		align = TITAN_PANEL_BUTTONS_ALIGN_LEFT, -- TITAN_PANEL_BUTTONS_ALIGN_CENTER
@@ -913,6 +929,15 @@ local function Titan_SyncAdjList()
 	end
 end

+local function AdjBarVars(to_profile)
+		local BV = TitanSettings.Players[to_profile].BarVars
+		for idx, v in pairs(TitanBarData) do
+			if v.color_border == nil then -- NEW Jan 2026
+				BV[idx].color_border = true
+			end
+		end
+end
+
 ---local Set the Titan bar settings of the given profile from saved variables
 ---@param to_profile string
 --- If no profile found, use Titan defaults
@@ -955,6 +980,9 @@ local function Set_bar_vars(to_profile)
 			.. " " .. tostring(to_profile) .. ""
 		Titan_Debug.Out('titan', 'profile', str)
 	end
+
+	-- New 2026 : Add any new BarVar or delete removed fields to ALL known profiles
+	AdjBarVars(to_profile)
 end

 local function Check_Titan_settings()
diff --git a/TitanPost/TitanPost.lua b/TitanPost/TitanPost.lua
index cca38a2..f06701a 100644
--- a/TitanPost/TitanPost.lua
+++ b/TitanPost/TitanPost.lua
@@ -15,17 +15,19 @@ local FolderPre = "Interface\\AddOns\\TitanPost\\artwork\\"
 -- Local variables
 local L = LibStub("AceLocale-3.0"):GetLocale("Titan", true)
 local AceConfigDialog = LibStub("AceConfigDialog-3.0")
+local AceTimer = LibStub("AceTimer-3.0")
+local AceHook = LibStub("AceHook-3.0")

 local mailbox = {} -- default on load; store display info here
 mailbox.opened = false
+mailbox.open_now = false
 mailbox.new = 0
 mailbox.expiry_text = ""
 mailbox.expiry_num = 0
-
-local enter_or_reload = true
+mailbox.ignore_next_pending = false

 local player = TitanUtils_GetPlayer()
-local debug = false;
+local debug = true;
 local SECONDS_PER_DAY = 24 * 60 * 60
 local SECONDS_PER_HOUR = 60 * 60
 local MAX_MAIL_DAYS = 30
@@ -34,12 +36,12 @@ local expiry_warn_days = 7
 local expiry_err_days = 2

 local DAYS_REM = "Days Remaining"
-local MAIL_OPENED = "Mail Opened"
+local MAIL_OPENED = "Opened"
 local CHAR = "Character"

-local NEW_PRE = " + "
-local EXP_PRE = " ! "
-local READ_TT_PRE = "About : "
+local READ_PRE = " ?? "
+local NEW_PRE = " ++ "
+local EXP_PRE = " !! "
 local READ_TT_POST = " : About read / total "
 local NEW_TT_POST = " : New Mail this session "
 local EXP_TT_POST = " : Characters with expiring Mail "
@@ -52,7 +54,10 @@ TitanPost.mailbox = mailbox

 function Debug(str)
 	if (debug) then
-		DEFAULT_CHAT_FRAME:AddMessage("TitanPost " .. str);
+		DEFAULT_CHAT_FRAME:AddMessage("TitanPost "
+			.. date("%H:%M:%S ", _G.time())
+			.. str
+		);
 	end
 end

@@ -81,18 +86,21 @@ local function FormatTS(time_sec, with_time)
 	return str
 end

+---Start / stop the timer to check expiry
+---@param action string 'start' | 'stop'
+local function ExpiryTimer(action) end -- real declaration later...
+
 ---Format an expiry text for a single toon
 ---@param toon_name string
----@param next_expiry number timestamp
----@param last_update number timestamp
+---@param toon_info table toon
 ---@return string
-local function ShowExpiry(toon_name, next_expiry, last_update)
+local function ShowExpiry(toon_name, toon_info)
 	local now = _G.time()
 	local res = ""
 	local use_color = true -- TitanGetVar(TITAN_POST_ID, "ShowColoredText")

-	local days = math.floor((next_expiry - now) / SECONDS_PER_DAY)
-	local last = FormatTS(last_update, true)
+	local days = math.floor((toon_info.nextExpiry - now) / SECONDS_PER_DAY)
+	local last = toon_info.lastUpdate_str
 	local estr = toon_name
 		.. " : " .. days
 		.. " : " .. last
@@ -141,24 +149,25 @@ local function CheckExpiry()

 	str = "CheckExpiry"
 		.. " @ " .. FormatTS(expiry_check, true)
-		.. " " .. tostring(expiry_check) .. ""
+		.. " (" .. tostring(expiry_check) .. ")"
 	Debug(str)

 	for toon_name, characterList in pairs(TitanPostDB) do
 		str = ".CheckExpiry"
 			.. " " .. tostring(toon_name) .. ""
 			.. " # " .. tostring(characterList.mailCount) .. ""
-		Debug(str)
+		--		Debug(str)
 		if (characterList.mailCount > 0) then
 			str = "...CheckExpiry"
+				.. " [" .. tostring(characterList.nextExpiry_str) .. "]"
 				.. " " .. tostring(characterList.nextExpiry) .. ""
 				.. " < " .. tostring(expiry_check) .. ""
 				.. " = " .. tostring((characterList.nextExpiry < expiry_check)) .. ""
-			Debug(str)
+			--			Debug(str)
 			if (characterList.nextExpiry < expiry_check) then -- add to list for user
 				has_expiry = true

-				local estr = ShowExpiry(toon_name, characterList.nextExpiry, characterList.lastUpdate)
+				local estr = ShowExpiry(toon_name, characterList)

 				exp_str = exp_str .. estr
 				expiry_toons = expiry_toons + 1 -- count the number of toons with expiring mail
@@ -177,7 +186,9 @@ local function CheckExpiry()
 		-- still time
 	end

-	str = "CheckExpiry"
+	ExpiryTimer('start')
+
+	str = "=CheckExpiry"
 		.. " " .. tostring(expiry_toons) .. ""
 		.. " \n" .. tostring(res) .. ""
 	Debug(str)
@@ -185,6 +196,35 @@ local function CheckExpiry()
 	return expiry_toons, res
 end

+local expiry_timer = nil
+function ExpiryTimer(action) -- prior declaration
+	if action == 'start' then
+		-- stop current, just in case for sanity
+		if expiry_timer == nil then
+			-- no timer
+		else
+			AceTimer.CancelTimer(_G[TITAN_BUTTON], expiry_timer)
+		end
+		local time_int = TitanGetVar(TITAN_POST_ID, "ExpiryTimer")
+		expiry_timer =
+			AceTimer.ScheduleRepeatingTimer(_G[TITAN_BUTTON], CheckExpiry, time_int * 60) -- in seconds
+		local str = "Expiry next check in"
+			.. " " .. tostring(time_int) .. " minutes"
+		Debug(str)
+	elseif action == 'stop' then
+		if expiry_timer == nil then
+			-- no timer
+		else
+			AceTimer.CancelTimer(_G[TITAN_BUTTON], expiry_timer)
+			expiry_timer = nil
+		end
+	end
+
+	local str = "ExpiryTimer"
+		.. " " .. tostring(action) .. ""
+	Debug(str)
+end
+
 ---Create an x/y string for Mail
 ---@param playerName string
 ---@param addOpen boolean
@@ -197,12 +237,12 @@ local function GetCountsStr(playerName, addOpen)
 		if mailbox.opened then
 			res = res .. " " -- counts should be accurate for this session
 		else
-			res = res .. "? " -- counts may not be accurate
+			res = res .. READ_PRE -- counts may not be accurate
 		end
 	end

 	if (toon.lastUpdate == 0) then
-		res = res .. L["TITAN_PANEL_NA"] --NOT_OPENED for first time yet
+		res = res .. L["TITAN_PANEL_NA"] --NOT_OPENED yet
 	else
 		if toon.mailCount > 0 then
 			res = res .. toon.mailReadNum .. "/" .. toon.mailCount
@@ -249,15 +289,16 @@ function GetTooltipText()
 	local res = ""

 	local toon = TitanPostDB[player]
-	res = MAIL_TT_PRE .. FormatTS(toon.lastUpdate, true) .. "\n"
-	res = res.. " "
+	res = MAIL_TT_PRE .. tostring(toon.lastUpdate_str) .. "\n"
+	res = res .. " "
 		.. GetCountsStr(player, true)
 		.. READ_TT_POST .. "\n"


 	local new = ""
 	if (mailbox.new > 0) then
-		new = NEW_PRE .. tostring(mailbox.new) .. NEW_TT_POST .. "\n"
+		--		new = NEW_PRE .. tostring(mailbox.new) .. NEW_TT_POST .. "\n"
+		new = NEW_PRE .. NEW_TT_POST .. "\n"
 	else
 		new = ""
 	end
@@ -282,7 +323,7 @@ local function UpdateInboxData()
 		return
 	end

-	local inboxCount = _G.GetInboxNumItems()
+	local inboxCount, totalCount = _G.GetInboxNumItems()
 	local playerData = TitanPostDB[player]
 	local remainingDays = MAX_MAIL_DAYS
 	local mailReadNum = 0
@@ -293,6 +334,7 @@ local function UpdateInboxData()
 	local str = "UpdateInboxData"
 		.. " " .. tostring(playerData) .. ""
 		.. " #" .. tostring(inboxCount) .. ""
+		.. " #" .. tostring(totalCount) .. ""
 	Debug(str)

 	for index = 1, inboxCount do
@@ -334,9 +376,12 @@ local function UpdateInboxData()
 	end

 	playerData.lastUpdate = now
+	playerData.lastUpdate_str = FormatTS(playerData.lastUpdate, true)
 	playerData.mailCount = inboxCount
 	playerData.mailReadNum = mailReadNum
-	playerData.nextExpiry = math.floor(remainingDays * SECONDS_PER_DAY) + now -- make timestamp
+	local ex_ts = math.floor(remainingDays * SECONDS_PER_DAY) + now -- make timestamp
+	playerData.nextExpiry = ex_ts
+	playerData.nextExpiry_str = FormatTS(ex_ts, true)
 end

 ---Look for expiry notifications, the routine will include the current too
@@ -372,33 +417,68 @@ local function InitVars()
 end

 ---Process the MAIL_INBOX_UPDATE event while mailbox is open
-local function MailInboxUpdate()
-	local str = "MAIL_INBOX_UPDATE"
-		.. " " .. tostring(mailbox.opened) .. ""
+local function MailInboxUpdate(reason)
+	local str = "MailInboxUpdate"
+		.. " o: " .. tostring(mailbox.opened) .. ""
+		.. " on: " .. tostring(mailbox.open_now) .. ""
+		.. " " .. tostring(reason) .. ""
 	Debug(str)

 	UpdateInboxData()

 	UpdateInfo()
-	Debug("Inbox: " .. GetInboxNumItems());
+
+	local numItems, totalItems = GetInboxNumItems()
+	Debug("Inbox: "
+		.. " #" .. numItems .. ""
+		.. " #" .. totalItems .. ""
+	)
 	TitanPanelButton_UpdateButton(TITAN_POST_ID)
 end

+local function OpenMailbox()
+	mailbox.opened = true -- this session only
+	mailbox.open_now = true
+
+	MailInboxUpdate("Mailbox opened")
+end
+
+local function CloseMailbox()
+	mailbox.open_now = false -- this session only
+
+	if HasNewMail() then
+		-- has unread mail, expect a nag event :)
+		mailbox.ignore_next_pending = true
+	else
+		-- no unread mail
+	end
+
+	local reason = "Mailbox closed " .. tostring(mailbox.ignore_next_pending)
+	MailInboxUpdate(reason)
+end
+
 ---Process the UPDATE_PENDING_MAIL event
 local function UpdatePending()
+	local action = ""
 	-- Fires on entering world if player has unread mail...
-	if enter_or_reload then
+	if mailbox.ignore_next_pending then
 		-- ignore 1st event on entering world (first or instance or reload)
-		enter_or_reload = false
+		mailbox.ignore_next_pending = false
+		action = "ignored"
 	else
 		-- Likely a brand new mail
 		mailbox.new = mailbox.new + 1
+		action = "+ 1"
 	end

-	local str = "UPDATE_PENDING_MAIL"
-		.. " " .. tostring(mailbox.opened) .. ""
-		.. " " .. tostring(HasNewMail()) .. ""
-		.. " " .. tostring(GetInboxNumItems()) .. ""
+	local numItems, totalItems = GetInboxNumItems()
+	local str = "UpdatePending"
+		.. " o:" .. tostring(mailbox.opened) .. ""
+		.. " on: " .. tostring(mailbox.open_now) .. ""
+		.. " n:" .. tostring(HasNewMail()) .. ""
+		.. " #" .. numItems .. ""
+		.. " #" .. totalItems .. ""
+		.. " " .. tostring(action) .. ""

 	Debug(str)

@@ -421,16 +501,16 @@ end
 ---@param event string
 ---@param ... unknown
 local function OnEvent(self, event, arg1, ...)
-	Debug(time() .. " " .. event);
+	Debug("New >  " .. event);
 	if (event == "VARIABLES_LOADED") then
 		InitVars()
 	end
 	if (event == "PLAYER_ENTERING_WORLD") then
 		-- ignore 1st UPDATE_PENDING_MAIL event on entering world (first or instance or reload)
-		enter_or_reload = true
+		mailbox.ignore_next_pending = true
 	end
+	---[===[
 	if (event == "MAIL_INBOX_UPDATE") then
-		mailbox.opened = true -- this session only
 		--[[ Dec 2025  https://warcraft.wiki.gg/wiki/MAIL_INBOX_UPDATE
 Fires when the inbox list is loaded while the frame is open
 Fires when mail item changes from new to read
@@ -438,6 +518,7 @@ Fires when mail item is opened for the first time in a session
 		--]]
 		MailInboxUpdate()
 	end
+	--]===]
 	if (event == "UPDATE_PENDING_MAIL") then
 		--[[ Dec 2025  https://warcraft.wiki.gg/wiki/UPDATE_PENDING_MAIL
 Fired when the player enters the world and enters/leaves an instance, if there is mail in the player's mailbox.
@@ -445,7 +526,12 @@ Fired when new mail is received.
 Fired when mailbox window is closed if the number of mail items in the inbox changed (I.E. you deleted mail)
 Does not appear to trigger when auction outbid mail is received... may not in other cases as well
 		--]]
-		UpdatePending()
+
+		if mailbox.ignore_next_pending then
+			-- ignore this event
+		else
+			UpdatePending()
+		end
 	end
 	if (event == "CHAT_MSG_SYSTEM") then
 		--[[ Dec 2025  https://warcraft.wiki.gg/wiki/UPDATE_PENDING_MAIL
@@ -455,26 +541,68 @@ Appears to be a long standing bug that AH 'out bid' message does not fire a 'mai
 	end
 end

+TitanPost.time_ints = {
+	{ min = 1,  set = false },
+	{ min = 10, set = false },
+	{ min = 30, set = false },
+	{ min = 60, set = false },
+}
+local function SetTimer(val)
+	local timer = val or 10
+	local time_ints = TitanPost.time_ints
+	for idx = 1, #time_ints do
+		if time_ints[idx].min == timer then
+			time_ints[idx].set = true
+		else
+			time_ints[idx].set = false
+		end
+	end
+end
+
 ---First level of right click menu
 ---@param frame Button
 local function BuildMainMenu(frame)
 	local info;

-	TitanPanelRightClickMenu_AddTitle(TitanPlugins[TITAN_POST_ID].menuText);
-	TitanPanelRightClickMenu_AddSpacer();
+	TitanPanelRightClickMenu_AddTitle(TitanPlugins[TITAN_POST_ID].menuText, TitanPanelRightClickMenu_GetDropdownLevel());
+
+	TitanPanelRightClickMenu_AddSeparator(TitanPanelRightClickMenu_GetDropdownLevel());
+
+	info = {};
+	info.notCheckable = true
+	info.text = "Expiry Check Interval"
+	info.value = "ExpiryInterval";
+	TitanPanelRightClickMenu_AddButton(info, TitanPanelRightClickMenu_GetDropdownLevel());
+
+	SetTimer(TitanGetVar(TITAN_POST_ID, "ExpiryTimer"))
+	local time_ints = TitanPost.time_ints
+	for idx = 1, #time_ints do
+		info = {};
+		info.text = tostring(time_ints[idx].min) .. L["TITAN_PANEL_MINUTES_ABBR"]
+		info.checked = time_ints[idx].set
+		---@diagnostic disable-next-line: duplicate-set-field
+		info.func = function()
+			SetTimer(time_ints[idx].min)
+			TitanSetVar(TITAN_POST_ID, "ExpiryTimer", time_ints[idx].min)
+		end
+		TitanPanelRightClickMenu_AddButton(info, TitanPanelRightClickMenu_GetDropdownLevel());
+	end
+	TitanPanelRightClickMenu_AddSeparator(TitanPanelRightClickMenu_GetDropdownLevel());
+
 	info = {};
 	info.notCheckable = true
 	info.text = L["TITAN_PANEL_MENU_PROFILES"] .. " " .. L["TITAN_PANEL_MENU_CONFIGURATION"]
 	info.value = "ConfigProfile";
+	---@diagnostic disable-next-line: duplicate-set-field
 	info.func = function()
 		TitanUpdateConfig("init")
 		-- Open the profile config as distinct frame
 		AceConfigDialog:Open("Titan Panel Addon Chars")
 	end
-	TitanPanelRightClickMenu_AddButton(info);
-	TitanPanelRightClickMenu_AddSpacer();
+	TitanPanelRightClickMenu_AddButton(info, TitanPanelRightClickMenu_GetDropdownLevel());
+	--	TitanPanelRightClickMenu_AddSpacer(TitanPanelRightClickMenu_GetDropdownLevel());

-	TitanPanelRightClickMenu_AddControlVars(TITAN_POST_ID)
+	TitanPanelRightClickMenu_AddControlVars(TITAN_POST_ID, TitanPanelRightClickMenu_GetDropdownLevel())
 end

 ---Handle navigating within right click menu
@@ -497,8 +625,21 @@ local function OnShow(self)
 	self:RegisterEvent("UPDATE_PENDING_MAIL")
 	self:RegisterEvent("PLAYER_ENTERING_WORLD")

-	--	self:RegisterEvent("MAIL_SHOW") -- use MAIL_INBOX_UPDATE
-	--	self:RegisterEvent("MAIL_CLOSED") -- In retail, does NOT fire
+	-- Ace does parameter shuffling under the hood depending on the types passed
+	-- hence the ignore diagnostic...
+	---@diagnostic disable-next-line: param-type-mismatch
+	if AceHook:IsHooked("MailFrame_Show", OpenMailbox) then
+		-- Already hooked
+	else
+		AceHook:SecureHook("MailFrame_Show", OpenMailbox) -- MailFrame.lua
+	end
+	---@diagnostic disable-next-line: param-type-mismatch
+	if AceHook:IsHooked("MailFrame_Hide", CloseMailbox) then
+		-- Already hooked
+	else
+		-- Ace does parameter shuffling under the hood depending on the types passed
+		AceHook:SecureHook("MailFrame_Hide", CloseMailbox) -- MailFrame.lua
+	end

 	UpdateInfo()
 	TitanPanelButton_UpdateButton(TITAN_POST_ID)
@@ -513,6 +654,23 @@ local function OnHide(self)
 	self:UnregisterEvent("MAIL_INBOX_UPDATE");
 	self:UnregisterEvent("UPDATE_PENDING_MAIL");
 	self:UnregisterEvent("PLAYER_ENTERING_WORLD");
+
+	---@diagnostic disable-next-line: param-type-mismatch
+	if AceHook:IsHooked("MailFrame_Show", OpenMailbox) then
+		---@diagnostic disable-next-line: param-type-mismatch
+		AceHook:Unhook("MailFrame_Show", OpenMailbox) -- MailFrame.lua
+	else
+		-- nothing to do
+	end
+	---@diagnostic disable-next-line: param-type-mismatch
+	if AceHook:IsHooked("MailFrame_Hide", CloseMailbox) then
+		---@diagnostic disable-next-line: param-type-mismatch
+		AceHook:Unhook("MailFrame_Hide", CloseMailbox) -- MailFrame.lua
+	else
+		-- nothing to do
+	end
+
+	ExpiryTimer('stop')
 end

 ---Handle any mouse clicks
@@ -555,11 +713,8 @@ local function OnLoad(self)
 			ShowLabelText = true,
 			--			ShowColoredText = true,
 			DisplayOnRightSide = false,
-			ShowCount = 1,
-			ShowText = 1,
-			new = 0,
-			total = 0,
-			chat = 1,
+			ExpiryTimer = 10,
+			ShowToonList = true,
 		}
 	};

@@ -568,6 +723,45 @@ local function OnLoad(self)
 	self:RegisterEvent("PLAYER_ENTERING_WORLD");
 end

+local function FormatCounts(playerName, now)
+	local toon = TitanPostDB[playerName]
+	local res = ""
+	local div = " : "
+
+	local last_open = ""
+	local counts = ""
+	local warning = ""
+	if (toon.lastUpdate == 0) then
+		last_open = L["TITAN_PANEL_NA"] --NOT_OPENED yet
+	else
+		last_open = tostring(toon.lastUpdate_str)
+
+		if toon.mailCount >= 0 then
+			-- counts may not be accurate
+			counts = div .. READ_PRE .. toon.mailReadNum .. "/" .. toon.mailCount
+		else
+			-- leave blank
+		end
+
+		if (toon.nextExpiry < ExpiryWarn()) then
+			-- add to list for user
+			local days = math.floor((toon.nextExpiry - now) / SECONDS_PER_DAY)
+			local color = TitanUtils_GetThresholdColor(ThresholdTable, days)
+			local days_str = DAYS_REM .. " : " .. tostring(days)
+			days_str = TitanUtils_GetColoredText(days_str, color)
+			warning = div .. days_str
+		else
+			-- no mail to warn about
+			warning = ""
+		end
+	end
+
+	res = MAIL_OPENED .. " " .. last_open .. counts .. warning .. "\n"
+
+	return res
+end
+
+
 ---Titan Allow Titan to lookup mail info for a toon; return a formatted string
 ---@param playerName string
 ---@return string
@@ -581,33 +775,7 @@ function TitanPost.GetMailInfo(playerName)

 	if _G[TITAN_BUTTON]:IsShown() then
 		if TitanPostDB and TitanPostDB[playerName] then
-			local p = TitanPostDB[playerName]
-			local estr = ""
-			local last = ""
-
-			if p.lastUpdate == 0 then
-				-- not opened mail yet
-				res = L["TITAN_PANEL_NA"]
-			else
-				if (p.mailCount > 0) then
-					estr = GetCountsStr(playerName, false) .. "? "
-					if (p.nextExpiry < ExpiryWarn()) then -- add to list for user
-						local days = math.floor((p.nextExpiry - now) / SECONDS_PER_DAY)
-						local color = TitanUtils_GetThresholdColor(ThresholdTable, days)
-						local days_str = DAYS_REM .. " : " .. tostring(days)
-						days_str = TitanUtils_GetColoredText(days_str, color)
-						estr = estr .. days_str .. "\n"
-					else
-						-- no mail to warn about
-						estr = ""
-					end
-				else
-					-- empty
-				end
-
-				last = MAIL_OPENED .. " : " .. FormatTS(p.lastUpdate, true) .. "\n"
-				res = estr .. last
-			end
+			res = FormatCounts(playerName, now)
 		else
 			res = L["TITAN_PANEL_NA"]
 		end
@@ -741,7 +909,13 @@ end

 function TitanPost.SimUpdateExpiry()
 	UpdateInfo()
-	Debug("Inbox: " .. GetInboxNumItems());
+
+	local numItems, totalItems = GetInboxNumItems()
+	local str = "Inbox Sim : "
+		.. " " .. numItems .. ""
+		.. " " .. totalItems .. ""
+	Debug(str);
+
 	TitanPanelButton_UpdateButton(TITAN_POST_ID)
 end

diff --git a/TitanUI/Tools.lua b/TitanUI/Tools.lua
index 1171d7a..7f094d7 100755
--- a/TitanUI/Tools.lua
+++ b/TitanUI/Tools.lua
@@ -22,7 +22,7 @@ local VERSION = C_AddOns.GetAddOnMetadata(add_on, "Version")
 local trace = false -- true / false    Make true when debug output is needed.

 local function SendSlash(slash, params)
-	DEFAULT_CHAT_FRAME.editBox:SetText(_G[slash].." "..tostring(params))
+	DEFAULT_CHAT_FRAME.editBox:SetText(_G[slash] .. " " .. tostring(params))
 	ChatEdit_SendText(DEFAULT_CHAT_FRAME.editBox, 0)
 end

@@ -85,7 +85,7 @@ local function CreateMenu()
 	info = {};
 	info.notCheckable = true
 	info.text = "Open WoWLua"
----@diagnostic disable-next-line: undefined-global
+	---@diagnostic disable-next-line: undefined-global
 	info.disabled = (SLASH_WOWLUA1 == nil)
 	info.func = function()
 		SendSlash("SLASH_WOWLUA1")
@@ -103,10 +103,36 @@ end

 -- Create the tooltip string
 local function GetTooltipText()
-	local returnstring = ""
-	returnstring = returnstring.."Left Click: Reloads the User Interface\n"
-	returnstring = returnstring.."Right Click: For Shortcuts and Debug Tools\n"
-	return returnstring
+	local res = ""
+	local rtn = "\n"
+	local tab = "\t"
+
+	local realm = TitanUtils_GetNormalText("Current Server :") .. tab
+		.. TitanUtils_GetHighlightText(GetRealmName()) .. rtn
+
+	local now = _G.time()
+
+	local resets = "Resets in Server Time" .. rtn
+	local week_reset = C_DateAndTime.GetSecondsUntilWeeklyReset()
+	local weekly = TitanUtils_GetNormalText("Weekly :") .. tab
+		.. TitanUtils_GetHighlightText(TitanUtils_GetDateText(week_reset + now, true)) .. rtn
+
+	local day_reset = C_DateAndTime.GetSecondsUntilDailyReset()
+	local daily = TitanUtils_GetNormalText("Daily :") .. tab
+		.. TitanUtils_GetHighlightText(TitanUtils_GetDateText(day_reset + now, true)) .. rtn
+
+	local hints = ""
+		.. TitanUtils_GetGreenText("Left Click: Reloads the User Interface") .. rtn
+		.. TitanUtils_GetGreenText("Right Click: For Shortcuts and Debug Tools") .. rtn
+
+	res = res
+		.. realm
+		.. rtn
+		.. resets .. daily .. weekly
+		.. rtn
+		.. hints
+
+	return res
 end

 -- Create the .registry for Titan so it can register and place the plugin
@@ -131,13 +157,13 @@ local function OnLoad(self)
 		controlVariables = {
 			ShowIcon = true,
 			ShowLabelText = true,
---			ShowColoredText = true,
+			--			ShowColoredText = true,
 			DisplayOnRightSide = true,
 		},
 		savedVariables = {
 			ShowIcon = 1,
 			ShowLabelText = 1,
---			ShowColoredText = 1,
+			--			ShowColoredText = 1,
 			DisplayOnRightSide = false,
 		}
 	};
@@ -155,7 +181,7 @@ local function OnClick(self, button)
 		)
 	end
 	if (button == "LeftButton") then
-		ReloadUI()
+		C_UI.Reload() --ReloadUI()
 	end
 end