diff --git a/Orbit/Core/CanvasMode/CanvasModeTour.lua b/Orbit/Core/CanvasMode/CanvasModeTour.lua
index f7ef164..4d60ab5 100644
--- a/Orbit/Core/CanvasMode/CanvasModeTour.lua
+++ b/Orbit/Core/CanvasMode/CanvasModeTour.lua
@@ -1,5 +1,7 @@
 -- [ CANVAS MODE - TOUR ]------------------------------------------------------------
--- Sequential tour that cycles through help points with Next/Done
+-- Sequential tour that cycles through help points with Next/Done.
+-- Strings live in Orbit/Localization/Domains/Tours.lua under the TOUR_CM_* prefix.
+-- When adding or renaming tour stops here, update the matching keys there.
 local _, Orbit = ...
 local OrbitEngine = Orbit.Engine
 local CanvasMode = OrbitEngine.CanvasMode
@@ -22,155 +24,9 @@ local TITLE_CLR = { r = ACCENT.r, g = ACCENT.g, b = ACCENT.b }
 local FONT = "GameFontNormalSmall"
 
 -- [ LOCALIZATION ]-------------------------------------------------------------------
-local LOCALE_STRINGS = {
-    enUS = {
-        TOUR_TOOLTIP = "Canvas Mode Tour",
-        NEXT = "Next", DONE = "Done",
-        DOCK_TITLE = "Component Dock",
-        DOCK_TEXT = "Drag components here to disable them.\nClick a docked icon to re-enable it.",
-        FILTER_TITLE = "Category Filters",
-        FILTER_TEXT = "Filter which component types are visible.\nUse this to focus on text, icons, or auras.",
-        COMP_TITLE = "Component Controls",
-        COMP_TEXT = "Click and drag to reposition.\nHold Shift for 1px precision.\nArrow keys nudge 1px at a time.",
-        VIEW_TITLE = "Viewport Navigation",
-        VIEW_TEXT = "Scroll wheel to zoom in and out.\nClick and drag empty space to pan around.",
-        OVER_TITLE = "Component Overrides",
-        OVER_TEXT = "Click any component to open its settings.\nOverrides here take priority over your\nglobal settings for this component only.",
-        RESIZE_TITLE = "Resize Window",
-        RESIZE_TEXT = "Drag this handle to resize the canvas\nwindow for a larger or smaller workspace.",
-    },
-    deDE = {
-        TOUR_TOOLTIP = "Canvas-Modus Tour",
-        NEXT = "Weiter", DONE = "Fertig",
-        DOCK_TITLE = "Komponentenablage",
-        DOCK_TEXT = "Komponenten hierher ziehen zum Deaktivieren.\nAuf ein Symbol klicken zum Reaktivieren.",
-        FILTER_TITLE = "Kategoriefilter",
-        FILTER_TEXT = "Angezeigte Komponententypen filtern.\nText, Symbole oder Auren gezielt anzeigen.",
-        COMP_TITLE = "Komponentensteuerung",
-        COMP_TEXT = "Klicken und ziehen zum Verschieben.\nUmschalttaste f%]%r 1-Pixel-Pr%ñ
-        VIEW_TITLE = "Ansichtsnavigation",
-        VIEW_TEXT = "Mausrad zum Zoomen.\nFreie Fl%ñ
-        OVER_TITLE = "Komponenten%]%berschreibungen",
-        OVER_TEXT = "Auf eine Komponente klicken f%]%r Einstellungen.\n%£berschreibungen haben Vorrang vor\nglobalen Einstellungen dieser Komponente.",
-        RESIZE_TITLE = "Fenstergr%b%%’e %ñ
-        RESIZE_TEXT = "Diesen Griff ziehen, um das Fenster\nzu vergr%b%%’ern oder zu verkleinern.",
-    },
-    frFR = {
-        TOUR_TOOLTIP = "Visite du mode Canevas",
-        NEXT = "Suivant", DONE = "Termin%#",
-        DOCK_TITLE = "Dock des composants",
-        DOCK_TEXT = "Faites glisser les composants ici pour\nles d%#sactiver. Cliquez pour r%#activer.",
-        FILTER_TITLE = "Filtres de cat%#gorie",
-        FILTER_TEXT = "Filtrez les types de composants affich%#s.\nConcentrez-vous sur le texte, les ic%$%nes ou les auras.",
-        COMP_TITLE = "Contr%$%les des composants",
-        COMP_TEXT = "Cliquez et faites glisser pour repositionner.\nMaintenez Maj pour une pr%#cision de 1px.\nLes fl%¿ches d%#placent de 1px.",
-        VIEW_TITLE = "Navigation de la vue",
-        VIEW_TEXT = "Molette pour zoomer.\nCliquez et faites glisser l'espace vide pour naviguer.",
-        OVER_TITLE = "Remplacements de composant",
-        OVER_TEXT = "Cliquez sur un composant pour ses param%¿tres.\nLes remplacements ici ont priorit%# sur\nvos param%¿tres globaux pour ce composant.",
-        RESIZE_TITLE = "Redimensionner la fen%¬tre",
-        RESIZE_TEXT = "Faites glisser cette poign%#e pour\nredimensionner l'espace de travail.",
-    },
-    esES = {
-        TOUR_TOOLTIP = "Tour del modo Lienzo",
-        NEXT = "Siguiente", DONE = "Hecho",
-        DOCK_TITLE = "Dock de componentes",
-        DOCK_TEXT = "Arrastra componentes aqu%¡ para desactivarlos.\nHaz clic en un icono para reactivar.",
-        FILTER_TITLE = "Filtros de categor%¡a",
-        FILTER_TEXT = "Filtra los tipos de componentes visibles.\nEnf%%cate en texto, iconos o auras.",
-        COMP_TITLE = "Controles de componentes",
-        COMP_TEXT = "Haz clic y arrastra para reposicionar.\nMant%#n May%Q%s para precisi%%n de 1px.\nFlechas desplazan 1px.",
-        VIEW_TITLE = "Navegaci%%n de vista",
-        VIEW_TEXT = "Rueda del rat%%n para acercar y alejar.\nHaz clic y arrastra el espacio vac%¡o para desplazar.",
-        OVER_TITLE = "Ajustes de componente",
-        OVER_TEXT = "Haz clic en un componente para su configuraci%%n.\nLos ajustes aqu%¡ tienen prioridad sobre\nla configuraci%%n global de este componente.",
-        RESIZE_TITLE = "Redimensionar ventana",
-        RESIZE_TEXT = "Arrastra esta esquina para cambiar\nel tama%’%o del espacio de trabajo.",
-    },
-    ptBR = {
-        TOUR_TOOLTIP = "Tour do modo Canvas",
-        NEXT = "Pr%%ximo", DONE = "Conclu%¡do",
-        DOCK_TITLE = "Dock de componentes",
-        DOCK_TEXT = "Arraste componentes aqui para desativar.\nClique num %¡cone para reativar.",
-        FILTER_TITLE = "Filtros de categoria",
-        FILTER_TEXT = "Filtre os tipos de componentes vis%¡veis.\nConcentre-se em texto, %¡cones ou auras.",
-        COMP_TITLE = "Controles de componentes",
-        COMP_TEXT = "Clique e arraste para reposicionar.\nSegure Shift para precis%ú
-        VIEW_TITLE = "Navega%º%ú
-        VIEW_TEXT = "Roda do mouse para zoom.\nClique e arraste o espa%ºo vazio para mover.",
-        OVER_TITLE = "Substitui%º%a%es de componente",
-        OVER_TEXT = "Clique num componente para configura%º%a%es.\nSubstitui%º%a%es aqui t%¬m prioridade sobre\nsuas configura%º%a%es globais deste componente.",
-        RESIZE_TITLE = "Redimensionar janela",
-        RESIZE_TEXT = "Arraste esta al%ºa para redimensionar\na %í
-    },
-    ruRU = {
-        TOUR_TOOLTIP = "h%§ h%’%h%V%h%[%d%Ç
-        NEXT = "h%ö
-        DOCK_TITLE = "h%’h%‘%h%\%h%a%h%W%d%î
-        DOCK_TEXT = "h%’h%a%d%Ç
-        FILTER_TITLE = "h%ñ
-        FILTER_TEXT = "h%ñ
-        COMP_TITLE = "h%ú
-        COMP_TEXT = "h%¥h%‘%h%b%h%]%h%U%d%é
-        VIEW_TITLE = "h%¥h%‘%h%“%h%U%h%%h%‘%d%å
-        VIEW_TEXT = "h%Ü
-        OVER_TITLE = "h%’h%a%d%Ç
-        OVER_TEXT = "h%¥h%‘%h%b%h%]%h%U%d%é
-        RESIZE_TITLE = "h%ÿh%V%h%]%h%a%h%\%h%U%d%é
-        RESIZE_TEXT = "h%’h%a%d%Ç
-    },
-    koKR = {
-        TOUR_TOOLTIP = ""Q%ö
-        NEXT = "´ï
-        DOCK_TITLE = "©a%¼"ä
-        DOCK_TEXT = "©a%¼"ä
-        FILTER_TITLE = ""c%$%Æà
-        FILTER_TEXT = "Ææ
-        COMP_TITLE = "©a%¼"ä
-        COMP_TEXT = "Æü
-        VIEW_TITLE = "´V%‘%ÆÅ
-        VIEW_TEXT = "´ºê
-        OVER_TITLE = "©a%¼"ä
-        OVER_TEXT = "©a%¼"ä
-        RESIZE_TITLE = ""‘%\% Æü
-        RESIZE_TEXT = ""¥$% Æò
-    },
-    zhCN = {
-        TOUR_TOOLTIP = "Äö
-        NEXT = "£U%ï
-        DOCK_TITLE = "ÄW%ä
-        DOCK_TEXT = "Ã‘%å
-        FILTER_TITLE = "Ãê
-        FILTER_TEXT = "Ä¡¢˜Ç
-        COMP_TITLE = "ÄW%ä
-        COMP_TEXT = "Äé
-        VIEW_TITLE = "¦ºå
-        VIEW_TEXT = "µW%Ü
-        OVER_TITLE = "ÄW%ä
-        OVER_TEXT = "Äé
-        RESIZE_TITLE = "¦‘%â
-        RESIZE_TEXT = "µï
-    },
-    zhTW = {
-        TOUR_TOOLTIP = "Äò
-        NEXT = "£U%ï
-        DOCK_TITLE = "Ãà
-        DOCK_TEXT = "Ã‘%ç
-        FILTER_TITLE = "Ãê
-        FILTER_TEXT = "Ä»#˜ü
-        COMP_TITLE = "Ãà
-        COMP_TEXT = "˜W%§ µô
-        VIEW_TITLE = "¦ªû
-        VIEW_TEXT = "µW%[%¦]%¬ÄU%«µö
-        OVER_TITLE = "Ãà
-        OVER_TEXT = "˜W%§ µô
-        RESIZE_TITLE = "¦¬%µò
-        RESIZE_TEXT = "µï
-    },
-}
-LOCALE_STRINGS.enGB = LOCALE_STRINGS.enUS
-LOCALE_STRINGS.esMX = LOCALE_STRINGS.esES
-local L = LOCALE_STRINGS[GetLocale()] or LOCALE_STRINGS.enUS
+-- Strings live in Localization/Domains/Tours.lua (TOUR_CM_* keys) and Common.lua
+-- (CMN_NEXT / CMN_DONE).
+local L = Orbit.L
 
 -- CJK needs wider tooltips for multi-byte glyphs
 local isCJK = ({ koKR = true, zhCN = true, zhTW = true })[GetLocale()]
@@ -180,10 +36,10 @@ if isCJK then TOOLTIP_MAX_WIDTH = 240 end
 local TOUR_STOPS = {
     { anchor = function() return Dialog.DisabledDock end,
       tooltipPoint = "BOTTOM", tooltipRel = "TOP", tpX = 0, tpY = 8,
-      title = L.DOCK_TITLE, text = L.DOCK_TEXT },
+      title = L.TOUR_CM_DOCK_TITLE, text = L.TOUR_CM_DOCK_TEXT },
     { anchor = function() return Dialog.FilterTabBar end,
       tooltipPoint = "TOP", tooltipRel = "BOTTOM", tpX = 0, tpY = -8,
-      title = L.FILTER_TITLE, text = L.FILTER_TEXT },
+      title = L.TOUR_CM_FILTER_TITLE, text = L.TOUR_CM_FILTER_TEXT },
     { anchor = function()
           if not Dialog.previewComponents then return nil end
           for _, comp in pairs(Dialog.previewComponents) do
@@ -192,7 +48,7 @@ local TOUR_STOPS = {
           return nil
       end,
       tooltipPoint = "TOPLEFT", tooltipRel = "TOPRIGHT", tpX = 8, tpY = 4,
-      title = L.COMP_TITLE, text = L.COMP_TEXT,
+      title = L.TOUR_CM_COMP_TITLE, text = L.TOUR_CM_COMP_TEXT,
       allAnchors = function()
           local list = {}
           if Dialog.previewComponents then
@@ -204,10 +60,10 @@ local TOUR_STOPS = {
       end },
     { anchor = function() return Dialog.Viewport end,
       tooltipPoint = "CENTER", tooltipRel = "CENTER", tpX = 0, tpY = 0,
-      title = L.VIEW_TITLE, text = L.VIEW_TEXT },
+      title = L.TOUR_CM_VIEW_TITLE, text = L.TOUR_CM_VIEW_TEXT },
     { anchor = function() return Dialog.OverrideContainer end,
       tooltipPoint = "BOTTOM", tooltipRel = "TOP", tpX = 0, tpY = 8,
-      title = L.OVER_TITLE, text = L.OVER_TEXT,
+      title = L.TOUR_CM_OVER_TITLE, text = L.TOUR_CM_OVER_TEXT,
       onEnter = function()
           Dialog._tourOpenedComp = nil
           if not Dialog.previewComponents then return end
@@ -237,7 +93,7 @@ local TOUR_STOPS = {
       end },
     { anchor = function() return Dialog.ResizeHandle end,
       tooltipPoint = "RIGHT", tooltipRel = "LEFT", tpX = -8, tpY = 0,
-      title = L.RESIZE_TITLE, text = L.RESIZE_TEXT },
+      title = L.TOUR_CM_RESIZE_TITLE, text = L.TOUR_CM_RESIZE_TEXT },
 }
 
 -- [ STATE ]--------------------------------------------------------------------------
@@ -392,7 +248,7 @@ local function LayoutTooltip(anchor, stop, idx, total)
     tip.title:SetText(stop.title)
     tip.text:SetText(stop.text)
     local isLast = idx == total
-    tip.nextBtn:SetText(isLast and L.DONE or L.NEXT)
+    tip.nextBtn:SetText(isLast and L.CMN_DONE or L.CMN_NEXT)
     -- Size to fit
     local textH = tip.counter:GetStringHeight() + 2 + tip.title:GetStringHeight() + 3 + tip.text:GetStringHeight()
     local h = textH + TOOLTIP_PAD * 2 + NEXT_BTN_GAP + NEXT_BTN_HEIGHT + TOOLTIP_PAD
@@ -473,7 +329,7 @@ btn.Icon:SetSize(BUTTON_SIZE, BUTTON_SIZE)
 btn.Icon:SetPoint("CENTER")
 btn:SetScript("OnEnter", function(self)
     GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
-    GameTooltip:AddLine(L.TOUR_TOOLTIP, 1, 1, 1)
+    GameTooltip:AddLine(L.TOUR_CM_TOOLTIP, 1, 1, 1)
     GameTooltip:Show()
 end)
 btn:SetScript("OnLeave", function() GameTooltip:Hide() end)
diff --git a/Orbit/Core/Config/Config.xml b/Orbit/Core/Config/Config.xml
index 74288ae..ae97a00 100644
--- a/Orbit/Core/Config/Config.xml
+++ b/Orbit/Core/Config/Config.xml
@@ -1,16 +1,20 @@
 <Ui xmlns="http://www.blizzard.com/wow/ui/">
-    <Script file="ConfigLayout.lua"/>
-    <Script file="SchemaBuilder.lua"/>
+    <Script file="Schema\ConfigLayout.lua"/>
+    <Script file="Schema\SchemaBuilder.lua"/>
     <Include file="Widgets\Widgets.xml"/>
-    <Script file="ConfigRenderer.lua"/>
-    <Script file="OrbitSettingsDialog.lua"/>
-    <Script file="OrbitOptionsPanel.lua"/>
-    <Script file="SlashCommands.lua"/>
+    <Script file="Schema\ConfigRenderer.lua"/>
+    <Script file="Panels\OrbitSettingsDialog.lua"/>
+    <Script file="Panels\OrbitOptionsPanel.lua"/>
+    <Script file="Panels\Tabs\GlobalTab.lua"/>
+    <Script file="Panels\Tabs\ColorsTab.lua"/>
+    <Script file="Panels\Tabs\EditModeTab.lua"/>
+    <Script file="Panels\Tabs\ProfilesTab.lua"/>
+    <Script file="Entry\SlashCommands.lua"/>
     <Script file="WhatsNew.lua"/>
-    <Script file="OrbitOptionsButton.lua"/>
-    <Script file="MinimapButton.lua"/>
-    <Script file="OrbitAdvancedSettings.lua"/>
-    <Script file="Advanced/PluginManager.lua"/>
-    <Script file="Advanced/VisibilityEngine.lua"/>
-    <Script file="Advanced/QoL.lua"/>
+    <Script file="Entry\OrbitOptionsButton.lua"/>
+    <Script file="Entry\MinimapButton.lua"/>
+    <Script file="Panels\OrbitAdvancedSettings.lua"/>
+    <Script file="Advanced\PluginManager.lua"/>
+    <Script file="Advanced\VisibilityEngine.lua"/>
+    <Script file="Advanced\QoL.lua"/>
 </Ui>
diff --git a/Orbit/Core/Config/Entry/SlashCommands.lua b/Orbit/Core/Config/Entry/SlashCommands.lua
index da1f843..309759a 100644
--- a/Orbit/Core/Config/Entry/SlashCommands.lua
+++ b/Orbit/Core/Config/Entry/SlashCommands.lua
@@ -85,6 +85,7 @@ local function PrintHelp()
     print("  |cFF00FFFF/orbit reset <name>|r “Ç
     print("  |cFF00FFFF/orbit ve|r “Ç
     print("  |cFF00FFFF/orbit ve reset|r “Ç
+    print("  |cFF00FFFF/orbit tracked flush|r “Ç
     print("  |cFF00FFFF/orbit whatsnew|r “Ç
     print("  |cFF00FFFF/orbit hardreset|r “Ç
 end
@@ -286,6 +287,18 @@ SlashCmdList["ORBIT"] = function(msg)
         else
             Orbit:Print("ViewerInjection not loaded.")
         end
+    elseif cmd == "tracked" then
+        local sub = args[2] and args[2]:lower() or ""
+        if sub == "flush" then
+            local plugin = Orbit:GetPlugin("Orbit_Tracked")
+            if plugin and plugin.FlushCurrentSpec then
+                plugin:FlushCurrentSpec()
+            else
+                Orbit:Print("Tracked plugin not loaded.")
+            end
+        else
+            Orbit:Print("Usage: /orbit tracked flush")
+        end
     else
         Orbit:Print("Unknown command: " .. cmd .. ". Type |cFF00FFFF/orbit help|r for a list.")
     end
diff --git a/Orbit/Core/Config/Panels/OrbitOptionsPanel.lua b/Orbit/Core/Config/Panels/OrbitOptionsPanel.lua
index ab95370..6f0403d 100644
--- a/Orbit/Core/Config/Panels/OrbitOptionsPanel.lua
+++ b/Orbit/Core/Config/Panels/OrbitOptionsPanel.lua
@@ -1,55 +1,34 @@
 -- [ ORBIT OPTIONS PANEL ]---------------------------------------------------------------------------
+-- Shell for the Orbit Options dialog. Owns tab registration, open/hide/refresh lifecycle, and the
+-- shared helpers each tab uses to build a Global-scoped settings plugin. Individual tab schemas
+-- and plugins live in `Tabs/` and register themselves into `Panel.Tabs`.
 
 local _, Orbit = ...
 local OrbitEngine = Orbit.Engine
-local Layout = OrbitEngine.Layout
 local Config = OrbitEngine.Config
-local Constants = Orbit.Constants
 
--- [ CONSTANTS ]-------------------------------------------------------------------------------------
+-- [ TAB ORDER ]-------------------------------------------------------------------------------------
 
-local BUTTON_WIDTH = 180
-local DROPDOWN_WIDTH = 200
-local SPACER_SMALL = 10
-local SPACER_LARGE = 20
+local TAB_ORDER = { "Global", "Colors", "Edit Mode", "Profiles" }
 
--- [ HELPERS ]---------------------------------------------------------------------------------------
+-- [ PANEL ]-----------------------------------------------------------------------------------------
 
 Orbit.OptionsPanel = {}
 local Panel = Orbit.OptionsPanel
 
+Panel.Tabs = {}
+Panel.TabOrder = TAB_ORDER
+
+-- [ SHARED HELPERS ]--------------------------------------------------------------------------------
+-- Exposed on Panel._helpers so tab files can build Global-scoped settings plugins without
+-- duplicating the GetSetting/SetSetting/ApplySettings wiring.
+
 local function RefreshAllPreviews()
     for _, plugin in ipairs(OrbitEngine.systems) do
         if plugin.ApplyPreviewVisuals then plugin:ApplyPreviewVisuals() end
     end
 end
 
-local function GetBorderStyleOptions()
-    local opts = {}
-    for _, entry in ipairs(Constants.BorderStyle.Styles) do
-        opts[#opts + 1] = entry
-    end
-    local LSM = LibStub and LibStub("LibSharedMedia-3.0", true)
-    if LSM then
-        local existing = {}
-        for _, entry in ipairs(opts) do existing[entry.label] = true end
-        local borders = LSM:HashTable("border")
-        if borders then
-            for name, path in pairs(borders) do
-                if not existing[name] and path and path ~= "" and name ~= "None" and not name:match("^Blizzard") then
-                    opts[#opts + 1] = { label = name, value = "lsm:" .. name }
-                end
-            end
-        end
-    end
-    table.sort(opts, function(a, b)
-        if a.value == "flat" then return true end
-        if b.value == "flat" then return false end
-        return a.label < b.label
-    end)
-    return opts
-end
-
 local function CreateGlobalSettingsPlugin(name, onSetOverride)
     return {
         name = name,
@@ -74,867 +53,25 @@ local function CreateGlobalSettingsPlugin(name, onSetOverride)
     }
 end
 
--- [ GLOBAL TAB ]------------------------------------------------------------------------------------
-
-local GlobalPlugin = CreateGlobalSettingsPlugin("OrbitGlobal")
-
-local function GetGlobalSchema()
-    local controls = {
-        { type = "font", key = "Font", label = "Font", default = "PT Sans Narrow" },
-        {
-            type = "dropdown", key = "FontOutline", label = "Outline",
-            options = {
-                { label = "None", value = "" }, { label = "Outline", value = "OUTLINE" },
-                { label = "Thick Outline", value = "THICKOUTLINE" }, { label = "Monochrome", value = "MONOCHROME" },
-            },
-            default = "OUTLINE",
-            valueCheckbox = {
-                tooltip = "Text Shadow",
-                initialValue = Orbit.db.GlobalSettings and Orbit.db.GlobalSettings.FontShadow or false,
-                callback = function(checked)
-                    GlobalPlugin:SetSetting(nil, "FontShadow", checked)
-                    GlobalPlugin:ApplySettings()
-                end,
-            },
-        },
-        {
-            type = "dropdown", key = "BorderStyle", label = "Border Style", options = GetBorderStyleOptions(), default = Constants.BorderStyle.Default,
-            onChange = function(val)
-                GlobalPlugin:SetSetting(nil, "BorderStyle", val)
-                GlobalPlugin:ApplySettings()
-                Orbit.EventBus:Fire("ORBIT_BORDER_SIZE_CHANGED")
-                local dialog = Orbit.SettingsDialog
-                if dialog and dialog.OrbitPanel and dialog.OrbitPanel.Tabs then
-                    local oldTab = dialog.OrbitPanel.Tabs["Global"]
-                    if oldTab then
-                        Layout:Reset(oldTab)
-                        oldTab:Hide()
-                    end
-                    dialog.OrbitPanel.Tabs["Global"] = nil
-                end
-                Panel.lastTab = nil
-                Panel:Open("Global")
-            end,
-        },
-    }
-
-    local currentStyle = Orbit.db and Orbit.db.GlobalSettings and Orbit.db.GlobalSettings.BorderStyle or Constants.BorderStyle.Default
-    local function borderSizeChanged(key, val)
-        GlobalPlugin:SetSetting(nil, key, val)
-        GlobalPlugin:ApplySettings()
-        Orbit.EventBus:Fire("ORBIT_BORDER_SIZE_CHANGED")
-    end
-    if currentStyle == "flat" then
-        tinsert(controls, { type = "slider", key = "BorderSize", label = "Border Size", default = 2, min = 0, max = 5, step = 1, updateOnRelease = true, onChange = function(v) borderSizeChanged("BorderSize", v) end })
-    else
-        tinsert(controls, { type = "slider", key = "BorderEdgeSize", label = "Border Edge Size", default = 16, min = 1, max = 32, step = 1, updateOnRelease = true, onChange = function(v) borderSizeChanged("BorderEdgeSize", v) end })
-        tinsert(controls, { type = "slider", key = "BorderOffset", label = "Border Offset", default = 0, min = 0, max = 16, step = 1, updateOnRelease = true, onChange = function(v) borderSizeChanged("BorderOffset", v) end })
-    end
-
-    tinsert(controls, {
-        type = "dropdown", key = "IconBorderStyle", label = "Icon Border Style", options = GetBorderStyleOptions(), default = Constants.BorderStyle.Default,
-        onChange = function(val)
-            GlobalPlugin:SetSetting(nil, "IconBorderStyle", val)
-            GlobalPlugin:ApplySettings()
-            Orbit.EventBus:Fire("ORBIT_BORDER_SIZE_CHANGED")
-            local dialog = Orbit.SettingsDialog
-            if dialog and dialog.OrbitPanel and dialog.OrbitPanel.Tabs then
-                local oldTab = dialog.OrbitPanel.Tabs["Global"]
-                if oldTab then Layout:Reset(oldTab); oldTab:Hide()
-                    dialog.OrbitPanel.Tabs["Global"] = nil
-                end
-                Panel.lastTab = nil
-                Panel:Open("Global")
-            end
-        end,
-    })
-
-    local currentIconStyle = Orbit.db and Orbit.db.GlobalSettings and Orbit.db.GlobalSettings.IconBorderStyle or Constants.BorderStyle.Default
-    if currentIconStyle == "flat" then
-        tinsert(controls, { type = "slider", key = "IconBorderSize", label = "Icon Border Size", default = 2, min = 0, max = 5, step = 1, updateOnRelease = true, onChange = function(v) borderSizeChanged("IconBorderSize", v) end })
-    else
-        tinsert(controls, { type = "slider", key = "IconBorderEdgeSize", label = "Icon Border Edge Size", default = 16, min = 1, max = 32, step = 1, updateOnRelease = true, onChange = function(v) borderSizeChanged("IconBorderEdgeSize", v) end })
-        tinsert(controls, { type = "slider", key = "IconBorderOffset", label = "Icon Border Offset", default = 0, min = 0, max = 16, step = 1, updateOnRelease = true, onChange = function(v) borderSizeChanged("IconBorderOffset", v) end })
-    end
-
-
-    return {
-        hideNativeSettings = true,
-        hideResetButton = false,
-        openPluginManager = true,
-        controls = controls,
-        onReset = function()
-            local d = Orbit.db.GlobalSettings
-            if d then
-                d.Font = "PT Sans Narrow"
-                d.FontOutline = "OUTLINE"
-                d.FontShadow = false
-                d.BorderSize = 2
-                d.BorderStyle = Constants.BorderStyle.Default
-                d.BorderEdgeSize = 16
-                d.BorderOffset = 0
-                d.IconBorderStyle = Constants.BorderStyle.Default
-                d.IconBorderSize = 2
-                d.IconBorderEdgeSize = 16
-                d.IconBorderOffset = 0
-            end
-            Orbit:Print("Global settings reset to defaults.")
-        end,
-    }
-end
-
--- [ COLORS TAB ]------------------------------------------------------------------------------------
-
-local ColorsPlugin = CreateGlobalSettingsPlugin("OrbitColors")
-
-local function GetColorsSchema()
-    local controls = {
-        { type = "texture", key = "Texture", label = "Texture", default = "Melli", previewColor = { r = 0.8, g = 0.8, b = 0.8 } },
-        { type = "texture", key = "OverlayTexture", label = "Overlay Texture", default = "None", previewColor = { r = 0.5, g = 0.5, b = 0.5 } },
-        {
-            type = "texture", key = "AbsorbTexture", label = "Absorb Texture", default = "Blizzard",
-            previewColor = { r = 0.5, g = 0.8, b = 1.0 },
-            valueCheckbox = {
-                tooltip = "Always Show Absorb",
-                initialValue = Orbit.db.GlobalSettings and Orbit.db.GlobalSettings.AlwaysShowAbsorb or false,
-                callback = function(checked)
-                    ColorsPlugin:SetSetting(nil, "AlwaysShowAbsorb", checked)
-                    Orbit.EventBus:Fire("ORBIT_ABSORB_STYLE_CHANGED")
-                end,
-            },
-        },
-        {
-            type = "colorcurve", key = "FontColorCurve", label = "Font",
-            default = { pins = { { position = 0, color = { r = 1, g = 1, b = 1, a = 1 } } } },
-            onChange = function(val)
-                ColorsPlugin:SetSetting(nil, "FontColorCurve", val)
-                Orbit.Async:Debounce("ColorsPanel_FontColor", function() ColorsPlugin:ApplySettings() end, 0.15)
-            end,
-        },
-        {
-            type = "colorcurve", key = "BarColorCurve", label = "Unit Health",
-            default = { pins = { { position = 0, color = { r = 0.2, g = 0.8, b = 0.2, a = 1 } } } },
-            tooltip = "Health bar color. Use the color picker to select class color or create custom gradients.",
-            valueCheckbox = {
-                tooltip = "Standard Spatial Gradient\n\nApply the first and last pins as a fixed horizontal gradient instead of sampling the curve dynamically based on health progress.",
-                initialValue = Orbit.db.GlobalSettings and Orbit.db.GlobalSettings.UnitHealthUseGradient or false,
-                callback = function(checked)
-                    ColorsPlugin:SetSetting(nil, "UnitHealthUseGradient", checked)
-                    Orbit.Async:Debounce("ColorsPanel_BarColor", function()
-                        ColorsPlugin:ApplySettings()
-                        RefreshAllPreviews()
-                    end, 0.15)
-                end,
-            },
-            onChange = function(val)
-                ColorsPlugin:SetSetting(nil, "BarColorCurve", val)
-                Orbit.Async:Debounce("ColorsPanel_BarColor", function()
-                    ColorsPlugin:ApplySettings()
-                    RefreshAllPreviews()
-                end, 0.15)
-            end,
-        },
-        {
-            type = "colorcurve", key = "UnitFrameBackdropColourCurve", label = "Background",
-            default = { pins = { { position = 0, color = { r = 0.08, g = 0.08, b = 0.08, a = 0.5 } } } },
-            onChange = function(val)
-                ColorsPlugin:SetSetting(nil, "UnitFrameBackdropColourCurve", val)
-                Orbit.Async:Debounce("ColorsPanel_UnitFrameBg", function()
-                    ColorsPlugin:ApplySettings()
-                    RefreshAllPreviews()
-                    Orbit.EventBus:Fire("ORBIT_GLOBAL_BACKDROP_CHANGED")
-                end, 0.15)
-            end,
-        },
-        {
-            type = "solidcolor", key = "BorderColor", label = "Frame Borders",
-            visibleIf = function() return (Orbit.db and Orbit.db.GlobalSettings and Orbit.db.GlobalSettings.BorderStyle or Constants.BorderStyle.Default) == "flat" end,
-            default = { r = 0, g = 0, b = 0, a = 1 },
-            tooltip = "Border color for unit frames, cast bars, and other non-icon frames.",
-            onChange = function(val)
-                ColorsPlugin:SetSetting(nil, "BorderColor", val)
-                Orbit.Async:Debounce("ColorsPanel_BorderColor", function()
-                    ColorsPlugin:ApplySettings()
-                    RefreshAllPreviews()
-                    Orbit.EventBus:Fire("ORBIT_GLOBAL_BORDER_COLOR_CHANGED")
-                end, 0.15)
-            end,
-        },
-        {
-            type = "solidcolor", key = "IconBorderColor", label = "Icon Borders",
-            visibleIf = function() return (Orbit.db and Orbit.db.GlobalSettings and Orbit.db.GlobalSettings.IconBorderStyle or Constants.BorderStyle.Default) == "flat" end,
-            default = { r = 0, g = 0, b = 0, a = 1 },
-            tooltip = "Border color for aura icons, cooldown icons, and other icon frames.",
-            onChange = function(val)
-                ColorsPlugin:SetSetting(nil, "IconBorderColor", val)
-                Orbit.Async:Debounce("ColorsPanel_IconBorderColor", function()
-                    ColorsPlugin:ApplySettings()
-                    RefreshAllPreviews()
-                    Orbit.EventBus:Fire("ORBIT_GLOBAL_BORDER_COLOR_CHANGED")
-                end, 0.15)
-            end,
-        },
-    }
-
-    return {
-        hideNativeSettings = true,
-        hideResetButton = false,
-        openPluginManager = true,
-        controls = controls,
-        onReset = function()
-            local d = Orbit.db.GlobalSettings
-            if d then
-                d.Texture = "Melli"
-
-                d.OverlayTexture = "None"
-                d.AbsorbTexture = "Blizzard"
-                d.AlwaysShowAbsorb = false
-                d.UnitHealthUseGradient = false
-                d.BarColorCurve = { pins = { { position = 0, color = { r = 0.2, g = 0.8, b = 0.2, a = 1 } } } }
-                d.UnitFrameBackdropColourCurve = { pins = { { position = 0, color = { r = 0.08, g = 0.08, b = 0.08, a = 0.5 } } } }
-                d.FontColorCurve = { pins = { { position = 0, color = { r = 1, g = 1, b = 1, a = 1 } } } }
-                d.BorderColor = { r = 0, g = 0, b = 0, a = 1 }
-                d.IconBorderColor = { r = 0, g = 0, b = 0, a = 1 }
-            end
-            Orbit:Print("Colors settings reset to defaults.")
-            if Orbit.OptionsPanel then Orbit.OptionsPanel:Refresh() end
-        end,
-    }
-end
-
--- [ EDIT MODE TAB ]---------------------------------------------------------------------------------
-
-local EditModePlugin = CreateGlobalSettingsPlugin("OrbitEditMode", function(key, value)
-    if Orbit.Engine.FrameSelection then Orbit.Engine.FrameSelection:RefreshVisuals() end
-end)
-
-EditModePlugin.ApplySettings = function(self, systemFrame) end
-
-local function GetEditModeSchema()
-    return {
-        hideNativeSettings = true,
-        hideResetButton = false,
-        openPluginManager = true,
-        controls = {
-            { type = "checkbox", key = "ShowBlizzardFrames", label = "Show Blizzard Frames", default = true, tooltip = "Show selection overlays for native Blizzard frames in Edit Mode." },
-            { type = "checkbox", key = "ShowOrbitFrames", label = "Show Orbit Frames", default = true, tooltip = "Show selection overlays for Orbit-owned frames in Edit Mode." },
-            { type = "checkbox", key = "AnchoringEnabled", label = "Enable Frame Anchoring", default = true, tooltip = "Allow frames to anchor to other frames. Disabling preserves existing anchors but prevents new ones.\n\nHold Shift while dragging to temporarily bypass anchoring." },
-            {
-                type = "colorcurve", key = "EditModeColorCurve", label = "Orbit Frame Color",
-                default = { pins = { { position = 0, color = { r = 0.7, g = 0.6, b = 1.0, a = 1.0 } } } },
-                tooltip = "Color of the selection overlay for Orbit-owned frames.",
-            },
-        },
-        onReset = function()
-            local d = Orbit.db.GlobalSettings
-            if d then
-                d.ShowBlizzardFrames = true
-                d.ShowOrbitFrames = true
-                d.AnchoringEnabled = true
-                d.EditModeColor = { r = 0.7, g = 0.6, b = 1.0, a = 1.0 }
-                d.EditModeColorCurve = { pins = { { position = 0, color = { r = 0.7, g = 0.6, b = 1.0, a = 1.0 } } } }
-            end
-            if Orbit.Engine.FrameSelection then Orbit.Engine.FrameSelection:RefreshVisuals() end
-            Orbit:Print("Edit Mode settings reset to defaults.")
-        end,
-    }
-end
-
--- [ PROFILES TAB ]----------------------------------------------------------------------------------
-
-local ICON_BUTTON_SIZE = 14
-
--- Composite widget: dropdown for active profile (greyed out when spec profiles control it)
-Layout:RegisterWidgetType("profileactive", function(container, def, getValue, callback)
-    local options = type(def.options) == "function" and def.options() or def.options or {}
-    local initialValue = getValue and getValue() or def.default or ""
-    local frame = Layout:CreateDropdown(container, def.label, options, initialValue, function(value)
-        if callback then callback(value) end
-    end)
-    frame.OrbitType = "ProfileActive"
-    if Orbit.Profile:IsSpecProfilesEnabled() then
-        frame:SetAlpha(0.4)
-        if frame.Dropdown then
-            frame.Dropdown:SetEnabled(false)
-            frame.Dropdown:EnableMouse(false)
-        end
-        local overlay = CreateFrame("Frame", nil, frame)
-        overlay:SetAllPoints()
-        overlay:SetFrameLevel((frame.Dropdown and frame.Dropdown:GetFrameLevel() or frame:GetFrameLevel()) + 10)
-        overlay:EnableMouse(true)
-        overlay:SetScript("OnEnter", function(self)
-            GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
-            GameTooltip:SetText("Active profile is controlled by Spec Profiles")
-            GameTooltip:Show()
-        end)
-        overlay:SetScript("OnLeave", function() GameTooltip:Hide() end)
-    end
-    return frame
-end)
-
--- Composite widget: dropdown + reset/copy/delete icons
-Layout:RegisterWidgetType("profileselect", function(container, def, getValue, callback)
-    local options = type(def.options) == "function" and def.options() or def.options or {}
-    local initialValue = getValue and getValue() or def.default or ""
-    local UpdateButtonStates
-    local frame = Layout:CreateDropdown(container, def.label, options, initialValue, function(value)
-        if callback then callback(value) end
-        C_Timer.After(0, function() if UpdateButtonStates then UpdateButtonStates() end end)
-    end)
-    frame.OrbitType = "ProfileSelect"
-
-    UpdateButtonStates = function()
-        if not frame.resetBtn then return end
-        local val = frame.currentValue
-        local isActive = val == Orbit.Profile:GetActiveProfileName()
-        local isGlobal = val == "Global"
-        -- Reset: only Global
-        if isGlobal then
-            frame.resetBtn:Enable()
-            frame.resetBtn.Icon:SetDesaturated(false)
-            frame.resetBtn.Icon:SetAlpha(1)
-        else
-            frame.resetBtn:Disable()
-            frame.resetBtn.Icon:SetDesaturated(true)
-            frame.resetBtn.Icon:SetAlpha(0.4)
-        end
-        -- Copy: always allowed
-        frame.copyBtn:Enable()
-        frame.copyBtn.Icon:SetDesaturated(false)
-        frame.copyBtn.Icon:SetAlpha(1)
-        -- Delete: blocked for active/Global
-        if isActive or isGlobal then
-            frame.xBtn:Disable()
-            frame.xBtn.Icon:SetDesaturated(true)
-            frame.xBtn.Icon:SetAlpha(0.4)
-        else
-            frame.xBtn:Enable()
-            frame.xBtn.Icon:SetDesaturated(false)
-            frame.xBtn.Icon:SetAlpha(1)
-        end
-    end
-
-    local gap = Constants.Widget.ValueWidth
-    local tripleWidth = ICON_BUTTON_SIZE * 3 + 4
-    local tripleOffset = (gap - tripleWidth) / 2
-
-    -- Reset button (undo icon)
-    local resetBtn = CreateFrame("Button", nil, frame)
-    resetBtn:SetSize(ICON_BUTTON_SIZE, ICON_BUTTON_SIZE)
-    resetBtn:SetPoint("LEFT", frame.Dropdown, "RIGHT", tripleOffset + 10, 0)
-    frame.resetBtn = resetBtn
-    local resetIcon = resetBtn:CreateTexture(nil, "ARTWORK")
-    resetIcon:SetAllPoints()
-    resetIcon:SetAtlas("talents-button-undo")
-    resetBtn.Icon = resetIcon
-    resetBtn:SetScript("OnEnter", function(self)
-        GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
-        if self:IsEnabled() then
-            self.Icon:SetAlpha(0.8)
-            GameTooltip:SetText("Reset selected profile to defaults")
-        else
-            GameTooltip:SetText("Only the Global profile can be reset")
-        end
-        GameTooltip:Show()
-    end)
-    resetBtn:SetScript("OnLeave", function(self)
-        if self:IsEnabled() then self.Icon:SetAlpha(1) end
-        GameTooltip:Hide()
-    end)
-    resetBtn:SetScript("OnClick", function()
-        if def.onReset then def.onReset(frame.currentValue) end
-    end)
-
-    -- Copy button (+ icon)
-    local copyBtn = CreateFrame("Button", nil, frame)
-    copyBtn:SetSize(ICON_BUTTON_SIZE, ICON_BUTTON_SIZE)
-    copyBtn:SetPoint("LEFT", resetBtn, "RIGHT", 2, 0)
-    frame.copyBtn = copyBtn
-    local copyIcon = copyBtn:CreateTexture(nil, "ARTWORK")
-    copyIcon:SetAllPoints()
-    copyIcon:SetAtlas("communities-chat-icon-plus")
-    copyBtn.Icon = copyIcon
-    copyBtn:SetScript("OnEnter", function(self)
-        if self:IsEnabled() then
-            self.Icon:SetAlpha(0.8)
-            GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
-            GameTooltip:SetText("Copy selected profile")
-            GameTooltip:Show()
-        end
-    end)
-    copyBtn:SetScript("OnLeave", function(self)
-        if self:IsEnabled() then self.Icon:SetAlpha(1) end
-        GameTooltip:Hide()
-    end)
-    copyBtn:SetScript("OnClick", function()
-        if def.onCopy then def.onCopy(frame.currentValue) end
-    end)
-
-    -- Delete button (X icon)
-    local xBtn = CreateFrame("Button", nil, frame)
-    xBtn:SetSize(ICON_BUTTON_SIZE, ICON_BUTTON_SIZE)
-    xBtn:SetPoint("LEFT", copyBtn, "RIGHT", 2, 0)
-    frame.xBtn = xBtn
-    local xIcon = xBtn:CreateTexture(nil, "ARTWORK")
-    xIcon:SetAllPoints()
-    xIcon:SetAtlas("transmog-icon-remove")
-    xBtn.Icon = xIcon
-    xBtn:SetScript("OnEnter", function(self)
-        GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
-        if self:IsEnabled() then
-            self.Icon:SetAlpha(0.8)
-            GameTooltip:SetText("Delete selected profile")
-        else
-            GameTooltip:SetText("Cannot delete active or Global profile")
-        end
-        GameTooltip:Show()
-    end)
-    xBtn:SetScript("OnLeave", function(self)
-        if self:IsEnabled() then self.Icon:SetAlpha(1) end
-        GameTooltip:Hide()
-    end)
-    xBtn:SetScript("OnClick", function()
-        if def.onDelete then def.onDelete(frame.currentValue) end
-    end)
-
-    UpdateButtonStates()
-    return frame
-end)
-
--- Collapsible header: text + toggle arrow (far right)
-local ARROW_SIZE = 16
-local ARROW_DOWN = -math.pi / 2
-local ARROW_UP = math.pi / 2
-
-Layout:RegisterWidgetType("collapseheader", function(container, def)
-    local frame = CreateFrame("Button", nil, container)
-    frame:SetHeight(30)
-    frame.OrbitType = "CollapseHeader"
-
-    local text = frame:CreateFontString(nil, "ARTWORK", "GameFontNormalHuge")
-    text:SetPoint("LEFT", 0, 0)
-    text:SetText(def.text or "")
-
-    local arrow = frame:CreateTexture(nil, "ARTWORK")
-    arrow:SetSize(ARROW_SIZE, ARROW_SIZE)
-    arrow:SetPoint("RIGHT", frame, "RIGHT", 0, 0)
-    arrow:SetAtlas("shop-header-arrow")
-    arrow:SetRotation(def.collapsed and ARROW_DOWN or ARROW_UP)
-
-    frame:SetScript("OnClick", function()
-        if def.onToggle then def.onToggle() end
-    end)
-    frame:SetScript("OnEnter", function()
-        arrow:SetAtlas("shop-header-arrow-hover")
-        arrow:SetRotation(def.collapsed and ARROW_DOWN or ARROW_UP)
-    end)
-    frame:SetScript("OnLeave", function()
-        arrow:SetAtlas("shop-header-arrow")
-        arrow:SetRotation(def.collapsed and ARROW_DOWN or ARROW_UP)
-    end)
-    frame:SetScript("OnMouseDown", function()
-        arrow:SetAtlas("shop-header-arrow-pressed")
-        arrow:SetRotation(def.collapsed and ARROW_DOWN or ARROW_UP)
-    end)
-    frame:SetScript("OnMouseUp", function()
-        arrow:SetAtlas("shop-header-arrow-hover")
-        arrow:SetRotation(def.collapsed and ARROW_DOWN or ARROW_UP)
-    end)
-
-    return frame
-end)
-
--- Header with checkbox toggle on the right (same visual as collapseheader)
-local CHECKBOX_SIZE = 20
-
-Layout:RegisterWidgetType("checkheader", function(container, def, getValue, callback)
-    local frame = CreateFrame("Frame", nil, container)
-    frame:SetHeight(30)
-    frame.OrbitType = "CheckHeader"
-    local text = frame:CreateFontString(nil, "ARTWORK", "GameFontNormalHuge")
-    text:SetPoint("LEFT", 0, 0)
-    text:SetText(def.text or "")
-    local cb = CreateFrame("CheckButton", nil, frame, "UICheckButtonTemplate")
-    cb:SetSize(CHECKBOX_SIZE, CHECKBOX_SIZE)
-    cb:SetPoint("RIGHT", frame, "RIGHT", 0, 0)
-    local checked = getValue and getValue() or false
-    cb:SetChecked(checked)
-    cb:SetScript("OnClick", function(self)
-        if callback then callback(self:GetChecked()) end
-    end)
-    if def.tooltip then
-        cb:SetScript("OnEnter", function(self)
-            GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
-            GameTooltip:SetText(def.tooltip)
-            GameTooltip:Show()
-        end)
-        cb:SetScript("OnLeave", function() GameTooltip:Hide() end)
-    end
-    return frame
-end)
-
-
-local profilesSubView = nil -- nil, "export", "import", "clone", "delete"
-local exportSelectedProfile = nil
-local exportString = ""
-local importString = ""
-local importName = ""
-local cloneSource = nil
-local cloneName = ""
-local deleteTarget = nil
-
-local ProfilesPlugin = {
-    name = "OrbitProfiles",
-    settings = {},
-    GetSetting = function(self, systemIndex, key)
-        if key == "ActiveProfile" then return Orbit.Profile:GetActiveProfileName()
-        elseif key == "UseSpecProfiles" then return Orbit.Profile:IsSpecProfilesEnabled()
-        elseif key == "CreateProfile" then return Orbit.Profile._selectedToCreate or "Global"
-        elseif key == "ExportProfile" then return exportSelectedProfile or "Global"
-        elseif key == "ExportString" then return exportString
-        elseif key == "ImportString" then return importString
-        elseif key == "ImportName" then return importName
-        elseif key == "CloneName" then return cloneName
-        end
-        local specID = key:match("^SpecMapping_(%d+)$")
-        if specID then return Orbit.Profile:GetProfileForSpec(tonumber(specID)) or "Global" end
-        return nil
-    end,
-    SetSetting = function(self, systemIndex, key, value)
-        if key == "ActiveProfile" then
-            Orbit.Profile:SetActiveProfile(value)
-            if Orbit.OptionsPanel then
-                Orbit.OptionsPanel.lastTab = nil
-                Orbit.OptionsPanel:Open("Profiles")
-            end
-        elseif key == "UseSpecProfiles" then
-            Orbit.Profile:SetSpecProfilesEnabled(value)
-            if value then
-                Orbit.Profile:CheckSpecProfile()
-            else
-                if Orbit.db.specMappings then wipe(Orbit.db.specMappings) end
-            end
-            if Orbit.OptionsPanel then Orbit.OptionsPanel.lastTab = nil; Orbit.OptionsPanel.currentTab = nil; Orbit.OptionsPanel:Open("Profiles") end
-        elseif key == "CreateProfile" then
-            Orbit.Profile._selectedToCreate = value
-        elseif key == "ExportProfile" then
-            exportSelectedProfile = value
-            local str = Orbit.Profile:ExportSingleProfile(value)
-            exportString = str or ""
-            if Orbit.OptionsPanel then Orbit.OptionsPanel.lastTab = nil; Orbit.OptionsPanel:Open("Profiles") end
-        elseif key == "ExportString" then
-            exportString = value or ""
-        elseif key == "ImportString" then
-            importString = value or ""
-        elseif key == "ImportName" then
-            importName = value or ""
-        elseif key == "CloneName" then
-            cloneName = value or ""
-        else
-            local specID = key:match("^SpecMapping_(%d+)$")
-            if specID then
-                specID = tonumber(specID)
-                local profileName = (value == "Global") and nil or value
-                Orbit.Profile:SetProfileForSpec(specID, profileName)
-                local _, specName = GetSpecializationInfoByID(specID)
-                Orbit:Print("Spec '" .. (specName or "?") .. "' “å
-                Orbit.Profile:CheckSpecProfile()
-            end
-        end
-    end,
-    ApplySettings = function(self, systemFrame) end,
+Panel._helpers = {
+    RefreshAllPreviews = RefreshAllPreviews,
+    CreateGlobalSettingsPlugin = CreateGlobalSettingsPlugin,
 }
 
-local function GetProfileOptions()
-    local opts = {}
-    for _, n in ipairs(Orbit.Profile:GetProfiles()) do
-        table.insert(opts, { text = n, value = n })
-    end
-    return opts
-end
-
-local function GetProfileOptionsWithDefault()
-    local opts = { { text = "Global", value = "Global" } }
-    for _, n in ipairs(Orbit.Profile:GetProfiles()) do
-        if n ~= "Global" then
-            table.insert(opts, { text = n, value = n })
-        end
-    end
-    return opts
-end
-
-
-local flashLabel
-Layout:RegisterWidgetType("statusmessage", function(container, def)
-    local frame = CreateFrame("Frame", nil, container)
-    frame:SetHeight(20)
-    frame.OrbitType = "StatusMessage"
-    local text = frame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
-    text:SetPoint("CENTER")
-    text:SetTextColor(1, 0.3, 0.3)
-    text:SetText("")
-    frame.Text = text
-    flashLabel = frame
-    return frame
-end)
-
-local flashActive = false
-local function ShowFlashMessage(msg)
-    if not flashLabel or flashActive then return end
-    flashActive = true
-    flashLabel.Text:SetText(msg)
-    flashLabel:SetAlpha(0)
-    flashLabel:Show()
-    UIFrameFadeIn(flashLabel, 0.2, 0, 1)
-    C_Timer.After(2, function()
-        if not flashLabel then flashActive = false; return end
-        UIFrameFadeOut(flashLabel, 0.5, 1, 0)
-        C_Timer.After(0.5, function() flashActive = false end)
-    end)
-end
-
-local function ReopenProfiles()
-    if Orbit.OptionsPanel then
-        Orbit.OptionsPanel.lastTab = nil
-        Orbit.OptionsPanel.currentTab = nil
-        Orbit.OptionsPanel:Open("Profiles")
-    end
-end
-
-local function GetProfilesSchema()
-    -- Export sub-view
-    if profilesSubView == "export" then
-        return {
-            hideNativeSettings = true,
-            hideResetButton = true,
-            controls = {
-                { type = "header", text = "Export Profile" },
-                { type = "dropdown", key = "ExportProfile", label = "Profile", options = GetProfileOptions, default = "Global", width = DROPDOWN_WIDTH },
-                { type = "spacer", height = SPACER_SMALL },
-                { type = "editbox", key = "ExportString", label = "Export String", height = 120, multiline = true, readOnly = true, hideScrollBar = true },
-                { type = "spacer", height = SPACER_SMALL },
-            },
-            extraButtons = {
-                {
-                    text = "\194\171 Back",
-                    callback = function() profilesSubView = nil; exportString = ""; exportSelectedProfile = nil; ReopenProfiles() end,
-                },
-            },
-        }
-    end
-
-    -- Import sub-view
-    if profilesSubView == "import" then
-        return {
-            hideNativeSettings = true,
-            hideResetButton = true,
-            controls = {
-                { type = "header", text = "Import Profile" },
-                { type = "editbox", key = "ImportString", label = "Paste String", height = 120, multiline = true, hideScrollBar = true },
-                { type = "statusmessage" },
-                { type = "editbox", key = "ImportName", label = "Profile Name", height = 50 },
-            },
-            extraButtons = {
-                {
-                    text = "\194\171 Back",
-                    callback = function() profilesSubView = nil; importString = ""; importName = ""; ReopenProfiles() end,
-                },
-                {
-                    text = "Apply",
-                    callback = function()
-                        if importString == "" then ShowFlashMessage("Please paste an import string."); return end
-                        if importName == "" then ShowFlashMessage("Please enter a profile name."); return end
-                        for _, n in ipairs(Orbit.Profile:GetProfiles()) do
-                            if n == importName then ShowFlashMessage("A profile named '" .. importName .. "' already exists."); return end
-                        end
-                        local ok, err = Orbit.Profile:ImportProfile(importString, importName)
-                        if ok then
-                            Orbit:Print("Import successful.")
-                            profilesSubView = nil
-                            importString = ""
-                            importName = ""
-                            ReopenProfiles()
-                        else
-                            ShowFlashMessage("Invalid Import String.")
-                        end
-                    end,
-                },
-            },
-        }
-    end
-
-    -- Clone sub-view
-    if profilesSubView == "clone" then
-        return {
-            hideNativeSettings = true,
-            hideResetButton = true,
-            controls = {
-                { type = "header", text = "Clone Profile" },
-                { type = "description", text = "Create a copy of '" .. (cloneSource or "?") .. "' with a new name." },
-                { type = "spacer", height = SPACER_SMALL },
-                { type = "editbox", key = "CloneName", label = "New Name", height = 50 },
-            },
-            extraButtons = {
-                { text = "\194\171 Back", callback = function() profilesSubView = nil; cloneName = ""; cloneSource = nil; ReopenProfiles() end },
-                {
-                    text = "Create",
-                    callback = function()
-                        if cloneName == "" then Orbit:Print("Please enter a profile name."); return end
-                        local created = Orbit.Profile:CreateProfile(cloneName, cloneSource)
-                        if created then
-                            Orbit:Print("Created profile '" .. cloneName .. "' (from '" .. (cloneSource or "?") .. "')")
-                            profilesSubView = nil; cloneName = ""; cloneSource = nil
-                            ReopenProfiles()
-                        else
-                            Orbit:Print("A profile named '" .. cloneName .. "' already exists.")
-                        end
-                    end,
-                },
-            },
-        }
-    end
-
-    -- Delete sub-view
-    if profilesSubView == "delete" then
-        return {
-            hideNativeSettings = true,
-            hideResetButton = true,
-            controls = {
-                { type = "header", text = "Delete Profile" },
-                { type = "description", text = "|cFFFF0000WARNING:|r You are about to delete '" .. (deleteTarget or "?") .. "'.\n\nThis cannot be undone." },
-            },
-            extraButtons = {
-                { text = "\194\171 Back", callback = function() profilesSubView = nil; deleteTarget = nil; ReopenProfiles() end },
-                {
-                    text = "Delete",
-                    callback = function()
-                        if deleteTarget then
-                            Orbit.Profile:DeleteProfile(deleteTarget)
-                            Orbit:Print("Deleted profile '" .. deleteTarget .. "'")
-                        end
-                        profilesSubView = nil; deleteTarget = nil
-                        ReopenProfiles()
-                    end,
-                },
-            },
-        }
-    end
-
-    -- Reset Global sub-view
-    if profilesSubView == "resetglobal" then
-        return {
-            hideNativeSettings = true,
-            hideResetButton = true,
-            controls = {
-                { type = "header", text = "Reset Global Profile" },
-                { type = "description", text = "|cFFFF0000WARNING:|r You are about to reset the 'Global' profile back to its factory defaults.\n\nAll customizations on this profile will be lost. Your other profiles will not be affected.\n\nThis cannot be undone." },
-            },
-            extraButtons = {
-                { text = "\194\171 Back", callback = function() profilesSubView = nil; ReopenProfiles() end },
-                {
-                    text = "Reset to Defaults",
-                    callback = function()
-                        Orbit.API:ResetProfile("Global")
-                        profilesSubView = nil
-                        ReopenProfiles()
-                    end,
-                },
-            },
-        }
-    end
-
-    -- Main profiles view
-    local controls = {}
-
-    controls[#controls + 1] = { type = "header", text = "Profile" }
-    controls[#controls + 1] = {
-        type = "profileactive", key = "ActiveProfile", label = "Active",
-        options = GetProfileOptions, default = "Global", width = DROPDOWN_WIDTH,
-    }
-    controls[#controls + 1] = {
-        type = "profileselect", key = "CreateProfile", label = "Manage",
-        options = GetProfileOptions, default = "Global",
-        onReset = function(selected)
-            if selected == "Global" then profilesSubView = "resetglobal"; ReopenProfiles()
-            else Orbit:Print("Only the Global profile can be reset.") end
-        end,
-        onCopy = function(selected)
-            cloneSource = selected
-            cloneName = ""
-            profilesSubView = "clone"
-            ReopenProfiles()
-        end,
-        onDelete = function(selected)
-            if selected == "Global" then Orbit:Print("Cannot delete the Global profile."); return end
-            if selected == Orbit.Profile:GetActiveProfileName() then Orbit:Print("Cannot delete the active profile."); return end
-            deleteTarget = selected
-            profilesSubView = "delete"
-            ReopenProfiles()
-        end,
-    }
-    controls[#controls + 1] = { type = "spacer", height = SPACER_LARGE }
-
-    -- Spec Profiles (header with checkbox toggle)
-    controls[#controls + 1] = {
-        type = "checkheader", key = "UseSpecProfiles", text = "Spec Profiles", default = false,
-        tooltip = "Assign a profile to each specialization. When you change spec, Orbit switches automatically.",
-    }
-    if Orbit.Profile:IsSpecProfilesEnabled() then
-        local numSpecs = GetNumSpecializations and GetNumSpecializations() or 0
-        for i = 1, numSpecs do
-            local specID, specName = GetSpecializationInfo(i)
-            if specID and specName then
-                controls[#controls + 1] = {
-                    type = "dropdown", key = "SpecMapping_" .. specID, label = specName,
-                    options = GetProfileOptionsWithDefault, default = "Global", width = DROPDOWN_WIDTH,
-                }
-            end
-        end
-    end
-    controls[#controls + 1] = { type = "spacer", height = SPACER_SMALL }
-
-    return {
-        hideNativeSettings = true,
-        hideResetButton = true,
-        controls = controls,
-        extraButtons = {
-            { text = "Export", callback = function()
-                local active = Orbit.Profile:GetActiveProfileName()
-                exportSelectedProfile = active
-                exportString = Orbit.Profile:ExportSingleProfile(active) or ""
-                profilesSubView = "export"
-                ReopenProfiles()
-            end },
-            { text = "Import", callback = function() profilesSubView = "import"; ReopenProfiles() end },
-        },
-    }
-end
-
-
-
 -- [ MAIN LOGIC ]------------------------------------------------------------------------------------
 
-local TABS = {
-    { name = "Global", plugin = GlobalPlugin, schema = GetGlobalSchema },
-    { name = "Colors", plugin = ColorsPlugin, schema = GetColorsSchema },
-    { name = "Edit Mode", plugin = EditModePlugin, schema = GetEditModeSchema },
-    { name = "Profiles", plugin = ProfilesPlugin, schema = GetProfilesSchema },
-}
-
 function Panel:Open(tabName)
     if InCombatLockdown() then return end
     local dialog = Orbit.SettingsDialog
     if not dialog then return end
 
-    tabName = tabName or self.lastTab or TABS[1].name
+    tabName = tabName or self.lastTab or TAB_ORDER[1]
 
-    local tabDef = nil
-    for _, t in ipairs(TABS) do
-        if t.name == tabName then tabDef = t; break end
-    end
-    tabDef = tabDef or TABS[1]
+    local resolvedName = self.Tabs[tabName] and tabName or TAB_ORDER[1]
+    local tabDef = self.Tabs[resolvedName]
+    if not tabDef then return end
 
-    if dialog:IsShown() and self.lastTab == tabDef.name and dialog.Title and dialog.Title:GetText() == "Orbit Options" then
+    if dialog:IsShown() and self.lastTab == resolvedName and dialog.Title and dialog.Title:GetText() == "Orbit Options" then
         return
     end
 
@@ -942,27 +79,24 @@ function Panel:Open(tabName)
         EditModeSystemSettingsDialog:Hide()
     end
 
-    self.lastTab = tabDef.name
-    dialog.orbitCurrentTab = tabDef.name
+    self.lastTab = resolvedName
+    dialog.orbitCurrentTab = resolvedName
     dialog.attachedPlugin = nil
     dialog:Show()
 
     if dialog.Title then dialog.Title:SetText("Orbit Options") end
 
-    local tabNames = {}
-    for _, t in ipairs(TABS) do tabNames[#tabNames + 1] = t.name end
-
     local schema = tabDef.schema()
     table.insert(schema.controls, 1, {
-        type = "tabs", tabs = tabNames, activeTab = tabDef.name,
+        type = "tabs", tabs = TAB_ORDER, activeTab = resolvedName,
         onTabSelected = function(newTab) Panel:Open(newTab) end,
     })
 
     local mockFrame = CreateFrame("Frame")
     mockFrame.systemIndex = 1
-    mockFrame.system = "Orbit_" .. tabDef.name
+    mockFrame.system = "Orbit_" .. resolvedName
 
-    Config:Render(dialog, mockFrame, tabDef.plugin, schema, tabDef.name)
+    Config:Render(dialog, mockFrame, tabDef.plugin, schema, resolvedName)
     dialog:PositionNearButton()
 end
 
@@ -997,8 +131,9 @@ function Panel:Toggle(tab)
         dialog.Title:SetText("Profiles - " .. Orbit.Profile:GetActiveProfileName())
     end
 
-    if tab == "Profiles" then
-        Config:Render(dialog, systemFrame, ProfilesPlugin, GetProfilesSchema(), "Profiles")
+    local tabDef = self.Tabs[tab]
+    if tab == "Profiles" and tabDef then
+        Config:Render(dialog, systemFrame, tabDef.plugin, tabDef.schema(), "Profiles")
     end
 
     dialog:Show()
@@ -1013,5 +148,3 @@ function Panel:Refresh()
         self:Open(tabToRefresh)
     end
 end
-
-
diff --git a/Orbit/Core/Config/README.md b/Orbit/Core/Config/README.md
index 99856d5..0c96155 100644
--- a/Orbit/Core/Config/README.md
+++ b/Orbit/Core/Config/README.md
@@ -6,23 +6,51 @@ the schema-driven configuration ui. renders settings panels from declarative sch
 
 provides the entire settings interface for orbit. plugins declare their settings as schemas (using schemabuilder), and config renders them as tabs with controls (sliders, checkboxes, dropdowns, color pickers).
 
+## layout
+
+```
+Config/
+  Schema/    -- schema declaration + rendering pipeline
+  Panels/    -- the Orbit Options dialog shell + tab content
+    Tabs/    -- one file per tab (Global, Colors, Edit Mode, Profiles)
+  Entry/     -- user entry points (slash command, minimap button, addon compartment button)
+  Advanced/  -- addon-settings-panel content builders (plugin manager, visibility, QoL)
+  Widgets/   -- individual control widgets
+  WhatsNew.lua / ChangelogData.lua
+```
+
 ## files
 
 | file | responsibility |
 |---|---|
-| SchemaBuilder.lua | schema declaration api: `AddTab`, `AddSlider`, `AddCheckbox`, `AddDropdown`, `AddColorCurve`, etc. |
-| ConfigRenderer.lua | renders a schema into ui frames. walks the schema tree and creates widgets. |
-| ConfigLayout.lua | layout engine for settings panels (3-column grid, spacing, tab bar). |
-| OrbitOptionsPanel.lua | main orbit settings panel. tab navigation. Global tab exposes font, border (size, style, edge size, offset), and `IconBorderStyle` (action bars/cooldown icons only). Colors tab exposes textures, color curves, and `BorderColor`. Border Edge Size and Border Offset are conditionally hidden when Border Style is "Orbit Pixel" (flat). |
-| SlashCommands.lua | `/orbit` slash command handler, confirmation popups, and debug utilities (help, version, profile, frames, inspect). |
-| OrbitSettingsDialog.lua | settings dialog frame. hosts the tab bar and content area. |
-| OrbitOptionsButton.lua | minimap/addon compartment button. |
-| MinimapButton.lua | LibDBIcon minimap button. left-click opens edit mode + options, right-click opens plugin manager. |
-| OrbitAdvancedSettings.lua | orchestrator: tab bar, panel shell, settings registration for the addon settings panel. |
+| Schema/SchemaBuilder.lua | schema declaration api: `AddTab`, `AddSlider`, `AddCheckbox`, `AddDropdown`, `AddColorCurve`, etc. |
+| Schema/ConfigRenderer.lua | renders a schema into ui frames. walks the schema tree and creates widgets. |
+| Schema/ConfigLayout.lua | layout engine for settings panels (3-column grid, spacing, tab bar). |
+| Panels/OrbitOptionsPanel.lua | dialog shell only: tab registry, open/hide/toggle/refresh lifecycle, and `Panel._helpers` (shared `CreateGlobalSettingsPlugin` / `RefreshAllPreviews` used by tab files). |
+| Panels/OrbitSettingsDialog.lua | settings dialog frame. hosts the tab bar and content area. |
+| Panels/OrbitAdvancedSettings.lua | orchestrator: tab bar, panel shell, settings registration for the addon settings panel. |
+| Panels/Tabs/GlobalTab.lua | Global tab schema: font, border (size, style, edge size, offset), `IconBorderStyle`. Border Edge Size and Border Offset are conditionally hidden when Border Style is "Orbit Pixel". |
+| Panels/Tabs/ColorsTab.lua | Colors tab schema: textures, color curves (font/bar/backdrop), `BorderColor`, `IconBorderColor`. |
+| Panels/Tabs/EditModeTab.lua | Edit Mode tab schema: show/hide blizzard frames, anchoring, edit mode color curve. |
+| Panels/Tabs/ProfilesTab.lua | Profiles tab schema + sub-views (export/import/clone/delete/reset). owns widget registrations only used here (`profileactive`, `profileselect`, `collapseheader`, `checkheader`, `statusmessage`). |
+| Entry/SlashCommands.lua | `/orbit` slash command handler, confirmation popups, and debug utilities (help, version, profile, frames, inspect). |
+| Entry/OrbitOptionsButton.lua | addon compartment button. |
+| Entry/MinimapButton.lua | LibDBIcon minimap button. left-click opens edit mode + options, right-click opens plugin manager. |
 | Advanced/PluginManager.lua | plugin enable/disable checkbox grid content builder. |
 | Advanced/VisibilityEngine.lua | visibility engine scrollable table content builder. |
 | Advanced/QoL.lua | quality-of-life expandable accordion sections content builder. |
 | Widgets/ | individual control widgets (slider, checkbox, dropdown, color picker, font/texture pickers, etc.). |
+| WhatsNew.lua / ChangelogData.lua | post-update changelog popup. |
+
+## adding a new tab
+
+1. create `Panels/Tabs/MyTab.lua`
+2. pull `local Panel = Orbit.OptionsPanel` and `local helpers = Panel._helpers`
+3. build a plugin via `helpers.CreateGlobalSettingsPlugin("OrbitMyTab")` (or a custom table if you need bespoke GetSetting/SetSetting)
+4. declare a `schema()` function returning the schema tree
+5. register with `Panel.Tabs["My Tab"] = { plugin = ..., schema = ... }`
+6. add `"My Tab"` to `TAB_ORDER` in `OrbitOptionsPanel.lua`
+7. add `<Script file="Panels\Tabs\MyTab.lua"/>` to `Config.xml` **after** `OrbitOptionsPanel.lua` (tab files reference `Panel._helpers` at load time)
 
 ## adding a new widget type
 
diff --git a/Orbit/Core/Config/WhatsNew.lua b/Orbit/Core/Config/WhatsNew.lua
index 548c0e2..d8b8d82 100644
--- a/Orbit/Core/Config/WhatsNew.lua
+++ b/Orbit/Core/Config/WhatsNew.lua
@@ -35,7 +35,7 @@ local FOOTER_TOTAL = FOOTER_HEIGHT + FOOTER_TEXT_HEIGHT
 
 -- [ FRAME ]------------------------------------------------------------------------
 
-local Window = CreateFrame("Frame", "OrbitWhatsNewWindow", UIParent, "DefaultPanelTemplate")
+local Window = CreateFrame("Frame", "OrbitWhatsNewWindow", UIParent)
 Window:SetSize(WINDOW_WIDTH, MAX_HEIGHT)
 Window:SetPoint("CENTER")
 Window:SetFrameStrata(FRAME_STRATA)
@@ -46,6 +46,17 @@ Window:EnableMouse(true)
 Window:RegisterForDrag("LeftButton")
 Window:Hide()
 
+Window.NineSlice = CreateFrame("Frame", nil, Window, "NineSlicePanelTemplate")
+Window.NineSlice.layoutType = "ButtonFrameTemplateNoPortrait"
+NineSliceUtil.ApplyLayoutByName(Window.NineSlice, "ButtonFrameTemplateNoPortrait")
+
+Window.Bg = Window:CreateTexture(nil, "BACKGROUND", nil, -6)
+Window.Bg:SetTexture("Interface\\FrameGeneral\\UI-Background-Rock")
+Window.Bg:SetHorizTile(true)
+Window.Bg:SetVertTile(true)
+Window.Bg:SetPoint("TOPLEFT", 6, -21)
+Window.Bg:SetPoint("BOTTOMRIGHT", -2, 2)
+
 Window:SetScript("OnDragStart", function(self) self:StartMoving() end)
 Window:SetScript("OnDragStop", function(self)
     self:StopMovingOrSizing()
@@ -56,13 +67,21 @@ end)
 
 -- [ TITLE ]------------------------------------------------------------------------
 
+Window.TitleContainer = CreateFrame("Frame", nil, Window)
+Window.TitleContainer:SetFrameLevel(FRAME_LEVEL + 10)
+Window.TitleContainer:SetPoint("TOPLEFT", 30, -1)
+Window.TitleContainer:SetPoint("TOPRIGHT", -24, -1)
+Window.TitleContainer:SetHeight(20)
+
+Window.TitleContainer.TitleText = Window.TitleContainer:CreateFontString(nil, "OVERLAY", "GameFontNormal")
+Window.TitleContainer.TitleText:SetPoint("TOP", Window.TitleContainer, "TOP", 0, -5)
 Window.TitleContainer.TitleText:SetText("|cFFFFD100Orbit: What's New|r")
 
 -- [ CLOSE BUTTON ]-----------------------------------------------------------------
 
-Window.CloseButton = CreateFrame("Button", nil, Window.NineSlice, "UIPanelCloseButton")
-Window.CloseButton:SetPoint("TOPRIGHT", Window, "TOPRIGHT", CLOSE_BUTTON_OFFSET, CLOSE_BUTTON_OFFSET)
-Window.CloseButton:SetFrameLevel(520)
+Window.CloseButton = CreateFrame("Button", nil, Window, "UIPanelCloseButton")
+Window.CloseButton:SetPoint("TOPRIGHT", Window, "TOPRIGHT", 0, -1)
+Window.CloseButton:SetFrameLevel(FRAME_LEVEL + 10)
 Window.CloseButton:SetScript("OnClick", function() Window:Hide() end)
 
 -- [ FOOTER ]-----------------------------------------------------------------------
diff --git a/Orbit/Core/EditMode/Frame/Position/Persistence.lua b/Orbit/Core/EditMode/Frame/Position/Persistence.lua
index e98d8c0..95b3843 100644
--- a/Orbit/Core/EditMode/Frame/Position/Persistence.lua
+++ b/Orbit/Core/EditMode/Frame/Position/Persistence.lua
@@ -55,6 +55,140 @@ function Persistence:DrainAllPending()
     end
 end
 
+-- [ SPEC-SCOPED ANCHOR ROUTING ]---------------------------------------------------------------------
+-- A plugin's saved Anchor/Position normally lives in the global layout DB
+-- (one value across all specs). That works fine when both ends of the chain
+-- are stable across specs, but breaks when the TARGET frame is per-spec “Ç
+-- a Tracked container, where SpecA owns OrbitTrackedContainer1042 and SpecB
+-- owns OrbitTrackedContainer1043. A non-spec-scoped consumer like the Player
+-- frame can name only ONE of those targets in its global Anchor field, so the
+-- chain renders on one spec and silently routes around the other.
+--
+-- Solution: when the target frame opts in via `orbitAnchorTargetPerSpec = true`,
+-- the consumer's anchor is partitioned per-spec via `PluginMixin:SetSpecData`
+-- regardless of whether the consumer plugin itself is spec-scoped. The consumer
+-- doesn't have to know the target is per-spec “Ç
+-- Reads always try spec data first and fall back to global, so plugins that
+-- never wrote spec data are unaffected.
+--
+-- Save rules:
+--   * Built-in spec-scoped consumer (`IsSpecScopedIndex` true) “å
+--     never touch global. Existing CooldownManager behavior preserved.
+--   * Anchor target has `orbitAnchorTargetPerSpec` “å
+--     spec. Global is left intact so OTHER specs that have no override fall
+--     back to it (e.g. user wires the chain on SpecA only and SpecB keeps
+--     whatever the global was before).
+--   * Otherwise “å
+--     takes effect on this spec.
+--
+-- Position writes (no target, free positioning) follow the same shape but
+-- decide via "stickiness": if the consumer already has spec data for the
+-- current spec, the new free position stays per-spec for this spec. Without
+-- this, dragging a per-spec-anchored frame off into open space on SpecA
+-- would silently overwrite the chain on every spec.
+--
+-- Plugin opt-out: a plugin whose own SetSetting/GetSetting already partition
+-- per-spec at the record level (Tracked, where each record carries `spec`)
+-- sets `plugin.settingsArePerSpec = true`. For those plugins we never route
+-- to spec data “Ç
+-- second per-spec layer would silently desync the two on subsequent edits
+-- and lose the data on profile export (spec data is per-character, record
+-- settings are in the profile).
+local function HasGetSpecData(plugin)
+    if not plugin then return false end
+    if plugin.settingsArePerSpec then return false end
+    return plugin.GetSpecData and plugin.SetSpecData
+end
+
+local function IsBuiltinSpecScoped(plugin, systemIndex)
+    return plugin.IsSpecScopedIndex and plugin:IsSpecScopedIndex(systemIndex)
+end
+
+local function TargetIsPerSpec(anchor)
+    if not anchor or not anchor.target then return false end
+    local targetFrame = _G[anchor.target]
+    return targetFrame and targetFrame.orbitAnchorTargetPerSpec == true
+end
+
+local function HasCurrentSpecData(plugin, systemIndex)
+    if not HasGetSpecData(plugin) then return false end
+    return plugin:GetSpecData(systemIndex, "Anchor") ~= nil
+        or plugin:GetSpecData(systemIndex, "Position") ~= nil
+end
+
+-- Only clear an existing spec-data slot “Ç
+-- already nil, since SetSpecData lazily creates parent tables and would leave
+-- empty {} entries behind for plugins that have GetSpecData but never use it.
+local function ClearSpecDataIfPresent(plugin, systemIndex, key)
+    if plugin:GetSpecData(systemIndex, key) ~= nil then
+        plugin:SetSpecData(systemIndex, key, nil)
+    end
+end
+
+function Persistence:WriteAnchor(plugin, systemIndex, anchor)
+    if not plugin or not systemIndex or not anchor then return end
+    local useSpec = HasGetSpecData(plugin) and (IsBuiltinSpecScoped(plugin, systemIndex) or TargetIsPerSpec(anchor))
+    if useSpec then
+        plugin:SetSpecData(systemIndex, "Anchor", anchor)
+        plugin:SetSpecData(systemIndex, "Position", nil)
+        return
+    end
+    if plugin.SetSetting then
+        plugin:SetSetting(systemIndex, "Anchor", anchor)
+        plugin:SetSetting(systemIndex, "Position", nil)
+    end
+    if HasGetSpecData(plugin) then
+        ClearSpecDataIfPresent(plugin, systemIndex, "Anchor")
+        ClearSpecDataIfPresent(plugin, systemIndex, "Position")
+    end
+end
+
+function Persistence:WritePosition(plugin, systemIndex, pos)
+    if not plugin or not systemIndex or not pos then return end
+    local useSpec = HasGetSpecData(plugin) and (IsBuiltinSpecScoped(plugin, systemIndex) or HasCurrentSpecData(plugin, systemIndex))
+    if useSpec then
+        plugin:SetSpecData(systemIndex, "Position", pos)
+        plugin:SetSpecData(systemIndex, "Anchor", false)
+        return
+    end
+    if plugin.SetSetting then
+        plugin:SetSetting(systemIndex, "Position", pos)
+        plugin:SetSetting(systemIndex, "Anchor", false)
+    end
+    if HasGetSpecData(plugin) then
+        ClearSpecDataIfPresent(plugin, systemIndex, "Position")
+        ClearSpecDataIfPresent(plugin, systemIndex, "Anchor")
+    end
+end
+
+-- ReadAnchor distinguishes nil from false: nil = "no spec override, fall back
+-- to global", false = "this spec is intentionally using a Position, don't
+-- consult global". The caller (`if anchor and anchor.target`) skips the anchor
+-- branch on false and reads ReadPosition, which returns the spec position.
+function Persistence:ReadAnchor(plugin, systemIndex)
+    if not plugin or not systemIndex then return nil end
+    if HasGetSpecData(plugin) then
+        local v = plugin:GetSpecData(systemIndex, "Anchor")
+        if v ~= nil then return v end
+    end
+    if plugin.GetSetting then
+        return plugin:GetSetting(systemIndex, "Anchor")
+    end
+    return nil
+end
+
+function Persistence:ReadPosition(plugin, systemIndex)
+    if not plugin or not systemIndex then return nil end
+    if HasGetSpecData(plugin) then
+        local v = plugin:GetSpecData(systemIndex, "Position")
+        if v ~= nil then return v end
+    end
+    if plugin.GetSetting then
+        return plugin:GetSetting(systemIndex, "Position")
+    end
+    return nil
+end
+
 -- Restore frame position or anchor from plugin settings
 function Persistence:RestorePosition(frame, plugin, systemIndex)
     if not frame or not plugin then
@@ -122,8 +256,11 @@ function Persistence:RestorePosition(frame, plugin, systemIndex)
         end
     end
 
-    -- Try to restore SavedVariables Anchor first
-    local anchor = plugin:GetSetting(systemIndex, "Anchor")
+    -- Try to restore SavedVariables Anchor first. ReadAnchor checks the
+    -- current spec's per-spec store before falling back to the global plugin
+    -- setting, so plugins whose target is per-spec (Tracked containers) load
+    -- the right anchor for the current spec.
+    local anchor = self:ReadAnchor(plugin, systemIndex)
     if anchor and anchor.target then
         local targetFrame = _G[anchor.target]
         if targetFrame then
@@ -144,8 +281,8 @@ function Persistence:RestorePosition(frame, plugin, systemIndex)
         end
     end
 
-    -- Restore Position
-    local pos = plugin:GetSetting(systemIndex, "Position")
+    -- Restore Position (also spec-data-aware via ReadPosition)
+    local pos = self:ReadPosition(plugin, systemIndex)
     if pos and pos.point then
         if Engine.FrameAnchor and Engine.FrameAnchor:GetAnchorParent(frame) then
             Engine.FrameAnchor:BreakAnchor(frame, true)
@@ -183,6 +320,21 @@ function Persistence:RestorePosition(frame, plugin, systemIndex)
     return false
 end
 
+-- [ ATTACHED FRAME REGISTRY ] ---------------------------------------------------------------------
+-- Tracks every frame that's been wired through AttachSettingsListener so the
+-- spec-change handler (below) can re-run RestorePosition on consumers whose
+-- saved anchor was routed to per-spec storage by TargetIsPerSpec. Without this
+-- pass, a non-spec-scoped consumer (e.g. PlayerPower) anchored to a per-spec
+-- target (e.g. an OrbitTrackedContainer) keeps its previous-spec graph anchor
+-- after the spec swap “Ç
+-- the visual position lands on whichever ancestor PromoteGrandchild routed it
+-- to instead of the new spec's intended target. Re-restoring picks up the new
+-- spec's saved anchor entry.
+--
+-- Weak-keyed so dropped frames (Tracked container deletion, plugin teardown)
+-- don't pin garbage.
+Persistence._attachedFrames = Persistence._attachedFrames or setmetatable({}, { __mode = "k" })
+
 -- Attach listener for standard Orbit position/anchor saving
 function Persistence:AttachSettingsListener(frame, plugin, systemIndex)
     if not frame or not plugin then
@@ -200,6 +352,8 @@ function Persistence:AttachSettingsListener(frame, plugin, systemIndex)
     frame.orbitPlugin = plugin
     frame.systemIndex = systemIndex
 
+    self._attachedFrames[frame] = { plugin = plugin, systemIndex = systemIndex }
+
     -- Eager anchor seeding: if any child was waiting on this frame's name, wire it now.
     local frameName = frame:GetName()
     if frameName then
@@ -263,19 +417,27 @@ function Persistence:AttachSettingsListener(frame, plugin, systemIndex)
                 Engine.PositionManager:SetPosition(f, point, x, y)
             end
             Engine.PositionManager:MarkDirty(f)
-            -- Immediate write for spec-scoped frames (FlushToStorage's SetSetting chain is unreliable)
+            -- Immediate write for any consumer that routes into per-spec
+            -- storage (built-in spec-scoped plugin OR target frame with
+            -- orbitAnchorTargetPerSpec OR consumer that already has current-
+            -- spec data). FlushToStorage runs on edit-mode close, but a
+            -- /reload between drag-stop and flush would lose spec-scoped
+            -- writes “Ç
+            -- since they're well-handled by SetSetting.
             local p = f.orbitPlugin
             local sysIdx = f.systemIndex
-            if p and sysIdx and p.SetSpecData and p.IsSpecScopedIndex and p:IsSpecScopedIndex(sysIdx) then
-                if point == "ANCHORED" then
-                    local anch = Engine.PositionManager:GetAnchor(f)
-                    if anch then
-                        p:SetSpecData(sysIdx, "Anchor", anch)
-                        p:SetSpecData(sysIdx, "Position", nil)
+            if p and sysIdx and HasGetSpecData(p) then
+                local needsSpecImmediate = IsBuiltinSpecScoped(p, sysIdx) or HasCurrentSpecData(p, sysIdx)
+                if not needsSpecImmediate and point == "ANCHORED" then
+                    needsSpecImmediate = TargetIsPerSpec({ target = x })
+                end
+                if needsSpecImmediate then
+                    if point == "ANCHORED" then
+                        local anch = Engine.PositionManager:GetAnchor(f)
+                        if anch then Persistence:WriteAnchor(p, sysIdx, anch) end
+                    else
+                        Persistence:WritePosition(p, sysIdx, { point = point, x = x, y = y })
                     end
-                else
-                    p:SetSpecData(sysIdx, "Position", { point = point, x = x, y = y })
-                    p:SetSpecData(sysIdx, "Anchor", false)
                 end
             end
         end
@@ -313,6 +475,26 @@ function Persistence:AttachSettingsListener(frame, plugin, systemIndex)
     end)
 end
 
+-- [ SPEC-CHANGE RE-RESTORE ] ----------------------------------------------------------------------
+-- On spec swap, walk every attached frame and re-run RestorePosition for any
+-- consumer whose plugin opts into spec-scoped storage. This is what makes the
+-- TargetIsPerSpec routing actually visible: WriteAnchor saved the consumer's
+-- anchor to spec data when it was dragged onto a per-spec target, and now the
+-- new spec needs that data applied to the live AnchorGraph. Plugins with
+-- `settingsArePerSpec = true` (Tracked) are skipped “Ç
+-- container toggling via RefreshForCurrentSpec / SetContainerActive.
+--
+-- A frame whose plugin has GetSpecData but no entry for the new spec falls
+-- through to global inside RestorePosition (no-op for plain global anchors;
+-- correct fallback for cross-spec consumers that never wrote new-spec data).
+function Persistence:RestoreAffectedBySpecChange()
+    for frame, info in pairs(self._attachedFrames) do
+        if HasGetSpecData(info.plugin) then
+            self:RestorePosition(frame, info.plugin, info.systemIndex)
+        end
+    end
+end
+
 -- Safety net: after all plugins have been loaded by PLAYER_ENTERING_WORLD, make
 -- a final pass over anything still queued. Entries whose target never materialized
 -- (profile references a deleted plugin) remain stashed; DrainPendingFor is a no-op
@@ -321,4 +503,18 @@ if Orbit.EventBus then
     Orbit.EventBus:On("PLAYER_ENTERING_WORLD", function()
         Persistence:DrainAllPending()
     end)
+
+    -- Two-frame defer: PLAYER_SPECIALIZATION_CHANGED dispatches to all EventBus
+    -- listeners synchronously, including Tracked's RefreshForCurrentSpec which
+    -- toggles per-spec containers and schedules ReconcileChain via
+    -- C_Timer.After(0). The chain flush runs at frame+1; we run at frame+2 so
+    -- promotions and SetContainerActive's own RestorePosition calls have all
+    -- settled before we re-anchor consumers to their new-spec targets.
+    Orbit.EventBus:On("PLAYER_SPECIALIZATION_CHANGED", function()
+        C_Timer.After(0, function()
+            C_Timer.After(0, function()
+                Persistence:RestoreAffectedBySpecChange()
+            end)
+        end)
+    end)
 end
diff --git a/Orbit/Core/EditMode/Frame/Selection.lua b/Orbit/Core/EditMode/Frame/Selection.lua
index 119857a..87f8b2b 100644
--- a/Orbit/Core/EditMode/Frame/Selection.lua
+++ b/Orbit/Core/EditMode/Frame/Selection.lua
@@ -191,8 +191,16 @@ function Selection:GetSnapTargets(excludeFrame)
         return false
     end
 
+    -- Skip frames that are skipped in the AnchorGraph (virtual = content-empty,
+    -- or disabled = off-spec / plugin off). Snapping to a skipped frame would
+    -- create an anchor that ReconcileChain immediately promotes away, which
+    -- is confusing UX (the visual drop point isn't where the frame ends up).
+    -- Empty Tracked containers are the canonical case: they stay visible and
+    -- selectable in edit mode but must not be valid anchor targets until they
+    -- have content.
+    local Graph = Engine.AnchorGraph
     for f in pairs(self.selections) do
-        if f ~= excludeFrame and not f:IsForbidden() and f:IsVisible() then
+        if f ~= excludeFrame and not f:IsForbidden() and f:IsVisible() and not Graph:IsSkipped(f) then
             -- Skip children/descendants of the dragged frame
             if not IsDependent(f, excludeFrame) then
                 table.insert(targets, f)
@@ -202,7 +210,7 @@ function Selection:GetSnapTargets(excludeFrame)
 
     if EditModeManagerFrame and EditModeManagerFrame.registeredSystemFrames then
         for _, systemFrame in ipairs(EditModeManagerFrame.registeredSystemFrames) do
-            if systemFrame ~= excludeFrame and systemFrame:IsVisible() and not systemFrame:IsForbidden() and not systemFrame.orbitSnapExclude then
+            if systemFrame ~= excludeFrame and systemFrame:IsVisible() and not systemFrame:IsForbidden() and not systemFrame.orbitSnapExclude and not Graph:IsSkipped(systemFrame) then
                 if not IsDependent(systemFrame, excludeFrame) then
                     table.insert(targets, systemFrame)
                 end
diff --git a/Orbit/Core/EditMode/PositionManager.lua b/Orbit/Core/EditMode/PositionManager.lua
index c52e4b3..32703c9 100644
--- a/Orbit/Core/EditMode/PositionManager.lua
+++ b/Orbit/Core/EditMode/PositionManager.lua
@@ -39,29 +39,20 @@ function PositionManager:MarkDirty(frame)
     if name then PendingFrames[name] = frame end
 end
 
+-- Persistence:WriteAnchor / WritePosition handle the spec-vs-global routing
+-- (built-in spec-scoped plugins, per-spec target frames, sticky free-position
+-- writes). Centralizing the routing here means FlushToStorage just chooses
+-- between anchor and position and lets Persistence decide which store to hit.
 function PositionManager:FlushToStorage()
+    local Persistence = Orbit.Engine.FramePersistence
     for name, frame in pairs(PendingFrames) do
-        if frame and frame.orbitPlugin and frame.orbitPlugin.SetSetting then
+        if frame and frame.orbitPlugin then
             local systemIndex = frame.systemIndex or 1
             local plugin = frame.orbitPlugin
-            -- Spec-scoped frames: call SetSpecData directly (SetSetting override chain breaks from here)
-            local isSpecScoped = plugin.SetSpecData and plugin.IsSpecScopedIndex and plugin:IsSpecScopedIndex(systemIndex)
-            if isSpecScoped then
-                if ActiveAnchors[name] then
-                    plugin:SetSpecData(systemIndex, "Anchor", ActiveAnchors[name])
-                    plugin:SetSpecData(systemIndex, "Position", nil)
-                elseif ActivePositions[name] then
-                    plugin:SetSpecData(systemIndex, "Position", ActivePositions[name])
-                    plugin:SetSpecData(systemIndex, "Anchor", false)
-                end
-            else
-                if ActiveAnchors[name] then
-                    plugin:SetSetting(systemIndex, "Anchor", ActiveAnchors[name])
-                    plugin:SetSetting(systemIndex, "Position", nil)
-                elseif ActivePositions[name] then
-                    plugin:SetSetting(systemIndex, "Position", ActivePositions[name])
-                    plugin:SetSetting(systemIndex, "Anchor", false)
-                end
+            if ActiveAnchors[name] then
+                Persistence:WriteAnchor(plugin, systemIndex, ActiveAnchors[name])
+            elseif ActivePositions[name] then
+                Persistence:WritePosition(plugin, systemIndex, ActivePositions[name])
             end
         end
     end
diff --git a/Orbit/Core/EditMode/README.md b/Orbit/Core/EditMode/README.md
index 4aa8d25..bdbc4ae 100644
--- a/Orbit/Core/EditMode/README.md
+++ b/Orbit/Core/EditMode/README.md
@@ -107,6 +107,22 @@ if the flush fires during combat lockdown, it re-queues via `CombatManager:Queue
 
 entries whose target never materializes (profile references a deleted plugin) remain stashed and become no-ops on future drains.
 
+## per-spec anchor routing
+
+`Persistence:WriteAnchor` / `WritePosition` partition saved anchors per-spec when one of three conditions holds:
+
+- the consumer plugin is built-in spec-scoped (`IsSpecScopedIndex(systemIndex)` true “Ç
+- the **target** frame opts in via `frame.orbitAnchorTargetPerSpec = true` (Tracked containers / bars). this lets a non-spec-scoped consumer (e.g. PlayerPower) hold a separate saved anchor for each spec without the consumer plugin needing to know its target is per-spec.
+- the consumer already has spec data for the current spec (stickiness “Ç
+
+`ReadAnchor` / `ReadPosition` always check the current spec's slot first and fall back to the global plugin setting, so plugins that never wrote spec data are unaffected.
+
+plugins whose `SetSetting`/`GetSetting` already partition per-spec at the record level (Tracked, where each record carries `spec`) set `plugin.settingsArePerSpec = true` to opt OUT of the routing “Ç
+
+### spec-change re-restore
+
+`Persistence._attachedFrames` is a weak-keyed registry populated by `AttachSettingsListener`. on `PLAYER_SPECIALIZATION_CHANGED`, `RestoreAffectedBySpecChange` walks the registry (deferred two frames so Tracked's `RefreshForCurrentSpec` and the subsequent `ReconcileChain` flush both settle first) and re-runs `RestorePosition` for any frame whose plugin has spec-scoped storage. this is what makes per-spec anchor routing visible to the live `AnchorGraph`: without it, the consumer's previous-spec anchor entry persists and width sync still works through `PromoteGrandchild`'s ancestor route, but the visual position lands on the ancestor instead of the new spec's intended target.
+
 ## rules
 
 - edit mode code must work without any specific plugin loaded
diff --git a/Orbit/Core/Onboarding/EditModeTour.lua b/Orbit/Core/Onboarding/EditModeTour.lua
index 06c3ee0..91e1de3 100644
--- a/Orbit/Core/Onboarding/EditModeTour.lua
+++ b/Orbit/Core/Onboarding/EditModeTour.lua
@@ -1,4 +1,6 @@
 -- [ EDIT MODE - GUIDED TOUR (PLAYGROUND) ]-------------------------------------------
+-- Strings live in Orbit/Localization/Domains/Tours.lua under the TOUR_EM_* prefix.
+-- When adding or renaming tour steps here, update the matching keys there.
 ---@type Orbit
 local Orbit = Orbit
 local Engine = Orbit.Engine
@@ -41,219 +43,10 @@ local TOOLTIP_LEVEL = 9500
 local FONT = "GameFontNormalSmall"
 
 -- [ LOCALIZATION ]-------------------------------------------------------------------
-local LOCALE_STRINGS = {
-    enUS = {
-        NEXT = "Next", DONE = "Done",
-        STEP1_TITLE = "Your Frames",
-        STEP1_TEXT = "These are two Orbit frames.\nClick one to select it, then drag\nto reposition.",
-        STEP2_TITLE = "Frame Settings",
-        STEP2_TEXT = "A settings dialog opened!\nTry adjusting the width and height\nof the selected frame.",
-        STEP3_TITLE = "Anchoring",
-        STEP3_TEXT = "Drag the frames together around\nthe edges. Colored guidelines will\nappear showing where the frame\nwill anchor. The anchor direction\ncontrols how frames grow and\nresize together. Drop to anchor,\nor hold Shift for a precision\ndrop without anchoring.",
-        STEP4_TITLE = "Parent & Child",
-        STEP4_TEXT = "When frames are anchored, one\nbecomes the parent and the other\nthe child. Drag the parent - the\nchild follows! Dragging the child\nwill break the anchor.",
-        STEP5_TITLE = "Adjust Distance",
-        STEP5_TEXT = "Select the child frame, then scroll\nthe mouse wheel to change the gap\nbetween it and its parent.\nHold Shift for larger steps.\n\n|cFF66BB66Hint:|r Set Distance to 0 with\nSpacing at 0 and borders will merge\ninto a single shared edge!",
-        STEP6_TITLE = "Arrow Nudge",
-        STEP6_TEXT = "Select the parent frame and use\narrow keys to nudge it 1 pixel\nat a time. Both frames move\ntogether. Shift for 10px jumps.",
-        STEP7_TITLE = "Drag Resize",
-        STEP7_TEXT = "Grab the resize handle in the\nbottom-right corner of a selected\nframe. Drag to resize it within\nthe min/max bounds.\n\n|cFF66BB66Hint:|r Resize is based off your\nanchor position!",
-        STEP8_TITLE = "Orbit Options",
-        STEP8_TEXT = "This is the Orbit Options panel.\nChange your font, textures, colors,\nborders and more. All changes\napply globally across every frame.",
-        STEP9_TITLE = "Explore!",
-        STEP9_TEXT = "To get the most out of Orbit,\njust play around! Drag, drop,\nanchor and resize frames in\nEdit Mode. Every frame is yours\nto customize.",
-        CANVAS_TITLE = "Canvas Mode",
-        CANVAS_TEXT = "Right-click any frame to open\nCanvas Mode. This unlocks deeper\ncustomization per frame.",
-    },
-    deDE = {
-        NEXT = "Weiter", DONE = "Fertig",
-        STEP1_TITLE = "Deine Frames",
-        STEP1_TEXT = "Das sind zwei Orbit-Frames.\nKlicke einen an, um ihn auszuw%ñ
-        STEP2_TITLE = "Frame-Einstellungen",
-        STEP2_TEXT = "Ein Einstellungsdialog hat sich ge%b%ffnet!\nPasse die Breite und H%b%he\ndes ausgew%ñ
-        STEP3_TITLE = "Verankerung",
-        STEP3_TEXT = "Ziehe die Frames an den\nR%ñ
-        STEP4_TITLE = "Eltern & Kind",
-        STEP4_TEXT = "Verankerte Frames bilden eine\nEltern-Kind-Beziehung. Ziehe den\nEltern-Frame “Ç
-        STEP5_TITLE = "Abstand anpassen",
-        STEP5_TEXT = "W%ñ
-        STEP6_TITLE = "Pfeilverschiebung",
-        STEP6_TEXT = "W%ñ
-        STEP7_TITLE = "Gr%b%%’en%ñ
-        STEP7_TEXT = "Greife den Griff unten rechts\nam ausgew%ñ
-        STEP8_TITLE = "Orbit-Optionen",
-        STEP8_TEXT = "Das ist das Orbit-Optionsfenster.\n%ä
-        STEP9_TITLE = "Erkunden!",
-        STEP9_TEXT = "Um Orbit optimal zu nutzen,\nprobiere einfach herum! Ziehe,\nverankere und %ñ
-        CANVAS_TITLE = "Canvas-Modus",
-        CANVAS_TEXT = "Rechtsklick auf einen Frame %b%ffnet\nden Canvas-Modus. Dort lassen sich\nKomponenten detailliert anpassen.",
-    },
-    frFR = {
-        NEXT = "Suivant", DONE = "Termin%#",
-        STEP1_TITLE = "Vos cadres",
-        STEP1_TEXT = "Voici deux cadres Orbit.\nCliquez pour s%#lectionner, puis\nfaites glisser pour repositionner.",
-        STEP2_TITLE = "Param%¿tres du cadre",
-        STEP2_TEXT = "Un dialogue de param%¿tres s'est ouvert !\nEssayez d'ajuster la largeur et la\nhauteur du cadre s%#lectionn%#.",
-        STEP3_TITLE = "Ancrage",
-        STEP3_TEXT = "Rapprochez les cadres par les bords.\nDes guides color%#s apparaissent pour\nmontrer l'ancrage. La direction d'ancrage\ncontr%$%le la croissance des cadres.\nRel%ó
-        STEP4_TITLE = "Parent & Enfant",
-        STEP4_TEXT = "Quand les cadres sont ancr%#s, l'un\ndevient le parent et l'autre l'enfant.\nD%#placez le parent “Ç
-        STEP5_TITLE = "Ajuster la distance",
-        STEP5_TEXT = "S%#lectionnez le cadre enfant, puis\nutilisez la molette pour changer\nl'%#cart avec le parent.\nMaj pour de plus grands pas.\n\n|cFF66BB66Astuce :|r Distance %á
-        STEP6_TITLE = "Touches directionnelles",
-        STEP6_TEXT = "S%#lectionnez le cadre parent et\nutilisez les fl%¿ches pour le d%#placer\nde 1 pixel. Les deux cadres bougent\nensemble. Maj pour 10px.",
-        STEP7_TITLE = "Redimensionner",
-        STEP7_TEXT = "Saisissez la poign%#e en bas %á
-        STEP8_TITLE = "Options d'Orbit",
-        STEP8_TEXT = "Voici le panneau d'options d'Orbit.\nModifiez la police, les textures, les\ncouleurs, les bordures et plus.\nTout s'applique globalement.",
-        STEP9_TITLE = "Explorez !",
-        STEP9_TEXT = "Pour profiter pleinement d'Orbit,\nexp%#rimentez ! Glissez, d%#posez,\nancrez et redimensionnez les cadres\nen mode %ë
-        CANVAS_TITLE = "Mode Canevas",
-        CANVAS_TEXT = "Clic droit sur un cadre pour ouvrir\nle mode Canevas. Personnalisation\nplus approfondie par cadre.",
-    },
-    esES = {
-        NEXT = "Siguiente", DONE = "Hecho",
-        STEP1_TITLE = "Tus marcos",
-        STEP1_TEXT = "Estos son dos marcos de Orbit.\nHaz clic en uno para seleccionarlo\ny arr%í
-        STEP2_TITLE = "Ajustes del marco",
-        STEP2_TEXT = ",%í
-        STEP3_TITLE = "Anclaje",
-        STEP3_TEXT = "Arrastra los marcos juntos por\nlos bordes. Aparecer%í
-        STEP4_TITLE = "Padre e Hijo",
-        STEP4_TEXT = "Cuando los marcos est%í
-        STEP5_TITLE = "Ajustar distancia",
-        STEP5_TEXT = "Selecciona el marco hijo, luego usa\nla rueda del rat%%n para cambiar\nla separaci%%n con el padre.\nMay%Q%s para pasos m%í
-        STEP6_TITLE = "Desplazar con flechas",
-        STEP6_TEXT = "Selecciona el marco padre y\nusa las flechas para moverlo\n1 p%¡xel. Ambos marcos se mueven\njuntos. May%Q%s para 10px.",
-        STEP7_TITLE = "Redimensionar",
-        STEP7_TEXT = "Agarra el tirador en la esquina\ninferior derecha del marco\nseleccionado. Arrastra para\nredimensionar dentro de los\nl%¡mites min/max.\n\n|cFF66BB66Pista:|r ,%í
-        STEP8_TITLE = "Opciones de Orbit",
-        STEP8_TEXT = "Este es el panel de opciones de Orbit.\nCambia fuente, texturas, colores,\nbordes y m%í
-        STEP9_TITLE = ",%í
-        STEP9_TEXT = "Para sacar el m%í
-        CANVAS_TITLE = "Modo Lienzo",
-        CANVAS_TEXT = "Clic derecho en cualquier marco\npara abrir el Modo Lienzo.\nPersonalizaci%%n m%í
-    },
-    ptBR = {
-        NEXT = "Pr%%ximo", DONE = "Conclu%¡do",
-        STEP1_TITLE = "Seus Quadros",
-        STEP1_TEXT = "Estes s%ú
-        STEP2_TITLE = "Configura%º%a%es do Quadro",
-        STEP2_TEXT = "Um di%í
-        STEP3_TITLE = "Ancoragem",
-        STEP3_TEXT = "Arraste os quadros pelas bordas.\nGuias coloridos aparecer%ú
-        STEP4_TITLE = "Pai & Filho",
-        STEP4_TEXT = "Quando os quadros est%ú
-        STEP5_TITLE = "Ajustar Dist%ó
-        STEP5_TEXT = "Selecione o quadro filho, depois\nuse a roda do mouse para mudar\no espa%ºo entre ele e o pai.\nShift para passos maiores.\n\n|cFF66BB66Dica:|r Dist%ó
-        STEP6_TITLE = "Mover com Setas",
-        STEP6_TEXT = "Selecione o quadro pai e use as\nsetas para mov%¬-lo 1 pixel por\nvez. Ambos os quadros se movem\njuntos. Shift para 10px.",
-        STEP7_TITLE = "Redimensionar",
-        STEP7_TEXT = "Pegue a al%ºa no canto inferior\ndireito do quadro selecionado.\nArraste para redimensionar\ndentro dos limites min/max.\n\n|cFF66BB66Dica:|r O redimensionamento\ndepende da posi%º%ú
-        STEP8_TITLE = "Op%º%a%es do Orbit",
-        STEP8_TEXT = "Este %# o painel de op%º%a%es do Orbit.\nAltere fonte, texturas, cores,\nbordas e mais. Todas as altera%º%a%es\nse aplicam globalmente.",
-        STEP9_TITLE = "Explore!",
-        STEP9_TEXT = "Para aproveitar o m%í
-        CANVAS_TITLE = "Modo Canvas",
-        CANVAS_TEXT = "Clique direito em qualquer quadro\npara abrir o Modo Canvas.\nPersonaliza%º%ú
-    },
-    ruRU = {
-        NEXT = "h%ö
-        STEP1_TITLE = "h%Æ
-        STEP1_TEXT = "h%¡d%é
-        STEP2_TITLE = "h%¥h%‘%d%ü
-        STEP2_TEXT = "h%§ d%é
-        STEP3_TITLE = "h%’d%Ç
-        STEP3_TEXT = "h%’h%a%d%Ç
-        STEP4_TITLE = "h%á
-        STEP4_TEXT = "h%’d%Ç
-        STEP5_TITLE = "h%ÿh%V%h%]%h%a%h%\%h%U%d%é
-        STEP5_TEXT = "h%Æ
-        STEP6_TITLE = "h%í
-        STEP6_TEXT = "h%Æ
-        STEP7_TITLE = "h%ÿh%V%h%]%h%a%h%\%h%a%h%\%h%U%h%a% d%Ç
-        STEP7_TEXT = "h%Æ
-        STEP8_TITLE = "h%¥h%‘%d%ü
-        STEP8_TEXT = "h%¡d%é
-        STEP9_TITLE = "h%ÿd%ü
-        STEP9_TEXT = "h%ºd%é
-        CANVAS_TITLE = "h%á
-        CANVAS_TEXT = "h%’d%Ç
-    },
-    koKR = {
-        NEXT = "´ï
-        STEP1_TITLE = "Æö
-        STEP1_TEXT = "Orbit Æö
-        STEP2_TITLE = "Æö
-        STEP2_TEXT = ""ä
-        STEP3_TITLE = ""ò
-        STEP3_TEXT = "Æö
-        STEP4_TITLE = "´b%Ç
-        STEP4_TEXT = "Æö
-        STEP5_TITLE = "©’%‘%´ª¼ "í
-        STEP5_TEXT = ""§ É
-        STEP6_TITLE = "ÆÖ
-        STEP6_TEXT = "´b%Ç
-        STEP7_TITLE = "´ô
-        STEP7_TEXT = ""ä
-        STEP8_TITLE = "Orbit "ÿa%"à
-        STEP8_TEXT = "Orbit "ÿa%"à
-        STEP9_TITLE = "Æâ
-        STEP9_TEXT = "Orbit"¥ä
-        CANVAS_TITLE = ""Q%ö
-        CANVAS_TEXT = "Æö
-    },
-    zhCN = {
-        NEXT = "£U%ï
-        STEP1_TITLE = "µé
-        STEP1_TEXT = "¦%Ö
-        STEP2_TITLE = "µí
-        STEP2_TEXT = "¦«[%Ä\%«Ã»c%¦»¥µí
-        STEP3_TITLE = "˜ö
-        STEP3_TEXT = "Ã‘%å
-        STEP4_TITLE = "Äê
-        STEP4_TEXT = "µí
-        STEP5_TITLE = "¦‘%â
-        STEP5_TEXT = "˜Ç
-        STEP6_TITLE = "µû
-        STEP6_TEXT = "˜Ç
-        STEP7_TITLE = "µï
-        STEP7_TEXT = "µè
-        STEP8_TITLE = "Orbit ˜Ç
-        STEP8_TEXT = "¦%Ö
-        STEP9_TITLE = "µÄ
-        STEP9_TEXT = "¦ªü
-        CANVAS_TITLE = "Äö
-        CANVAS_TEXT = "ÃÅ
-    },
-    zhTW = {
-        NEXT = "£U%ï
-        STEP1_TITLE = "µé
-        STEP1_TEXT = "˜Ç
-        STEP2_TITLE = "µí
-        STEP2_TEXT = "¦¿¡Ã«Ü
-        STEP3_TITLE = "˜î
-        STEP3_TEXT = "Ã‘%ç
-        STEP4_TITLE = "Äê
-        STEP4_TEXT = "µí
-        STEP5_TITLE = "¦¬%µò
-        STEP5_TEXT = "˜ü
-        STEP6_TITLE = "µû
-        STEP6_TEXT = "˜ü
-        STEP7_TITLE = "µï
-        STEP7_TEXT = "µè
-        STEP8_TITLE = "Orbit ˜ü
-        STEP8_TEXT = "˜Ç
-        STEP9_TITLE = "µÄ
-        STEP9_TEXT = "¦ªü
-        CANVAS_TITLE = "Äò
-        CANVAS_TEXT = "ÃÅ
-    },
-}
-LOCALE_STRINGS.enGB = LOCALE_STRINGS.enUS
-LOCALE_STRINGS.esMX = LOCALE_STRINGS.esES
-local L = LOCALE_STRINGS[GetLocale()] or LOCALE_STRINGS.enUS
-
+-- Strings live in Localization/Domains/Tours.lua (TOUR_EM_* keys) and Common.lua
+-- (CMN_NEXT / CMN_DONE). Everything below this block up to `local isCJK = ...` was
+-- removed in the central localization migration.
+local L = Orbit.L
 local isCJK = ({ koKR = true, zhCN = true, zhTW = true })[GetLocale()]
 if isCJK then TOOLTIP_MAX_WIDTH = 280 end
 
@@ -594,7 +387,7 @@ local function LayoutTooltip(anchorFrame, stop, idx, total)
     tip.title:SetText(stop.title)
     tip.text:SetText(stop.text)
     local isLast = idx == total
-    tip.nextBtn:SetText(isLast and L.DONE or L.NEXT)
+    tip.nextBtn:SetText(isLast and L.CMN_DONE or L.CMN_NEXT)
     tip.nextBtn:Show()
     if stop.check and stop.check() then tip.nextBtn:Enable() else tip.nextBtn:Disable() end
     local textH = tip.counter:GetStringHeight() + 2 + tip.title:GetStringHeight() + 3 + tip.text:GetStringHeight()
@@ -686,7 +479,7 @@ end
 -- [ TOUR STOPS (deferred init “Ç
 TOUR_STOPS = {
     { anchorKey = "A",
-      title = L.STEP1_TITLE, text = L.STEP1_TEXT,
+      title = L.TOUR_EM_STEP1_TITLE, text = L.TOUR_EM_STEP1_TEXT,
       check = function() return taskState.dragged end,
       onLeave = function()
           -- Ensure frameA is selected so the settings dialog is open for step 2
@@ -705,13 +498,13 @@ TOUR_STOPS = {
           end
       end },
     { anchorKey = "dialog", tooltipPoint = "LEFT", tooltipRel = "RIGHT", tpX = 8, tpY = 0,
-      title = L.STEP2_TITLE, text = L.STEP2_TEXT,
+      title = L.TOUR_EM_STEP2_TITLE, text = L.TOUR_EM_STEP2_TEXT,
       check = function() return taskState.settingsChanged end },
     { anchorKey = "B",
-      title = L.STEP3_TITLE, text = L.STEP3_TEXT,
+      title = L.TOUR_EM_STEP3_TITLE, text = L.TOUR_EM_STEP3_TEXT,
       check = function() return taskState.anchored end },
     { anchorKey = "parent",
-      title = L.STEP4_TITLE, text = L.STEP4_TEXT,
+      title = L.TOUR_EM_STEP4_TITLE, text = L.TOUR_EM_STEP4_TEXT,
       check = function() return taskState.parentDragged or taskState.anchorBroken end,
       onEnter = function()
           taskState.parentDragged = false
@@ -739,18 +532,18 @@ TOUR_STOPS = {
           taskState.savedAnchor = nil
       end },
     { anchorKey = "child",
-      title = L.STEP5_TITLE, text = L.STEP5_TEXT,
+      title = L.TOUR_EM_STEP5_TITLE, text = L.TOUR_EM_STEP5_TEXT,
       check = function() return taskState.distanceChanged end },
     { anchorKey = "parent",
-      title = L.STEP6_TITLE, text = L.STEP6_TEXT,
+      title = L.TOUR_EM_STEP6_TITLE, text = L.TOUR_EM_STEP6_TEXT,
       check = function() return taskState.nudged end },
     { anchorKey = "A",
-      title = L.STEP7_TITLE, text = L.STEP7_TEXT,
+      title = L.TOUR_EM_STEP7_TITLE, text = L.TOUR_EM_STEP7_TEXT,
       check = function() return taskState.resized end,
       onEnter = function() taskState.resized = false; ShowResizePulse() end,
       onLeave = function() HideResizePulse() end },
     { anchorKey = "options", tooltipPoint = "LEFT", tooltipRel = "RIGHT", tpX = 8, tpY = 0,
-      title = L.STEP8_TITLE, text = L.STEP8_TEXT,
+      title = L.TOUR_EM_STEP8_TITLE, text = L.TOUR_EM_STEP8_TEXT,
       check = function() return true end,
       onEnter = function()
           if Orbit.OptionsPanel then
@@ -763,7 +556,7 @@ TOUR_STOPS = {
           if Orbit.OptionsPanel then Orbit.OptionsPanel:Hide() end
       end },
     { anchorKey = "center", tooltipPoint = "CENTER", tooltipRel = "CENTER", tpX = 0, tpY = 0,
-      title = L.STEP9_TITLE, text = L.STEP9_TEXT,
+      title = L.TOUR_EM_STEP9_TITLE, text = L.TOUR_EM_STEP9_TEXT,
       check = function() return true end,
       onEnter = function()
           if Orbit.OptionsPanel then
@@ -1158,8 +951,8 @@ end
 function Tour:ShowCanvasHint()
     local playerFrame = GetPlayerFrame()
     if not playerFrame then return end
-    canvasTip.title:SetText(L.CANVAS_TITLE)
-    canvasTip.text:SetText(L.CANVAS_TEXT)
+    canvasTip.title:SetText(L.TOUR_EM_CANVAS_TITLE)
+    canvasTip.text:SetText(L.TOUR_EM_CANVAS_TEXT)
     local textH = canvasTip.title:GetStringHeight() + 3 + canvasTip.text:GetStringHeight()
     canvasTip:SetSize(TOOLTIP_MAX_WIDTH, textH + TOOLTIP_PAD * 2)
     canvasTip:ClearAllPoints()
diff --git a/Orbit/Core/Shared/Constants.lua b/Orbit/Core/Shared/Constants.lua
index 47a4f12..5e74196 100644
--- a/Orbit/Core/Shared/Constants.lua
+++ b/Orbit/Core/Shared/Constants.lua
@@ -133,8 +133,8 @@ C.GroupFrames = {
     DefaultRaidOffsetX = 20,
     DefaultRaidOffsetY = -300,
     RolePriority = { TANK = 1, HEALER = 2, DAMAGER = 3, NONE = 4 },
-    GrowthDirection = { Down = "Down", Up = "Up", Left = "Left", Right = "Right" },
-    ContainerAnchor = { Down = "TOPLEFT", Up = "BOTTOMLEFT", Right = "TOPLEFT", Left = "TOPRIGHT" },
+    GrowthDirection = { Down = "down", Up = "up", Left = "left", Right = "right", Center = "center" },
+    ContainerAnchor = { down = "TOPLEFT", up = "BOTTOMLEFT", right = "TOPLEFT", left = "TOPRIGHT", center = "CENTER" },
 }
 
 C.Stagger = {
@@ -247,15 +247,12 @@ C.Cooldown = {
     },
 }
 
+-- Tracked plugin uses globally-unique counter ids for system indices.
+-- Container records live in a flat global table; ids are sparse and never reused.
 C.Tracked = {
-    MaxChildFrames = 14,
-    MaxBarChildren = 4,
-    SystemIndex = {
-        Tracked = 4,
-        Tracked_ChildStart = 5,
-        TrackedBar = 20,
-        TrackedBar_ChildStart = 21,
-    },
+    MaxIconContainers = 10, -- Per-spec cap on icon containers
+    MaxBars = 10, -- Per-spec cap on single-spell bars
+    SystemIndexBase = 1000, -- Counter starts here to avoid colliding with native cooldown system indices
 }
 
 -- Unified Glow configuration parameters (LibOrbitGlow)
diff --git a/Orbit/Core/UnitDisplay/README.md b/Orbit/Core/UnitDisplay/README.md
index 5540d5c..9b50114 100644
--- a/Orbit/Core/UnitDisplay/README.md
+++ b/Orbit/Core/UnitDisplay/README.md
@@ -34,7 +34,9 @@ eliminates duplication across unit frame plugins. any behavior shared by two or
 | AggroIndicatorMixin.lua | threat/aggro border coloring via `Skin:ApplyHighlightBorder`. |
 | DispelIndicatorMixin.lua | dispellable debuff type indication with `DispelOnlyByMe` filter support. caches the dispel color curve on plugin (`_dispelCurveCache`), invalidated via `InvalidateDispelCurve`. accepts pre-fetched harmful auras from snapshot. |
 | PandemicGlow.lua | thin adapter for pandemic glow on UnitDisplay aura icons. evaluates pandemic curves and delegates rendering to `GlowController`. |
-| UnitAuraGridMixin.lua | grid-based aura display with size categories (big/small). |
+| UnitAuraGridMixin.lua | grid-based aura display with size categories (big/small). owns the mixin surface and the file-local helpers (`ResolveGrowthDirection`, `UpdateCollapseArrow`, `CropIconTexture`) which are re-exposed on `Mixin._Internal` for the split files below. |
+| UnitAuraGridExpirationPulse.lua | shared expiration pulse ticker. attaches `Mixin._RegisterExpirationPulse(icon, durObj)`. owns the pulse list and lazy `C_Timer.NewTicker` that cancels when the list drains. lives outside the mixin file because mixins must be stateless. |
+| UnitAuraGridReparenting.lua | attaches `Mixin:_updateBlizzardBuffs()`. reparents Blizzard's native `BuffFrame.auraFrames` into an Orbit grid, suppresses stock borders/textures, and wires timer + stacks into Orbit's font/override system. reaches file-locals via `Mixin._Internal` and registers expirations via `Mixin._RegisterExpirationPulse`. |
 | SecondaryUnitFrameMixin.lua | secondary frames (target-of-target, focus-target). |
 
 ## adding a new mixin
diff --git a/Orbit/Core/UnitDisplay/UnitAuraGridMixin.lua b/Orbit/Core/UnitDisplay/UnitAuraGridMixin.lua
index dbd89d2..995c909 100644
--- a/Orbit/Core/UnitDisplay/UnitAuraGridMixin.lua
+++ b/Orbit/Core/UnitDisplay/UnitAuraGridMixin.lua
@@ -43,64 +43,10 @@ local ASPECT_RATIOS = {
 
 local GetPreviewIcon = function() return Orbit.AuraPreview.GetSpellbookIcon() end
 
--- [ EXPIRATION PULSE ]------------------------------------------------------------------------------
-local EXPIRATION_THRESHOLD = 0.30
-local EXPIRATION_ALPHA_MIN = 0.10
-local EXPIRATION_PULSE_SPEED = 3
-local EXPIRATION_TICK_INTERVAL = 0.065
-local math_sin = math.sin
-local math_abs = math.abs
-local GetTime = GetTime
-
-local swipeCurve = C_CurveUtil.CreateCurve()
-swipeCurve:SetType(Enum.LuaCurveType.Linear)
-swipeCurve:AddPoint(0, 1) -- At 0% remaining (end), 100% alpha
-swipeCurve:AddPoint(1, 0) -- At 100% remaining (start), 0% alpha
-
-local _pulseIcons = {}
-local _pulseTicker
-local function ExpirationPulseTick()
-    local n = #_pulseIcons
-    if n == 0 then
-        if _pulseTicker then _pulseTicker:Cancel(); _pulseTicker = nil end
-        return
-    end
-
-    -- Rebuild the target curve per-tick. C_CurveUtil handles secret evaluation natively.
-    local wave = 1 - (1 - EXPIRATION_ALPHA_MIN) * math_abs(math_sin(GetTime() * EXPIRATION_PULSE_SPEED))
-    local expirationCurve = C_CurveUtil.CreateCurve()
-    expirationCurve:SetType(Enum.LuaCurveType.Step)
-    expirationCurve:AddPoint(0, wave)
-    expirationCurve:AddPoint(EXPIRATION_THRESHOLD, 1)
-
-    local i = 1
-    while i <= n do
-        local icon = _pulseIcons[i]
-        local durObj = icon._orbitExpireDurObj
-        if not durObj or not icon:IsShown() then
-            icon._orbitPulseRegistered = nil
-            icon:SetAlpha(1)
-            _pulseIcons[i] = _pulseIcons[n]
-            _pulseIcons[n] = nil
-            n = n - 1
-        else
-            local alpha = C_CurveUtil.EvaluateColorValueFromBoolean(durObj:IsZero(), 1, durObj:EvaluateRemainingPercent(expirationCurve))
-            icon:SetAlpha(alpha)
-            if icon.Cooldown then
-                local cdAlpha = C_CurveUtil.EvaluateColorValueFromBoolean(durObj:IsZero(), 1, durObj:EvaluateRemainingPercent(swipeCurve))
-                icon.Cooldown:SetAlpha(cdAlpha)
-            end
-            i = i + 1
-        end
-    end
-end
-local function RegisterExpirationPulse(icon, durObj)
-    icon._orbitExpireDurObj = durObj
-    if icon._orbitPulseRegistered then return end
-    icon._orbitPulseRegistered = true
-    _pulseIcons[#_pulseIcons + 1] = icon
-    if not _pulseTicker then _pulseTicker = C_Timer.NewTicker(EXPIRATION_TICK_INTERVAL, ExpirationPulseTick) end
-end
+-- Expiration pulse ticker lives in UnitAuraGridExpirationPulse.lua and attaches
+-- `Mixin._RegisterExpirationPulse` at load time. Blizzard button reparenting lives in
+-- UnitAuraGridReparenting.lua and reads the file-local helpers below via Mixin._Internal,
+-- which is populated at the bottom of the HELPERS section.
 
 -- [ COLLAPSE ARROW ]--------------------------------------------------------------------------------
 local ARROW_SIZE = 15
@@ -222,6 +168,13 @@ local function CropIconTexture(iconFrame, w, h)
     tex:SetTexCoord(trim, 1 - trim, trim + crop, 1 - trim - crop)
 end
 
+-- Export file-local helpers so UnitAuraGridReparenting.lua can reach them without duplication.
+Mixin._Internal = {
+    ResolveGrowthDirection = ResolveGrowthDirection,
+    UpdateCollapseArrow = UpdateCollapseArrow,
+    CropIconTexture = CropIconTexture,
+}
+
 -- [ SETTINGS UI ]-----------------------------------------------------------------------------------
 function Mixin:AddAuraGridSettings(dialog, systemFrame)
     local Frame = self._agFrame
@@ -600,7 +553,7 @@ function Mixin:UpdateAuras()
         self:SetupAuraTooltip(icon, aura, cfg.unit, tooltipFilter)
         -- Expiration pulse: stash DurationObject for the pulse ticker
         local durObj = aura.auraInstanceID and C_UnitAuras.GetAuraDuration(cfg.unit, aura.auraInstanceID)
-        if durObj then RegisterExpirationPulse(icon, durObj) else icon._orbitExpireDurObj = nil; icon:SetAlpha(1) end
+        if durObj then Mixin._RegisterExpirationPulse(icon, durObj) else icon._orbitExpireDurObj = nil; icon:SetAlpha(1) end
         table.insert(activeIcons, icon)
     end
 
@@ -615,248 +568,7 @@ function Mixin:UpdateAuras()
     end
 end
 
--- [ BLIZZARD BUTTON REPARENTING ]-------------------------------------------------------------------
-function Mixin:_updateBlizzardBuffs()
-    local Frame = self._agFrame
-    local cfg = self._agConfig
-    if not Frame then return end
-    if not self:IsEnabled() then return end
-    if Orbit:IsEditMode() then return end
-
-    local blizzFrame = cfg.blizzardFrame
-    if not blizzFrame or not blizzFrame.auraFrames then return end
-
-    local collapsed = cfg.showIconLimit and self:GetSetting(1, "Collapsed")
-    local maxAuras, iconsPerRow, spacing, iconH, iconW = self:_resolveGrid()
-    local anchor, growthX, growthY = ResolveGrowthDirection(Frame, cfg.showIconLimit)
-    if Frame.collapseArrow then UpdateCollapseArrow(Frame.collapseArrow, collapsed, iconH, growthX, growthY) end
-
-    -- When collapsed, build a set of HELPFUL indices that are player-cast and not excluded
-    -- auraInstanceID is the ONLY non-secret field; use C-side filter HELPFUL|PLAYER to identify player auras
-    -- spellId may be non-secret from clean context; use issecretvalue guard
-    local showIndices
-    if collapsed then
-        local IsSecret = issecretvalue
-        local excludedSpells = Orbit.GroupAuraFilters and Orbit.GroupAuraFilters.AlwaysExcluded or {}
-        local playerIDs = {}
-        AuraUtil.ForEachAura("player", "HELPFUL|PLAYER", 40, function(aura)
-            playerIDs[aura.auraInstanceID] = true
-        end, true)
-        showIndices = {}
-        local idx = 0
-        AuraUtil.ForEachAura("player", "HELPFUL", 40, function(aura)
-            idx = idx + 1
-            local sid = aura.spellId
-            local isExcluded = not IsSecret(sid) and excludedSpells[sid]
-            if playerIDs[aura.auraInstanceID] and not isExcluded then showIndices[idx] = true end
-        end, true)
-    end
-
-    local LSM = LibStub and LibStub("LibSharedMedia-3.0", true)
-    local fontName = Orbit.db and Orbit.db.GlobalSettings and Orbit.db.GlobalSettings.Font
-    local fontPath = (LSM and fontName and LSM:Fetch("font", fontName)) or "Fonts\\FRIZQT__.TTF"
-    local fontOutline = Orbit.Skin and Orbit.Skin.GetFontOutline and Orbit.Skin:GetFontOutline() or ""
-    local timerFontSize = 8
-    local countFontSize = 8
-    local isPlayerGrid = self._agConfig and self._agConfig.unit == "player"
-    local skinBorderSize = isPlayerGrid and (Orbit.db.GlobalSettings.IconBorderSize or 2) or 1
-    local skinSettings = { zoom = 0, borderStyle = 1, borderSize = skinBorderSize, showTimer = true, iconBorder = isPlayerGrid or nil, padding = spacing, aspectRatio = self:GetSetting(1, "aspectRatio") or "1:1" }
-    local componentPositions = self:GetSetting(1, "ComponentPositions") or {}
-    local OverrideUtils = OrbitEngine.OverrideUtils
-
-    local function ApplyComponentPosition(textElement, btn, key, defaultAnchorX, defaultAnchorY, defaultOffsetX, defaultOffsetY)
-        if not textElement then return end
-        local pos = componentPositions[key] or {}
-        local anchorX = pos.anchorX or defaultAnchorX
-        local anchorY = pos.anchorY or defaultAnchorY
-        local offsetX = pos.offsetX or defaultOffsetX
-        local offsetY = pos.offsetY or defaultOffsetY
-        local justifyH = pos.justifyH or "CENTER"
-        local anchorPoint
-        if anchorY == "CENTER" and anchorX == "CENTER" then anchorPoint = "CENTER"
-        elseif anchorY == "CENTER" then anchorPoint = anchorX
-        elseif anchorX == "CENTER" then anchorPoint = anchorY
-        else anchorPoint = anchorY .. anchorX end
-        local textPoint = justifyH == "LEFT" and "LEFT" or justifyH == "RIGHT" and "RIGHT" or "CENTER"
-        local finalOffsetX = anchorX == "LEFT" and offsetX or -offsetX
-        local finalOffsetY = anchorY == "BOTTOM" and offsetY or -offsetY
-        textElement:ClearAllPoints()
-        textElement:SetPoint(textPoint, btn, anchorPoint, finalOffsetX, finalOffsetY)
-        if textElement.SetJustifyH then textElement:SetJustifyH(justifyH) end
-    end
-
-    -- Build clean index“å
-    local durObjByIndex = {}
-    local durIdx = 0
-    AuraUtil.ForEachAura(cfg.unit, "HELPFUL", 40, function(aura)
-        durIdx = durIdx + 1
-        if aura.auraInstanceID then
-            local durObj = C_UnitAuras.GetAuraDuration(cfg.unit, aura.auraInstanceID)
-            if durObj then durObjByIndex[durIdx] = durObj end
-        end
-    end, true)
-
-    local skinVersion = (Frame._orbitSkinVersion or 0)
-    local activeIcons = {}
-    for _, btn in ipairs(blizzFrame.auraFrames) do
-        if btn.hasValidInfo and not btn.isAuraAnchor then
-            local bi = btn.buttonInfo
-            -- When collapsed: hide temp enchants and non-player buffs
-            local excluded = collapsed and (bi.auraType ~= "Buff" or not showIndices[bi.index])
-            if excluded or #activeIcons >= maxAuras then
-                if btn:GetParent() == Frame then
-                    btn:SetParent(blizzFrame.AuraContainer)
-                    btn._orbitSkinned = nil
-                end
-                btn:EnableMouse(false)
-                btn:Hide()
-            else
-                -- Full setup only on first reparent or settings change
-                if btn._orbitSkinned ~= skinVersion then
-                    btn:SetParent(Frame)
-                    btn:SetFrameLevel(Frame:GetFrameLevel() + Orbit.Constants.Levels.StatusBar)
-                    btn:SetScale(1)
-                    btn:SetAlpha(1)
-                    btn:SetSize(iconW, iconH)
-                    CropIconTexture(btn, iconW, iconH)
-                    Orbit.Skin.Icons:ApplyCustom(btn, skinSettings)
-                    btn.Duration:Hide()
-                    -- Permanently suppress Blizzard's native border textures
-                    local nt = btn.GetNormalTexture and btn:GetNormalTexture() or btn.NormalTexture
-                    if nt then
-                        nt:SetAlpha(0); nt:Hide()
-                        if not btn.orbitNormalTextureHooked then
-                            hooksecurefunc(nt, "Show", function(self) self:Hide(); self:SetAlpha(0) end)
-                            btn.orbitNormalTextureHooked = true
-                        end
-                    end
-                    
-                    -- Permanently suppress standard Blizzard default borders
-                    local nativeBorder = btn.Border or btn.IconBorder
-                    if nativeBorder then
-                        nativeBorder:SetAlpha(0); nativeBorder:Hide()
-                        if not btn.orbitBorderHooked then
-                            hooksecurefunc(nativeBorder, "Show", function(self) self:Hide(); self:SetAlpha(0) end)
-                            btn.orbitBorderHooked = true
-                        end
-                    end
-                    
-                    -- Suppress and hook Colored Borders (TempEnchants, Debuffs) into Orbit's pixel-perfect Highlight system
-                    for _, b in ipairs({ {border=btn.TempEnchantBorder, key="_orbitTempEnchantBorder"}, {border=btn.DebuffBorder, key="_orbitDebuffBorder"} }) do
-                        local nb = b.border
-                        if nb then
-                            local wasShown = nb:IsShown()
-                            -- Proactively force the border rendering on if the button metadata confirms it is a TempEnchant,
-                            -- completely bypassing Blizzard's notoriously deferred `TempEnchantBorder:Show()` pool delay (which causes 3-5s visual pop-in on /load)
-                            if b.key == "_orbitTempEnchantBorder" and btn.buttonInfo and btn.buttonInfo.isTempEnchant then
-                                wasShown = true
-                            end
-                            if not btn[b.key.."Hooked"] then
-                                hooksecurefunc(nb, "Show", function(self)
-                                    self:Hide(); self:SetAlpha(0)
-                                    local r, g, b_color, a = self:GetVertexColor()
-                                    if b.key == "_orbitTempEnchantBorder" then
-                                        local q = btn.buttonInfo and (btn.buttonInfo.itemQuality or btn.buttonInfo.quality) or 4
-                                        r, g, b_color = C_Item.GetItemQualityColor(q)
-                                        a = 1
-                                    end
-                                    Orbit.Skin:ApplyHighlightBorder(btn, b.key, {r=r, g=g, b=b_color, a=a}, Orbit.Constants.Levels.IconOverlay + 1)
-                                end)
-                                hooksecurefunc(nb, "Hide", function(self)
-                                    Orbit.Skin:ClearHighlightBorder(btn, b.key)
-                                end)
-                                hooksecurefunc(nb, "SetVertexColor", function(self, r, g, b_color, a)
-                                    if b.key == "_orbitTempEnchantBorder" then
-                                        local q = btn.buttonInfo and (btn.buttonInfo.itemQuality or btn.buttonInfo.quality) or 4
-                                        r, g, b_color = C_Item.GetItemQualityColor(q)
-                                        a = 1
-                                    end
-                                    Orbit.Skin:ApplyHighlightBorder(btn, b.key, {r=r, g=g, b=b_color, a=a}, Orbit.Constants.Levels.IconOverlay + 1)
-                                end)
-                                btn[b.key.."Hooked"] = true
-                            end
-                            nb:SetAlpha(0); nb:Hide()
-                            
-                            -- Initial Sync
-                            local r, g, b_color, a = nb:GetVertexColor()
-                            if b.key == "_orbitTempEnchantBorder" then
-                                local q = btn.buttonInfo and (btn.buttonInfo.itemQuality or btn.buttonInfo.quality) or 4
-                                r, g, b_color = C_Item.GetItemQualityColor(q)
-                                a = 1
-                            end
-                            
-                            if wasShown then
-                                Orbit.Skin:ApplyHighlightBorder(btn, b.key, {r=r, g=g, b=b_color, a=a}, Orbit.Constants.Levels.IconOverlay + 1)
-                            else
-                                Orbit.Skin:ClearHighlightBorder(btn, b.key)
-                            end
-                        end
-                    end
-                    -- Cooldown frame for timer
-                    if not btn.Cooldown then
-                        btn.Cooldown = CreateFrame("Cooldown", nil, btn, "CooldownFrameTemplate")
-                        btn.Cooldown:SetAllPoints()
-                        btn.Cooldown:SetHideCountdownNumbers(false)
-                        btn.Cooldown:EnableMouse(false)
-                        btn.cooldown = btn.Cooldown
-                    end
-                    -- Text overlay above border
-                    if not btn.orbitTextOverlay then
-                        btn.orbitTextOverlay = CreateFrame("Frame", nil, btn)
-                        btn.orbitTextOverlay:SetAllPoints(btn)
-                        btn.orbitTextOverlay:SetFrameLevel(btn:GetFrameLevel() + Orbit.Constants.Levels.IconOverlay)
-                    end
-                    -- Style timer text
-                    local timerText = btn.Cooldown.Text
-                    if not timerText then
-                        for _, region in pairs({ btn.Cooldown:GetRegions() }) do
-                            if region:IsObjectType("FontString") then timerText = region; break end
-                        end
-                        btn.Cooldown.Text = timerText
-                    end
-                    if timerText and timerText.SetFont then
-                        timerText:SetParent(btn.orbitTextOverlay)
-                        if OverrideUtils then OverrideUtils.ApplyOverrides(timerText, (componentPositions.Timer or {}).overrides or {}, { fontSize = timerFontSize, fontPath = fontPath }) end
-                        timerText:SetDrawLayer("OVERLAY", 7)
-                        ApplyComponentPosition(timerText, btn, "Timer", "CENTER", "CENTER", 0, 0)
-                    end
-                    -- Style stacks
-                    btn.Count:SetParent(btn.orbitTextOverlay)
-                    if OverrideUtils then OverrideUtils.ApplyOverrides(btn.Count, (componentPositions.Stacks or {}).overrides or {}, { fontSize = countFontSize, fontPath = fontPath }) end
-                    Orbit.Skin:ApplyFontShadow(btn.Count)
-                    btn.Count:SetDrawLayer("OVERLAY", 7)
-                    ApplyComponentPosition(btn.Count, btn, "Stacks", "RIGHT", "BOTTOM", 1, 1)
-                    btn._orbitSkinned = skinVersion
-                end
-                -- Lightweight refresh: enforce size (Blizzard may resize between skin cycles)
-                btn:EnableMouse(true)
-                btn:SetSize(iconW, iconH)
-                CropIconTexture(btn, iconW, iconH)
-                btn.Cooldown:Clear()
-                if btn.Cooldown.Text then btn.Cooldown.Text:SetText("") end
-                local durObj = bi and bi.index and durObjByIndex[bi.index]
-                if durObj then btn.Cooldown:SetCooldownFromDurationObject(durObj) end
-                -- Expiration pulse: stash DurationObject for the pulse ticker
-                if durObj then RegisterExpirationPulse(btn, durObj) else btn._orbitExpireDurObj = nil; btn:SetAlpha(1) end
-                btn:Show()
-                table.insert(activeIcons, btn)
-            end
-        end
-    end
-
-    if #activeIcons == 0 then
-        if Frame._gridGroupBorder then Frame._gridGroupBorder:Hide() end
-        return
-    end
-    Orbit.AuraLayout:LayoutGrid(Frame, activeIcons, {
-        size = iconH, sizeW = iconW, spacing = spacing, maxPerRow = iconsPerRow,
-        anchor = anchor, growthX = growthX, growthY = growthY, yOffset = 0,
-    })
-    skinSettings._maxPerRow = iconsPerRow
-    skinSettings._growthX = growthX
-    skinSettings._growthY = growthY
-    self:_applyGridGroupBorder(Frame, activeIcons, spacing, skinSettings)
-end
+-- Mixin:_updateBlizzardBuffs() lives in UnitAuraGridReparenting.lua
 
 -- [ GRID GROUP BORDER ]-----------------------------------------------------------------------------
 function Mixin:_applyGridGroupBorder(Frame, activeIcons, spacing, skinSettings)
diff --git a/Orbit/Core/UnitDisplay/UnitDisplay.xml b/Orbit/Core/UnitDisplay/UnitDisplay.xml
index 744e215..33bb0f1 100644
--- a/Orbit/Core/UnitDisplay/UnitDisplay.xml
+++ b/Orbit/Core/UnitDisplay/UnitDisplay.xml
@@ -13,6 +13,8 @@
     <Script file="UnitPowerBarMixin.lua"/>
     <Script file="AuraMixin.lua"/>
     <Script file="UnitAuraGridMixin.lua"/>
+    <Script file="UnitAuraGridExpirationPulse.lua"/>
+    <Script file="UnitAuraGridReparenting.lua"/>
     <Script file="SecondaryUnitFrameMixin.lua"/>
     <Script file="ResourceBarMixin.lua"/>
     <Script file="AggroIndicatorMixin.lua"/>
diff --git a/Orbit/Orbit.toc b/Orbit/Orbit.toc
index e16e966..7d264a7 100644
--- a/Orbit/Orbit.toc
+++ b/Orbit/Orbit.toc
@@ -17,6 +17,11 @@
 # ==============================================================
 Core\Libs\Libs.xml
 
+# ==============================================================
+# LOCALIZATION
+# ==============================================================
+Localization\Localization.xml
+
 # ==============================================================
 # CORE
 # ==============================================================
@@ -49,11 +54,12 @@ Plugins\GroupFrames\GroupFrames.xml
 Plugins\BossFrames\BossFrames.xml
 Plugins\ActionBars\ActionBars.xml
 Plugins\CooldownManager\CooldownManager.xml
+Plugins\CooldownViewerExtensions\CooldownViewerExtensions.xml
 Plugins\Tracked\Tracked.xml
 Plugins\MenuItems\MenuItems.xml
 Plugins\Minimap\Minimap.xml
 Plugins\Datatexts\Datatexts.xml
-Plugins\Misc\TalkingHead.lua
+Plugins\Extras\TalkingHead.lua
 
 # ==============================================================
 # QUALITY OF LIFE
diff --git a/Orbit/Plugins/GroupFrames/GroupFrame.lua b/Orbit/Plugins/GroupFrames/GroupFrame.lua
index 70f0dd5..4e3a3b6 100644
--- a/Orbit/Plugins/GroupFrames/GroupFrame.lua
+++ b/Orbit/Plugins/GroupFrames/GroupFrame.lua
@@ -45,7 +45,7 @@ local UNIT_REREGISTER_EVENTS = {
 local TIER_DEFAULTS = {
     Party = {
         Width = 160, Height = 40, Scale = 100, Spacing = 3, Orientation = 0,
-        GrowthDirection = "Down", IncludePlayer = true,
+        GrowthDirection = "down", IncludePlayer = true,
         ShowPowerBar = true, PowerBarHeight = 10,
         HealthTextMode = "percent_short", ShowHealthValue = true,
         ComponentPositions = {
@@ -85,8 +85,8 @@ local TIER_DEFAULTS = {
     },
     Mythic = {
         Width = 100, Height = 40, Scale = 100, MemberSpacing = 2, GroupSpacing = 2,
-        GroupsPerRow = 6, GrowthDirection = "Down", SortMode = "Group",
-        Orientation = "Horizontal", FlatRows = 1,
+        GroupsPerRow = 6, GrowthDirection = "down", SortMode = "group",
+        Orientation = "horizontal", FlatRows = 1,
         ShowPowerBar = true, PowerBarHeight = 16, ShowGroupLabels = true,
         ShowHealthValue = false, HealthTextMode = "percent_short",
         ComponentPositions = {
@@ -498,8 +498,43 @@ function Plugin:AddSettings(dialog, systemFrame)
 end
 
 
+-- [ PHASE 0 MIGRATION: lowercase dropdown values ]--------------------------------------------------
+-- GrowthDirection / SortMode / Orientation (raid) used to store their English display
+-- text as the saved value. Phase 0 split text/value so the stored value is a stable
+-- lowercase key and the display text can be localized without breaking saved settings.
+-- Runs once per SavedVariables file, gated on a flag.
+local P0_LEGACY_VALUES = {
+    ["Down"] = "down", ["Up"] = "up", ["Left"] = "left", ["Right"] = "right", ["Center"] = "center",
+    ["Vertical"] = "vertical", ["Horizontal"] = "horizontal",
+    ["Group"] = "group", ["Role"] = "role", ["Alphabetical"] = "alphabetical",
+}
+local function MigrateP0DropdownValues(plugin)
+    if plugin:GetSetting(1, "_P0DropdownMigrated") then return end
+    local tiers = plugin:GetSetting(1, "Tiers")
+    if tiers then
+        for _, settings in pairs(tiers) do
+            if type(settings) == "table" then
+                if type(settings.GrowthDirection) == "string" and P0_LEGACY_VALUES[settings.GrowthDirection] then
+                    settings.GrowthDirection = P0_LEGACY_VALUES[settings.GrowthDirection]
+                end
+                if type(settings.SortMode) == "string" and P0_LEGACY_VALUES[settings.SortMode] then
+                    settings.SortMode = P0_LEGACY_VALUES[settings.SortMode]
+                end
+                -- Orientation is numeric (0/1) for Party tier, string for raid tiers.
+                -- Only rewrite the string form.
+                if type(settings.Orientation) == "string" and P0_LEGACY_VALUES[settings.Orientation] then
+                    settings.Orientation = P0_LEGACY_VALUES[settings.Orientation]
+                end
+            end
+        end
+        plugin:SetSetting(1, "Tiers", tiers)
+    end
+    plugin:SetSetting(1, "_P0DropdownMigrated", true)
+end
+
 -- [ ON LOAD ]---------------------------------------------------------------------------------------
 function Plugin:OnLoad()
+    MigrateP0DropdownValues(self)
 
     HideNativeGroupFrames()
     self:UpdateBlizzardRaidPanelVisibility()
@@ -828,7 +863,7 @@ function Plugin:AssignPartyUnits()
 end
 
 function Plugin:AssignRaidUnits()
-    local sortMode = self:GetTierSetting("SortMode") or "Group"
+    local sortMode = self:GetTierSetting("SortMode") or "group"
     local sortedUnits = Helpers:GetSortedRaidUnits(sortMode)
     local tierWidth = self:GetTierSetting("Width") or DEFAULT_WIDTH
     local tierHeight = self:GetTierSetting("Height") or DEFAULT_HEIGHT
diff --git a/Orbit/Plugins/GroupFrames/GroupFrameHelpers.lua b/Orbit/Plugins/GroupFrames/GroupFrameHelpers.lua
index e2180d3..f3ccab0 100644
--- a/Orbit/Plugins/GroupFrames/GroupFrameHelpers.lua
+++ b/Orbit/Plugins/GroupFrames/GroupFrameHelpers.lua
@@ -26,7 +26,7 @@ local GF = Orbit.Constants.GroupFrames
 
 Helpers.GROWTH_DIRECTION = GF.GrowthDirection
 
-local SORT_MODE = { Group = "Group", Role = "Role", Alphabetical = "Alphabetical" }
+local SORT_MODE = { Group = "group", Role = "role", Alphabetical = "alphabetical" }
 Helpers.SORT_MODE = SORT_MODE
 local ROLE_PRIORITY = GF.RolePriority
 
@@ -68,16 +68,17 @@ function Helpers:GetTierMaxFrames(tier) return TIER_MAX_FRAMES[tier] or MAX_GROU
 
 -- [ ANCHOR ]----------------------------------------------------------------------------------------
 local CONTAINER_ANCHOR = {
-    ["Down"] = "TOPLEFT",
-    ["Up"] = "BOTTOMLEFT",
-    ["Right"] = "TOPLEFT",
-    ["Left"] = "TOPRIGHT",
-    ["Center"] = "CENTER",
+    ["down"] = "TOPLEFT",
+    ["up"] = "BOTTOMLEFT",
+    ["right"] = "TOPLEFT",
+    ["left"] = "TOPRIGHT",
+    ["center"] = "CENTER",
 }
 
 function Helpers:GetContainerAnchor(growthDirection) return CONTAINER_ANCHOR[growthDirection] or "TOPLEFT" end
 
 -- [ AURA ANCHOR HELPER ]----------------------------------------------------------------------------
+-- "Right" here is a PositionUtils anchor token, NOT a GrowthDirection value. Keep capitalized.
 function Helpers:AnchorToPosition(posX, posY, halfW, halfH)
     return Orbit.Engine.PositionUtils.AnchorToPosition(posX, posY, halfW, halfH, "Right")
 end
@@ -96,14 +97,14 @@ end
 function Helpers:CalculatePartyFramePosition(index, frameWidth, frameHeight, spacing, orientation, growthDirection, scale)
     spacing = scale and Pixel:Multiple(spacing or 0, scale) or (spacing or 0)
     orientation = orientation or 0
-    growthDirection = growthDirection or (orientation == 0 and "Down" or "Right")
+    growthDirection = growthDirection or (orientation == 0 and "down" or "right")
     local step = orientation == 0 and (frameHeight + spacing) or (frameWidth + spacing)
     local offset = scale and Pixel:Snap((index - 1) * step, scale) or ((index - 1) * step)
-    if growthDirection == "Down" then return 0, -offset, "TOPLEFT", "TOPLEFT" end
-    if growthDirection == "Up" then return 0, offset, "BOTTOMLEFT", "BOTTOMLEFT" end
-    if growthDirection == "Right" then return offset, 0, "TOPLEFT", "TOPLEFT" end
-    if growthDirection == "Left" then return -offset, 0, "TOPRIGHT", "TOPRIGHT" end
-    if growthDirection == "Center" then
+    if growthDirection == "down" then return 0, -offset, "TOPLEFT", "TOPLEFT" end
+    if growthDirection == "up" then return 0, offset, "BOTTOMLEFT", "BOTTOMLEFT" end
+    if growthDirection == "right" then return offset, 0, "TOPLEFT", "TOPLEFT" end
+    if growthDirection == "left" then return -offset, 0, "TOPRIGHT", "TOPRIGHT" end
+    if growthDirection == "center" then
         if orientation == 0 then return 0, -offset, "TOPLEFT", "TOPLEFT" end
         return offset, 0, "TOPLEFT", "TOPLEFT"
     end
@@ -156,7 +157,7 @@ function Helpers:CalculateMemberPosition(memberIndex, frameWidth, frameHeight, m
         return mx, 0
     end
     local offset = scale and Pixel:Snap((memberIndex - 1) * (frameHeight + memberSpacing), scale) or ((memberIndex - 1) * (frameHeight + memberSpacing))
-    if memberGrowth == "Up" then return 0, offset end
+    if memberGrowth == "up" then return 0, offset end
     return 0, -offset
 end
 
diff --git a/Orbit/Plugins/GroupFrames/GroupFrameLayout.lua b/Orbit/Plugins/GroupFrames/GroupFrameLayout.lua
index 79bee5b..6ad68b1 100644
--- a/Orbit/Plugins/GroupFrames/GroupFrameLayout.lua
+++ b/Orbit/Plugins/GroupFrames/GroupFrameLayout.lua
@@ -37,7 +37,7 @@ function Orbit.GroupFrameLayoutMixin:PositionPartyFrames()
     local orientation = self:GetTierSetting("Orientation") or 0
     local width = self:GetTierSetting("Width") or 160
     local height = self:GetTierSetting("Height") or 40
-    local growthDirection = self:GetTierSetting("GrowthDirection") or (orientation == 0 and "Down" or "Right")
+    local growthDirection = self:GetTierSetting("GrowthDirection") or (orientation == 0 and "down" or "right")
     self.container.orbitForceAnchorPoint = Helpers:GetContainerAnchor(growthDirection)
 
     local visibleIndex = 0
@@ -59,12 +59,12 @@ function Orbit.GroupFrameLayoutMixin:PositionRaidFrames()
     local memberSpacing = self:GetTierSetting("MemberSpacing") or 2
     local groupSpacing = self:GetTierSetting("GroupSpacing") or 2
     local groupsPerRow = self:GetTierSetting("GroupsPerRow") or 6
-    local memberGrowth = self:GetTierSetting("GrowthDirection") or "Down"
+    local memberGrowth = self:GetTierSetting("GrowthDirection") or "down"
     self.container.orbitForceAnchorPoint = Helpers:GetContainerAnchor(memberGrowth)
-    local isHorizontal = (self:GetTierSetting("Orientation") or "Vertical") == "Horizontal"
+    local isHorizontal = (self:GetTierSetting("Orientation") or "vertical") == "horizontal"
 
     local activeGroups = Helpers:GetActiveGroups()
-    local sortMode = self:GetTierSetting("SortMode") or "Group"
+    local sortMode = self:GetTierSetting("SortMode") or "group"
 
     local isPreview = self.frames[1] and self.frames[1].preview
     local groupOrder = {}
@@ -78,10 +78,10 @@ function Orbit.GroupFrameLayoutMixin:PositionRaidFrames()
         end
     end
 
-    local growUp = (memberGrowth == "Up")
+    local growUp = (memberGrowth == "up")
     local scale = self.container:GetEffectiveScale() or 1
 
-    if sortMode ~= "Group" then
+    if sortMode ~= "group" then
         local flatRows = math.max(1, self:GetTierSetting("FlatRows") or 1)
         local visibleFrames = {}
         for i = 1, MAX_GROUP_FRAMES do
@@ -150,7 +150,7 @@ end
 -- [ GROUP LABELS ]----------------------------------------------------------------------------------
 function Orbit.GroupFrameLayoutMixin:UpdateGroupLabels(sortMode, groupOrder, width, height, memberSpacing, groupSpacing, groupsPerRow, isHorizontal, growUp, scale)
     if not self.groupLabels then self.groupLabels = {} end
-    local showLabels = (sortMode == "Group") and self:GetTierSetting("ShowGroupLabels")
+    local showLabels = (sortMode == "group") and self:GetTierSetting("ShowGroupLabels")
 
     for i = 1, MAX_RAID_GROUPS do
         if self.groupLabels[i] then self.groupLabels[i]:Hide() end
@@ -220,9 +220,9 @@ function Orbit.GroupFrameLayoutMixin:UpdateContainerSize()
         local memberSpacing = self:GetTierSetting("MemberSpacing") or 2
         local groupSpacing = self:GetTierSetting("GroupSpacing") or 2
         local groupsPerRow = self:GetTierSetting("GroupsPerRow") or 6
-        local sortMode = self:GetTierSetting("SortMode") or "Group"
+        local sortMode = self:GetTierSetting("SortMode") or "group"
 
-        if sortMode ~= "Group" then
+        if sortMode ~= "group" then
             local flatRows = math.max(1, self:GetTierSetting("FlatRows") or 1)
             local totalFrames = 0
             for _, frame in ipairs(self.frames) do
@@ -245,7 +245,7 @@ function Orbit.GroupFrameLayoutMixin:UpdateContainerSize()
                 for _ in pairs(activeGroups) do numGroups = numGroups + 1 end
                 numGroups = math.max(1, numGroups)
             end
-            local isHorizontal = (self:GetTierSetting("Orientation") or "Vertical") == "Horizontal"
+            local isHorizontal = (self:GetTierSetting("Orientation") or "vertical") == "horizontal"
             local scale = self.container:GetEffectiveScale() or 1
             local containerW, containerH = Helpers:CalculateRaidContainerSize(numGroups, FRAMES_PER_GROUP, width, height, memberSpacing, groupSpacing, groupsPerRow, isHorizontal, scale)
             self.container:SetSize(containerW, containerH)
diff --git a/Orbit/Plugins/GroupFrames/GroupFramePreview.lua b/Orbit/Plugins/GroupFrames/GroupFramePreview.lua
index 7e3f1ef..7f343c5 100644
--- a/Orbit/Plugins/GroupFrames/GroupFramePreview.lua
+++ b/Orbit/Plugins/GroupFrames/GroupFramePreview.lua
@@ -135,14 +135,14 @@ local function GetPreviewSortOrder(plugin)
     for i = 1, comp.healers do order[#order + 1] = plugin._previewRosterHealers[i] end
     for i = 1, comp.dps do order[#order + 1] = plugin._previewRosterDPS[i] end
     
-    local sortMode = plugin:GetTierSetting("SortMode") or "Group"
-    if sortMode == "Role" then
+    local sortMode = plugin:GetTierSetting("SortMode") or "group"
+    if sortMode == "role" then
         table.sort(order, function(a, b)
             local pa, pb = ROLE_PRIORITY[RAID_PREVIEW.Roles[a]] or 4, ROLE_PRIORITY[RAID_PREVIEW.Roles[b]] or 4
             if pa ~= pb then return pa < pb end
             return (RAID_PREVIEW.Names[a] or "") < (RAID_PREVIEW.Names[b] or "")
         end)
-    elseif sortMode == "Alphabetical" then
+    elseif sortMode == "alphabetical" then
         table.sort(order, function(a, b) return (RAID_PREVIEW.Names[a] or "") < (RAID_PREVIEW.Names[b] or "") end)
     end
     return order
diff --git a/Orbit/Plugins/GroupFrames/GroupFrameSettings.lua b/Orbit/Plugins/GroupFrames/GroupFrameSettings.lua
index 1f05788..9ca4933 100644
--- a/Orbit/Plugins/GroupFrames/GroupFrameSettings.lua
+++ b/Orbit/Plugins/GroupFrames/GroupFrameSettings.lua
@@ -1,4 +1,8 @@
 -- [ GROUP FRAME SETTINGS ]--------------------------------------------------------------------------
+-- Dropdown values for GrowthDirection / SortMode / Orientation (raid) are stable lowercase
+-- keys ("down", "group", "vertical"). The display `text` is the English label which is safe
+-- to localize. A one-shot SavedVariables migration in GroupFrame.lua:MigrateP0DropdownValues
+-- upgrades legacy capitalized values on first load.
 ---@type Orbit
 local Orbit = Orbit
 local OrbitEngine = Orbit.Engine
@@ -153,15 +157,15 @@ function Orbit.GroupFrameSettings(plugin, dialog, systemFrame)
                 options = { { text = "Vertical", value = 0 }, { text = "Horizontal", value = 1 } },
                 onChange = function(val)
                     plugin:SetTierSetting("Orientation", val, editTier)
-                    plugin:SetTierSetting("GrowthDirection", val == 0 and "Down" or "Right", editTier)
+                    plugin:SetTierSetting("GrowthDirection", val == 0 and "down" or "right", editTier)
                     plugin:ApplySettings()
                     if plugin.frames and plugin.frames[1] and plugin.frames[1].preview then plugin:SchedulePreviewUpdate() end
                     if dialog.orbitTabCallback then dialog.orbitTabCallback() end
                 end,
             })
-            local growthOptions = orientation == 0 and { { text = "Down", value = "Down" }, { text = "Up", value = "Up" }, { text = "Center", value = "Center" } }
-                or { { text = "Right", value = "Right" }, { text = "Left", value = "Left" }, { text = "Center", value = "Center" } }
-            table.insert(schema.controls, { type = "dropdown", key = "GrowthDirection", label = "Growth Direction", default = orientation == 0 and "Down" or "Right", options = growthOptions, onChange = TierMOC("GrowthDirection") })
+            local growthOptions = orientation == 0 and { { text = "Down", value = "down" }, { text = "Up", value = "up" }, { text = "Center", value = "center" } }
+                or { { text = "Right", value = "right" }, { text = "Left", value = "left" }, { text = "Center", value = "center" } }
+            table.insert(schema.controls, { type = "dropdown", key = "GrowthDirection", label = "Growth Direction", default = orientation == 0 and "down" or "right", options = growthOptions, onChange = TierMOC("GrowthDirection") })
             table.insert(schema.controls, { type = "slider", key = "Width", label = "Width", min = 50, max = 400, step = 1, default = 160, onChange = TierMOC("Width") })
             table.insert(schema.controls, { type = "slider", key = "Height", label = "Height", min = 20, max = 100, step = 1, default = 40, onChange = TierMOC("Height") })
             table.insert(schema.controls, { type = "slider", key = "Spacing", label = "Spacing", min = 0, max = 50, step = 1, default = 0, onChange = TierMOC("Spacing") })
@@ -184,18 +188,18 @@ function Orbit.GroupFrameSettings(plugin, dialog, systemFrame)
             -- Raid-specific layout controls
             local tierMax = Helpers:GetTierMaxFrames(editTier)
             local maxGroups = math.ceil(tierMax / 5)
-            local sortMode = plugin:GetTierSetting("SortMode", editTier) or "Group"
-            if sortMode == "Group" then
+            local sortMode = plugin:GetTierSetting("SortMode", editTier) or "group"
+            if sortMode == "group" then
                 table.insert(schema.controls, {
-                    type = "dropdown", key = "Orientation", label = "Orientation", default = "Vertical",
-                    options = { { text = "Vertical", value = "Vertical" }, { text = "Horizontal", value = "Horizontal" } },
+                    type = "dropdown", key = "Orientation", label = "Orientation", default = "vertical",
+                    options = { { text = "Vertical", value = "vertical" }, { text = "Horizontal", value = "horizontal" } },
                     onChange = TierMOC("Orientation"),
                 })
             end
-            table.insert(schema.controls, { type = "dropdown", key = "GrowthDirection", label = "Growth Direction", default = "Down", options = { { text = "Down", value = "Down" }, { text = "Up", value = "Up" }, { text = "Center", value = "Center" } }, onChange = TierMOC("GrowthDirection") })
+            table.insert(schema.controls, { type = "dropdown", key = "GrowthDirection", label = "Growth Direction", default = "down", options = { { text = "Down", value = "down" }, { text = "Up", value = "up" }, { text = "Center", value = "center" } }, onChange = TierMOC("GrowthDirection") })
             table.insert(schema.controls, {
-                type = "dropdown", key = "SortMode", label = "Sort Mode", default = "Group",
-                options = { { text = "Group", value = "Group" }, { text = "Role", value = "Role" }, { text = "Alphabetical", value = "Alphabetical" } },
+                type = "dropdown", key = "SortMode", label = "Sort Mode", default = "group",
+                options = { { text = "Group", value = "group" }, { text = "Role", value = "role" }, { text = "Alphabetical", value = "alphabetical" } },
                 onChange = function(val)
                     plugin:SetTierSetting("SortMode", val, editTier)
                     if not InCombatLockdown() then plugin:UpdateFrameUnits(); plugin:PositionFrames() end
@@ -206,7 +210,7 @@ function Orbit.GroupFrameSettings(plugin, dialog, systemFrame)
             table.insert(schema.controls, { type = "slider", key = "Width", label = "Width", min = 50, max = 200, step = 1, default = 100, onChange = TierMOC("Width") })
             table.insert(schema.controls, { type = "slider", key = "Height", label = "Height", min = 20, max = 80, step = 1, default = 40, onChange = TierMOC("Height") })
             table.insert(schema.controls, { type = "slider", key = "MemberSpacing", label = "Member Spacing", min = 0, max = 50, step = 1, default = 2, onChange = TierMOC("MemberSpacing") })
-            if sortMode == "Group" then
+            if sortMode == "group" then
                 local gprDefault = math.min(maxGroups, 6)
                 table.insert(schema.controls, { type = "slider", key = "GroupsPerRow", label = "Groups Per Row", min = 1, max = maxGroups, step = 1, default = gprDefault, onChange = TierMOC("GroupsPerRow") })
                 table.insert(schema.controls, { type = "slider", key = "GroupSpacing", label = "Group Spacing", min = 0, max = 50, step = 1, default = 4, onChange = TierMOC("GroupSpacing") })
@@ -238,7 +242,7 @@ function Orbit.GroupFrameSettings(plugin, dialog, systemFrame)
         local aggroRefresh = function() Orbit.AggroIndicatorMixin:InvalidateAggroSettings(plugin); if plugin.UpdateAllAggroIndicators then plugin:UpdateAllAggroIndicators(plugin) end end
         table.insert(schema.controls, { type = "color", key = "AggroColor", label = "Aggro Highlight", default = { r = 1.0, g = 0.0, b = 0.0, a = 1 }, onChange = TierMOC("AggroColor", aggroRefresh) })
     elseif currentTab == "Indicators" then
-        if not isParty and (plugin:GetTierSetting("SortMode", editTier) or "Group") == "Group" then
+        if not isParty and (plugin:GetTierSetting("SortMode", editTier) or "group") == "group" then
             table.insert(schema.controls, { type = "checkbox", key = "ShowGroupLabels", label = "Show Groups", default = true, onChange = TierMOC("ShowGroupLabels") })
         end
         local dispelRefresh = function() Orbit.DispelIndicatorMixin:InvalidateDispelCurve(plugin); if plugin.UpdateAllDispelIndicators then plugin:UpdateAllDispelIndicators(plugin) end end
diff --git a/Orbit/Plugins/Misc/TalkingHead.lua b/Orbit/Plugins/Misc/TalkingHead.lua
deleted file mode 100644
index d0a1940..0000000
--- a/Orbit/Plugins/Misc/TalkingHead.lua
+++ /dev/null
@@ -1,213 +0,0 @@
----@type Orbit
-local Orbit = Orbit
-local OrbitEngine = Orbit.Engine
-
--- [ PLUGIN REGISTRATION ]---------------------------------------------------------------------------
-local SYSTEM_ID = "Orbit_TalkingHead"
-local DEFAULT_OFFSET_Y = -100
-
-local Plugin = Orbit:RegisterPlugin("Talking Head", SYSTEM_ID, {
-    defaults = {
-        Scale = 60,
-        DisableTalkingHead = false,
-    },
-})
-
--- Apply NativeBarMixin for mouseOver helpers
-Mixin(Plugin, Orbit.NativeBarMixin)
-
--- [ SETTINGS UI ]-----------------------------------------------------------------------------------
-function Plugin:AddSettings(dialog, systemFrame)
-    local systemIndex = systemFrame.systemIndex or SYSTEM_ID
-    local SB = OrbitEngine.SchemaBuilder
-
-    local schema = {
-        hideNativeSettings = true,
-        controls = {
-            {
-                type = "checkbox",
-                key = "DisableTalkingHead",
-                label = "Disable",
-                tooltip = "Completely hides the Talking Head frame.",
-                default = false,
-            },
-        },
-    }
-
-    -- Scale setting
-    SB:AddSizeSettings(self, schema, systemIndex, systemFrame, nil, nil, {
-        key = "Scale",
-        label = "Scale",
-        default = 60,
-    })
-
-    OrbitEngine.Config:Render(dialog, systemFrame, self, schema)
-end
-
--- [ LIFECYCLE ]-------------------------------------------------------------------------------------
-function Plugin:OnLoad()
-    -- Create Container
-    self.frame = CreateFrame("Frame", "OrbitTalkingHeadContainer", UIParent)
-    self.frame:SetSize(500, 100) -- Approximate TalkingHead size
-    self.frame:SetClampedToScreen(true) -- Prevent dragging off-screen
-    self.frame.systemIndex = SYSTEM_ID
-    self.frame.editModeName = "Talking Head"
-
-    -- Anchor Options: Allow anchoring but disable property sync
-    self.frame.anchorOptions = {
-        horizontal = false,
-        vertical = false,
-        syncScale = false,
-        syncDimensions = false,
-    }
-
-    -- Default Position (top center, like native)
-    self.frame:SetPoint("TOP", UIParent, "TOP", 0, DEFAULT_OFFSET_Y)
-
-    -- Register to Orbit
-    OrbitEngine.Frame:AttachSettingsListener(self.frame, self, SYSTEM_ID)
-    OrbitEngine.Frame:RestorePosition(self.frame, self, SYSTEM_ID)
-
-    self:RegisterStandardEvents()
-    self:RegisterVisibilityEvents()
-
-    -- Hook TalkingHead addon loading
-    local function HookTalkingHead()
-        self:ReparentAll()
-        self:ApplySettings()
-
-        -- Hook play events to ensure settings persist (ReparentAll is called inside ApplySettings)
-        if TalkingHeadFrame_PlayCurrent then
-            hooksecurefunc("TalkingHeadFrame_PlayCurrent", function()
-                self:ApplySettings()
-            end)
-        end
-    end
-
-    if C_AddOns.IsAddOnLoaded("Blizzard_TalkingHeadUI") then
-        HookTalkingHead()
-    else
-        -- Store loader on self to prevent orphan frames on reload
-        self._addonLoader = CreateFrame("Frame")
-        self._addonLoader:RegisterEvent("ADDON_LOADED")
-        self._addonLoader:SetScript("OnEvent", function(f, event, addonName)
-            if addonName == "Blizzard_TalkingHeadUI" then
-                HookTalkingHead()
-                f:UnregisterEvent("ADDON_LOADED")
-            end
-        end)
-    end
-end
-
--- [ LOGIC ]-----------------------------------------------------------------------------------------
-function Plugin:ReparentAll()
-    if not TalkingHeadFrame then
-        return
-    end
-    if InCombatLockdown() then
-        Orbit.CombatManager:QueueUpdate(function()
-            self:ReparentAll()
-        end)
-        return
-    end
-    if TalkingHeadFrame:GetParent() ~= self.frame then
-        local parent = TalkingHeadFrame:GetParent()
-        -- Only capture from native Blizzard parents, never from another addon's container
-        if parent ~= UIParent and parent ~= (TalkingHeadContainerFrame or UIParent) then
-            self.conflicted = true
-            return
-        end
-        TalkingHeadFrame:SetParent(self.frame)
-    end
-
-    TalkingHeadFrame:ClearAllPoints()
-    TalkingHeadFrame:SetPoint("CENTER", self.frame, "CENTER", 0, 0)
-    TalkingHeadFrame:EnableMouse(false)
-
-    -- Mark as captured
-    self._captured = true
-
-    -- Suppress Blizzard's native Edit Mode selection (prevents double-highlight)
-    if TalkingHeadFrame.Selection then
-        TalkingHeadFrame.Selection:SetAlpha(0)
-        TalkingHeadFrame.Selection:EnableMouse(false)
-    end
-
-    -- Protect TalkingHeadFrame from being stolen by other addons/Blizzard
-    OrbitEngine.FrameGuard:Protect(TalkingHeadFrame, self.frame)
-    OrbitEngine.FrameGuard:UpdateProtection(TalkingHeadFrame, self.frame, function()
-        self:ApplySettings()
-    end, { enforceShow = false }) -- Don't enforce show for TalkingHead (it may be disabled)
-
-    -- Hook SetPoint to prevent position jumping during Edit Mode transitions
-    if not TalkingHeadFrame._orbitSetPointHooked then
-        hooksecurefunc(TalkingHeadFrame, "SetPoint", function(f, ...)
-            if f._orbitRestoringPoint then
-                return
-            end
-            -- If Blizzard tries to reposition, immediately restore our position
-            if f:GetParent() == self.frame then
-                local point = ...
-                if point ~= "CENTER" then
-                    f._orbitRestoringPoint = true
-                    f:ClearAllPoints()
-                    f:SetPoint("CENTER", self.frame, "CENTER", 0, 0)
-                    f._orbitRestoringPoint = nil
-                end
-            end
-        end)
-        TalkingHeadFrame._orbitSetPointHooked = true
-    end
-end
-
-function Plugin:ApplySettings()
-    local frame = self.frame
-    if not frame then
-        return
-    end
-    if InCombatLockdown() then
-        Orbit.CombatManager:QueueUpdate(function()
-            self:ApplySettings()
-        end)
-        return
-    end
-
-    local isEditMode = Orbit:IsEditMode()
-    local scale = self:GetSetting(SYSTEM_ID, "Scale") or 100
-    local disable = self:GetSetting(SYSTEM_ID, "DisableTalkingHead")
-    self:ReparentAll()
-    frame:SetScale(scale / 100)
-
-    -- Handle disable logic (don't disable during Edit Mode to allow configuration)
-    if disable and not isEditMode then
-        frame:Hide()
-        if TalkingHeadFrame then
-            OrbitEngine.NativeFrame:Disable(TalkingHeadFrame)
-        end
-    else
-        frame:Show()
-        if TalkingHeadFrame then
-            if OrbitEngine.NativeFrame:IsDisabled(TalkingHeadFrame) then
-                OrbitEngine.NativeFrame:Enable(TalkingHeadFrame)
-            end
-
-            -- Resize container to match TalkingHead content
-            local w, h = TalkingHeadFrame:GetSize()
-            if w and h and w > 0 and h > 0 then
-                frame:SetSize(w, h)
-            end
-
-            if isEditMode then
-                TalkingHeadFrame:Show()
-            end
-        end
-    end
-
-    -- Apply MouseOver
-    self:ApplyMouseOver(frame, SYSTEM_ID)
-end
-
--- [ BLIZZARD HIDER ]--------------------------------------------------------------------------------
-Orbit:RegisterBlizzardHider("Talking Head", function()
-    if TalkingHeadFrame then OrbitEngine.NativeFrame:SecureHide(TalkingHeadFrame) end
-end)
diff --git a/Orbit/Plugins/Misc/readme.md b/Orbit/Plugins/Misc/readme.md
deleted file mode 100644
index b0f7feb..0000000
--- a/Orbit/Plugins/Misc/readme.md
+++ /dev/null
@@ -1,25 +0,0 @@
-# misc
-
-standalone utility plugins that do not fit into other domains.
-
-## purpose
-
-small, self-contained features that enhance the hud without belonging to any major system.
-
-## files
-
-| file | responsibility |
-|---|---|
-| TalkingHead.lua | reskins and repositions the talking head npc dialog frame. |
-
-## adding a new misc plugin
-
-1. create a new lua file in this directory
-2. register via `Orbit:RegisterPlugin("Orbit_NewMisc", { ... })`
-3. keep it self-contained. if it grows beyond ~200 loc, consider promoting it to its own domain
-4. add to `Orbit.toc` and `DefaultProfile.lua`
-
-## rules
-
-- misc plugins must not depend on other plugins
-- if a misc plugin grows to need multiple files, promote it to its own domain directory
diff --git a/Orbit/Plugins/README.md b/Orbit/Plugins/README.md
index e7790b9..ead0173 100644
--- a/Orbit/Plugins/README.md
+++ b/Orbit/Plugins/README.md
@@ -17,7 +17,7 @@ Plugins/
   GroupFrames/      -- group unit frames (party + raid, tier-adaptive)
   MenuItems/        -- micro menu, bag bar, queue status
   Minimap/          -- minimap replacement (experimental)
-  Misc/             -- miscellaneous (talking head)
+  Extras/           -- small standalone plugins that don't fit a larger bounded context (talking head)
   Tracked/          -- tracked ability bars and icons
   UnitFrames/       -- player, target, focus frames and their sub-frames
 ```
@@ -54,6 +54,6 @@ sequenceDiagram
 - plugins may depend on core. they must never depend on other plugins.
 - inter-plugin communication must go through the eventbus, never direct calls
 - each plugin manages its own frames, events, and settings
-- plugin files must not exceed ~1000 loc. decompose into sub-files when approaching this limit.
+- plugin files decompose when they hold multiple responsibilities, not because they cross a line count. LOC is a smell, not a rule.
 - all constants at file top. no magic numbers.
 - follow the existing patterns: `PluginMixin` for settings, `Frame:AttachSettingsListener` for canvas, `Skin` for visuals
diff --git a/Orbit/Plugins/Tracked/README.md b/Orbit/Plugins/Tracked/README.md
index f4390d6..62e564e 100644
--- a/Orbit/Plugins/Tracked/README.md
+++ b/Orbit/Plugins/Tracked/README.md
@@ -1,60 +1,235 @@
-# tracked (stub)
+# tracked
 
-stub registration for the tracked plugin. the frame/layout/ticker implementation was deleted during the redesign “Ç
+user-authored cooldown surfaces. spawns icon-grid containers and single-spell bars from tabs added to blizzard's `CooldownViewerSettings` frame. each container is a record under `OrbitDB.GlobalSettings.TrackedContainers`, keyed by a monotonic counter id that doubles as the system index.
 
-## purpose
+## why it was rewritten
 
-the previous tracked plugin owned two frame types (tracked icons grid, tracked bar charges) with heavy coupling to edit mode anchoring, spec reconciliation, and content-empty virtualization. the spec-change reconcile path proved too brittle (see the anchor graph rescue logic in `Core/EditMode/Frame/Position/Anchor.lua`). the whole plugin is being redesigned; this stub is the landing pad.
+the old plugin was a single shared spec-scoped slot range with two frame types (icon grid + bar charges) reusing the same system indices across specs. that forced frames to "transform" between modes when the user swapped specs, and the spec-reconcile path was the most fragile area in the codebase. this rewrite drops the slot model entirely:
+
+- **distinct frame types per record** “Ç
+- **globally-unique counter ids** “Ç
+- **flat global record table** “Ç
 
 ## files
 
 | file | responsibility |
 |---|---|
-| TrackedPlugin.lua | plugin registration (`Orbit_Tracked`), spec-scoped `GetSetting`/`SetSetting` overrides, `SeedAllSpecSpatialData` for per-spec store bootstrapping. no frame creation, no apply, no event wiring. |
-| Tracked.xml | loads `TrackedPlugin.lua`. |
-
-> the drag-and-drop helpers previously in `TrackedDragDrop.lua` have moved to `Core/Shared/CooldownDragDrop.lua` (namespace `Orbit.CooldownDragDrop`) so both the redesigned Tracked plugin and `CooldownManager/ViewerInjection.lua` can consume them without creating a plugin-to-plugin load-order dependency.
-
-## preserved surface area
-
-**saved data contract** (unchanged):
-- `OrbitDB.SpecData[charKey][specID][systemIndex]["TrackedItems"]` “å
-- `OrbitDB.SpecData[charKey][specID][systemIndex]["TrackedBarSpell"]` “å
-- `OrbitDB.SpecData[charKey][specID][systemIndex]["TrackedBarChildren"]` “å
-- `Position` / `Anchor` keys also spec-scoped
-
-**system index range** (unchanged, reserved in `Constants.Tracked.SystemIndex`):
-- `Tracked = 4`, `Tracked_ChildStart = 5` (+14 child slots)
-- `TrackedBar = 20`, `TrackedBar_ChildStart = 21` (+4 child slots)
-
-**drag-and-drop helpers** (`Orbit.CooldownDragDrop`, loaded from `Core/Shared/CooldownDragDrop.lua`):
-- `HasCooldown(itemType, id)` “Ç
-- `ResolveCursorInfo()` “å
-- `ResolveSpellFromCursor()` “å
-- `IsDraggingCooldownAbility()` “å
-- `IsChargeSpell(spellId)` “å
-- `ResolveEquipmentSlot(itemId)` “å
-- `GetCursorTexture()` “å
-- `BuildTrackedItemEntry(itemType, itemId, x, y)` “å
-- `BuildInjectedItemEntry(itemType, itemId, afterNativeIndex)` “å
-- `BuildTrackedBarSpellEntry(spellId, maxCharges)` “å
-
-## what was deleted
-
-- frame creation (tracked anchor, charge bar, child frames, seed buttons, drop highlights)
-- layout engines (icon grid, bar dividers, tick marks, recharge segment)
-- ticker updates (cooldown swipe, glow, text, recharge animation)
-- control buttons (+/-) for child spawn/despawn
-- canvas mode previews
-- settings schema (sub-tabs, widgets)
-- edit mode hooks (enter/exit, selection refresh)
-- spec reconcile orchestration (`PerformSpecReconcile`, `FlushTrackedSpatial`, `SyncChildrenForCurrentSpec`, `ReloadTrackedBarsForSpec`)
-- keybind facade wrapper (moved into `CooldownManager.lua` directly as `GetSpellKeybind`/`GetItemKeybind`)
-
-## rules for the redesign
-
-- reuse the existing system index range so saved data remains addressable
-- use the `Orbit.CooldownDragDrop` helpers rather than re-implementing cursor resolution
-- keep spec-scoped keys flowing through `GetSetting`/`SetSetting` so writes land in the right spec bucket
-- when adding back frame creation, avoid the virtual/disabled toggle pattern that burned the previous design “Ç
-- do **not** reintroduce a cross-domain cursor watcher. the previous `TrackedUpdater.lua` polled the cursor for BOTH Tracked and `ViewerInjection`, which silently broke injection the moment Tracked was removed. each consumer owns its own cursor poll now (`ViewerInjection:StartCursorWatcher`) and any redesigned Tracked plugin must do the same.
+| TrackedPlugin.lua | plugin registration (`Orbit_Tracked`), record store, counter id allocation, `CreateIconContainer`/`CreateBar`/`DeleteContainer`, `RefreshForCurrentSpec`, `GetSetting`/`SetSetting` redirect by systemIndex “å
+| TrackedIconItem.lua | factory for individual icon-item buttons (icon + cooldown swipe + charge text + drop highlight). pure factory, stateless, container owns the ticker |
+| TrackedContainer.lua | icons-mode container frame. sparse 2D grid, neighbor-expansion drop zones, drop receive pipeline, per-container cursor watcher and ticker, shift-right-click empty container “å
+| TrackedBar.lua | single-spell bar frame. status bar driven by spell cooldown remaining-percent (via `IDENTITY_CURVE`), two-step shift-right-click ladder (clear spell “å
+| TrackedSettings.lua | `AddSettings` dispatch “Ç
+| Tracked.xml | load order: TrackedPlugin “å
+
+## record shape
+
+```lua
+OrbitDB.GlobalSettings.TrackedContainers[1042] = {
+    id = 1042,
+    mode = "icons",            -- "icons" | "bar"
+    spec = 257,                -- specID, only frames for current spec are built
+    grid = {                   -- only present for icons mode
+        ["0,0"] = { type = "spell", id = 12345, x = 0, y = 0, ... },
+        ["1,0"] = { type = "item",  id = 67890, x = 1, y = 0, ... },
+    },
+    spell = nil,               -- only present for bar mode (single spell)
+    settings = {               -- per-container settings
+        Position = { ... },
+        IconSize = 36,
+        IconPadding = 2,
+    },
+}
+
+OrbitDB.GlobalSettings.NextTrackedContainerId = 1043
+```
+
+settings live on the record, not under `Orbit.runtime.Layouts`. the plugin overrides `GetSetting`/`SetSetting` to redirect by systemIndex “å
+
+## tabs in the cooldown viewer settings
+
+both tabs are registered with the `Orbit_CooldownViewerExtensions` plugin (see `Orbit/Plugins/CooldownViewerExtensions/`), which owns the blizzard `ADDON_LOADED` hook for `Blizzard_CooldownViewer` and the anchor chain below `AurasTab`. tracked itself does not touch blizzard frames.
+
+| tab | atlas | action |
+|---|---|---|
+| Icons | `communities-chat-icon-plus` | spawn a new icon-grid container for the current spec |
+| Bars  | `communities-chat-icon-plus` | spawn a new single-spell bar for the current spec |
+
+tabs are click-to-spawn buttons, **not** panel switchers. clicking does not change `displayMode` on the parent frame; the cooldown viewer's content panel stays on whatever the user last selected.
+
+## per-spec caps
+
+- `Constants.Tracked.MaxIconContainers = 10` icon containers per spec
+- `Constants.Tracked.MaxBars = 10` bars per spec
+
+caps are independent (icons and bars don't share a budget). the plugin checks the cap on every create call and prints a message if it's reached.
+
+## drag-and-drop visuals
+
+both modes use the same three-layer drop zone treatment, so the visual language is consistent across the plugin:
+
+- backdrop: `cdm-empty` (deepest, matches the cooldown viewer's empty slot art)
+- tint: `talents-node-choiceflyout-square-green` (over the backdrop)
+- plus glyph: `bags-icon-addslots`, centered
+
+the whole zone is at **alpha 0.4 idle, 1.0 hover**. for icon containers each zone is its own child frame so we use frame-level `SetAlpha` (cascades to all child textures). bars share their drop hint textures with the bar frame itself, so they set per-texture alpha via a `SetDropHintAlpha(frame, a)` helper to avoid dimming the StatusBar.
+
+bars use the same three-layer treatment but the drop hint is sized to a `height x height` square anchored at the bar's TOPLEFT “Ç
+
+drop hints are shown whenever `Plugin:ShouldShowDropHints(isEmpty)` returns true, which is `IsDraggingCooldownAbility()` OR `CooldownViewerSettings:IsShown()` OR (`Orbit:IsEditMode()` AND the frame is empty). the latter two conditions are what make empty containers discoverable “Ç
+
+## border merging
+
+both modes set `anchorOptions.mergeBorders = true`. when two tracked frames (or a tracked frame and any other orbit frame with the same flag) are anchored at zero padding, `Orbit.Skin.GroupBorder:UpdateGroupBorder` walks the chain and draws a single wrapper border around the merged group instead of one border per frame. icon containers also set `frame._isIconContainer = true` so the wrapper picks the **Global Icon Border Style** (matching the cooldown manager's essential/utility group). bars do not flag themselves as icon containers, so a chain of bars uses the regular **Global Border Style** “Ç
+
+icon containers also merge their *internal* icon borders when `IconPadding == 0`. at zero padding, `Orbit.Skin.Icons:ApplyCustom` hides every per-icon border (its `mergeIconBorders` branch) and the container has to draw one wrapper border around the whole grid via `Skin:ApplyIconGroupBorder`. `TrackedContainer:Apply` does this at the end of every layout pass: applies the icon group border when padding is 0 and grid is non-empty, clears it otherwise. this matches the cooldown manager's essential/utility behavior “Ç
+
+## icon skinning
+
+each icon item is skinned via `Orbit.Skin.Icons:ApplyCustom(icon, skinSettings)` after every layout pass, with `skinSettings = OrbitEngine.CooldownUtils:BuildSkinSettings(plugin, record.id)`. that builder sets `iconBorder = true` and `borderSize = GlobalSettings.BorderSize`, so tracked icons inherit the **Global Icon Border Style** and **Border Size** the same way the cooldown manager does. each call also installs the swipe color hooks and pixel texcoord trim “Ç
+
+## global font / outline
+
+charge text on icons and the name/timer text on bars read from `GlobalSettings.Font` and `GlobalSettings.FontOutline` on every Apply pass. fonts are pulled via `Plugin:GetGlobalFont()` (LSM-fetched) and outlines via `Orbit.Skin:GetFontOutline()`. since `PluginMixin` auto-subscribes to `SETTINGS_CHANGED` and reroutes to `ApplySettings`, changing the global font in the settings panel propagates to all live tracked frames without an explicit listener “Ç
+
+## icons mode “Ç
+
+the algorithm is the one from the pre-redesign tracked plugin (commit 8ed3a8c, `TrackedLayout.lua`). sparse grid keyed by `"x,y"` strings. when a user drags a cooldown ability over the container, every existing item exposes its empty cardinal neighbors as drop zones. the grid extends in any direction up to `MAX_GRID_REACH` (10 cells in any direction from origin).
+
+`blockedDirections` walks `OrbitEngine.FrameAnchor.anchors` to find the container's parent edge and any docked children, and refuses to grow into those directions so the drag preview can't push the grid into another orbit frame.
+
+dropping on the container body (not a specific zone) lands at the next free right-edge slot. dropping on an empty container lands at `0,0`.
+
+## bars mode “Ç
+
+each bar holds **one** spell. attempting to drop a second spell while one is already assigned does nothing (with a `Orbit:Print` hint). to replace a spell, the user must:
+
+1. shift-right-click the bar “å
+2. drop the new spell on the empty bar
+
+shift-right-clicking an already-empty bar deletes the bar entirely. this two-click ladder is intentional “Ç
+
+## deletion
+
+| target | gesture | result |
+|---|---|---|
+| icon item inside a container | shift-right-click | item removed from grid, container resizes |
+| empty icon container | shift-right-click on container body | container deleted |
+| empty icon container (settings panel open) | shift-right-click on the visible drop zone | container deleted |
+| populated bar | shift-right-click | spell cleared, bar stays |
+| empty bar | shift-right-click | bar deleted |
+
+there is no delete tab. all deletion goes through shift-right-click. records are removed from the store on delete; the counter id is never reused.
+
+**descendant cleanup on delete.** wow frames can never be destroyed “Ç
+
+- `Anchor:GetAnchoredChildren(frame)` “Ç
+- `Anchor:GetLogicalChildren(frame)` “Ç
+
+note: when the cooldown viewer settings panel is open, an empty icon container's drop zone covers the container body and intercepts mouse input, so the shift-right-click delete is handled on the drop zone itself (it routes to `DeleteContainer` when the grid is empty). bars don't have this problem because their drop hint is a child texture of the bar frame, not a separate child frame, so clicks bubble straight to the bar's mouse handler.
+
+## bulk flush “Ç
+
+`Plugin:FlushCurrentSpec` walks the store and calls `DeleteContainer` on every record whose `spec` field matches the current spec. records for other specs are left untouched. used to recover from a desync where the per-spec cap blocks new containers because the store holds dormant records the user can't see (e.g. seeded by an earlier dev iteration that never built into a frame). exposed via `/orbit tracked flush` (slash dispatch in `Core/Config/Entry/SlashCommands.lua`).
+
+## spec switching
+
+`PLAYER_ENTERING_WORLD`, `ACTIVE_TALENT_GROUP_CHANGED`, and `PLAYER_SPECIALIZATION_CHANGED` all trigger `RefreshForCurrentSpec`:
+
+1. build a live frame for every record in the store that doesn't have one yet (across all specs, not just the current one)
+2. **first pass**: disable every frame whose record's spec doesn't match the current spec, and tear down frames whose store entry vanished
+3. **second pass**: enable every frame whose record's spec matches the current spec
+
+the disable-then-enable split matters when two records sit at the "same logical slot" (one per spec, e.g. each spec has its own TrackedIcon between FrameA and FrameC). a single-pass loop iterates `pairs(self.containers)` in arbitrary order, so during the swap there could be a window where both records are live anchor targets “Ç
+
+frames are **not destroyed** on a spec swap. every record gets a long-lived frame that stays in the AnchorGraph as a routing node, and the per-spec toggle is just `Anchor:SetFrameDisabled`. this is what makes anchor chains survive spec swaps “Ç
+
+`Plugin:SetContainerActive(frame, active)`:
+
+- **active = true**: `SetFrameDisabled(frame, false)` clears the skip flag and schedules a chain reconcile, then `RestorePosition` re-applies the saved Anchor (because `ParkFrame` only changed the visual SetPoint, not the graph entry “Ç
+- **active = false**: `SetFrameDisabled(frame, true)` marks the frame skipped, parks it at `defaultPosition`, and schedules a reconcile that promotes its children to the nearest non-skipped ancestor. then `frame:Hide()`.
+
+this is the same pattern `PlayerResources` / `PlayerPower` use for plugin enable/disable.
+
+## anchor chains across spec swaps
+
+if the user builds a chain like `FrameA > TrackedIcon > FrameC`, then swaps to a spec where TrackedIcon doesn't belong, the chain collapses to `FrameA > FrameC` for the duration of the swap and snaps back when the user swaps home. the mechanism:
+
+1. on swap, `SetContainerActive(TrackedIcon, false)` calls `Anchor:SetFrameDisabled(TrackedIcon, true)` which marks TrackedIcon as skipped in the AnchorGraph and schedules `ReconcileChain` from the chain root.
+2. `ReconcileChain` walks from FrameA, hits TrackedIcon, sees `IsSkipped(TrackedIcon) == true`, and calls `PromoteGrandchild(FrameC, FrameA)` “Ç
+3. on swap home, `SetContainerActive(TrackedIcon, true)` calls `SetFrameDisabled(TrackedIcon, false)`. another `ReconcileChain` runs, walks the chain, and hits `RestoreLogicalChildren(TrackedIcon)`. that walks `logicalChildrenOf[TrackedIcon]`, finds FrameC (whose logical parent is still TrackedIcon but whose physical parent is now FrameA), and re-anchors it back to TrackedIcon.
+
+this only works because TrackedIcon's frame stays alive across the swap. if we tore the frame down (the previous behavior), the AnchorGraph would lose the TrackedIcon node entirely, FrameC's logical pointer would dangle at a destroyed frame, and `ReconcileChain` would have nothing to walk through. that's why `BuildContainer` is now called for **every** record (not just current-spec), and `RefreshForCurrentSpec` toggles state instead of building/tearing.
+
+### two-Tracked-frames per spec “Ç
+
+each Tracked record is its own globally-unique frame name (`OrbitTrackedContainer1042`, `OrbitTrackedContainer1043`, ...). when the user wants the SAME logical chain shape on two specs (e.g. `FrameA > TrackedIcon > FrameC` on both), each spec gets its own TrackedIcon record with its own name. all three cases now work via target-driven per-spec anchor routing (see "per-spec anchor routing for non-spec-scoped consumers" below):
+
+- **FrameC is also a Tracked frame** “£à
+- **FrameC's plugin is spec-scoped** (`IsSpecScopedIndex` returns true “Ç
+- **FrameC's plugin is NOT spec-scoped** “£à
+
+## per-spec anchor routing for non-spec-scoped consumers
+
+a plugin's saved Anchor/Position normally lives in the global layout DB (one value across all specs). that's correct when the chain is stable across specs, but breaks when the **target** frame is per-spec “Ç
+
+`Engine.FramePersistence` solves this by **target-driven per-spec routing**: when the anchor target carries `orbitAnchorTargetPerSpec = true`, the consumer's saved anchor is written to the consumer plugin's per-spec store (`PluginMixin:SetSpecData`) regardless of whether the consumer plugin itself is spec-scoped. the consumer doesn't have to opt in or know the target is per-spec.
+
+| helper | behavior |
+|---|---|
+| `Persistence:WriteAnchor(plugin, systemIndex, anchor)` | `IsSpecScopedIndex` true OR target has `orbitAnchorTargetPerSpec` “å
+| `Persistence:WritePosition(plugin, systemIndex, pos)` | `IsSpecScopedIndex` true OR consumer already has current-spec data ("sticky") “å
+| `Persistence:ReadAnchor(plugin, systemIndex)` | spec data first, fall back to global plugin setting. plugins that never wrote spec data are unaffected. |
+| `Persistence:ReadPosition(plugin, systemIndex)` | same “Ç
+
+`PositionManager:FlushToStorage` (edit-mode close) and the `Persistence:AttachSettingsListener` drag callback (immediate-write safety net for /reload between drag-stop and flush) both go through these helpers, so the routing stays consistent. the immediate-write path only fires when the routing would actually go to spec data “Ç
+
+**spec-change re-restore.** writing the anchor to spec data is only half the story. on a spec swap, the consumer's *live* AnchorGraph entry still points at the previous spec's target frame; without a re-restore pass, width sync would still flow through `PromoteGrandchild`'s ancestor route but the visual position would land on the ancestor instead of the new spec's intended target. `Persistence._attachedFrames` is a weak-keyed registry of every frame that's been wired through `AttachSettingsListener`. on `PLAYER_SPECIALIZATION_CHANGED`, `Persistence:RestoreAffectedBySpecChange` walks the registry (deferred two frames so `RefreshForCurrentSpec` and the subsequent `ReconcileChain` flush both settle first) and re-runs `RestorePosition` for any consumer whose plugin uses spec-scoped storage. consumers with no per-spec data are no-ops (re-anchor to the same global target); consumers like the player frame anchored to a Tracked container pick up the new spec's saved entry and re-anchor to the right `OrbitTrackedContainer<id>`.
+
+`Tracked` opts in by setting `frame.orbitAnchorTargetPerSpec = true` on both `TrackedContainer` and `TrackedBar` build paths. any future plugin whose frames are inherently per-spec can set the same flag to make non-spec-scoped consumers route correctly.
+
+`Tracked` itself sets `plugin.settingsArePerSpec = true` in its registration block, which OPTS OUT of `Persistence`'s spec-data routing for the saving direction. each Tracked record carries its own `spec` field and `record.settings` is already partitioned per-spec at the storage layer (every spec gets its own record with its own `id`), so a second per-spec layer would silently desync from `record.settings` on subsequent edits and would not survive a profile export (spec data is per-character, record settings travel with the profile). when one Tracked container anchors to another Tracked container (which has `orbitAnchorTargetPerSpec`), the consumer-side write goes through `record.settings.Anchor` like any other Tracked setting, not through `SetSpecData`.
+
+`DeleteContainer` cleans up dangling consumer references in BOTH stores: it walks `GetAnchoredChildren` and `GetLogicalChildren`, and for each one calls `ClearChildSavedAnchorIfTargets` which checks the consumer's spec data AND global setting and only nulls an entry if its `target` field actually names the deleted frame (so unrelated anchors aren't nuked).
+
+## empty containers are virtual
+
+an empty tracked frame (icon container with no grid items, or bar with no spell) must not be a valid anchor *target*. without this, `FrameA > TrackedIcons (empty) > FrameC` traps FrameC behind a content-less frame and FrameC has no way to promote up to FrameA “Ç
+
+`Plugin:RefreshContainerVirtualState(frame)` toggles the AnchorGraph virtual flag on the frame based on record emptiness:
+
+- icons mode: `not record.grid or next(record.grid) == nil`
+- bar mode: `not record.spell or not record.spell.id`
+
+it's hooked into both `Container:Apply` and `Bar:Apply`, which run on every content mutation (drop, item removal, spell clear, spec swap). a `frame._isVirtual` guard avoids redundant `SetFrameVirtual` calls “Ç
+
+**empty containers stay selectable and movable in edit mode, but cannot anchor as children OR be valid snap targets for other frames.** four engine side effects need to be controlled to make this work:
+
+1. **position park** “Ç
+2. **orbitDisabled flag** “Ç
+3. **no-snap as child** “Ç
+4. **filtered out of snap targets** “Ç
+
+the virtual axis composes with the disabled axis: a frame is "skipped" when virtual OR disabled. so an empty TrackedIcons that's also off-spec is doubly-skipped. when the user swaps to its home spec, `SetContainerActive` clears the disabled axis and runs `RestorePosition`; the virtual flag stays on (set during the previous Apply), children stay routed past the frame, and the user can still see and drag the empty container at its restored position. once they add content, `Apply` “å
+
+## position / anchor restore
+
+`BuildContainer` sets `frame.defaultPosition` (CENTER of UIParent + per-mode offset) and then calls `Orbit.Engine.Frame:RestorePosition(frame, self, record.id)`. that walks the standard restore path: ephemeral PositionManager state “å
+
+saving goes through `Persistence:WriteAnchor` / `WritePosition`, which `PositionManager:FlushToStorage` (called when edit mode closes) and the drag-callback immediate-write path both share. for a Tracked container as a CONSUMER (its own anchor save), `WriteAnchor` sees `IsSpecScopedIndex` is false but the plugin's `SetSetting` override still redirects the call into `record.settings.Anchor` “Ç
+
+## secret-value safety
+
+bar fill remaining-percent comes from `durObj:EvaluateRemainingPercent(IDENTITY_CURVE)`. without a numeric curve, that call returns a secret value and any arithmetic on it would throw in combat. the curve maps percent “å
+
+icon mode uses `Cooldown:SetCooldownFromDurationObject(durObj, true)` which is a c++ sink “Ç
+
+charge text uses the sink pattern: `chargeInfo.currentCharges` is piped straight into `FontString:SetText` without any boolean test or comparison, since `SetText` accepts secret values.
+
+## rules for future changes
+
+- reuse `Orbit.CooldownDragDrop` (in `Core/Shared/CooldownDragDrop.lua`) for cursor resolution and saved-data builders. don't reimplement.
+- each consumer owns its own cursor poll. do not introduce a cross-domain cursor watcher “Ç
+- container records are flat. do not add per-spec or per-mode sub-tables to the store; the `spec` and `mode` fields on each record are how you filter.
+- shift-right-click is the deletion gesture across the entire plugin. do not add delete buttons or per-item context menus.
+- counter ids are sparse and global. do not add an "id reuse" optimization; the engine handles arbitrary system index keys (verified “Ç
+- adding a new mode (e.g. "ring") means a new file (`TrackedRing.lua`) and a new branch in `TrackedPlugin:BuildContainer` / `ApplySettings` / `TrackedSettings:_Build*Settings`. do not transform existing frames between modes.
diff --git a/Orbit/Plugins/Tracked/Tracked.xml b/Orbit/Plugins/Tracked/Tracked.xml
index 17dd5b0..3e0ca89 100644
--- a/Orbit/Plugins/Tracked/Tracked.xml
+++ b/Orbit/Plugins/Tracked/Tracked.xml
@@ -1,3 +1,7 @@
 <Ui xmlns="http://www.blizzard.com/wow/ui/">
     <Script file="TrackedPlugin.lua"/>
+    <Script file="TrackedIconItem.lua"/>
+    <Script file="TrackedContainer.lua"/>
+    <Script file="TrackedBar.lua"/>
+    <Script file="TrackedSettings.lua"/>
 </Ui>
diff --git a/Orbit/Plugins/Tracked/TrackedPlugin.lua b/Orbit/Plugins/Tracked/TrackedPlugin.lua
index 184c035..243a374 100644
--- a/Orbit/Plugins/Tracked/TrackedPlugin.lua
+++ b/Orbit/Plugins/Tracked/TrackedPlugin.lua
@@ -1,105 +1,510 @@
 -- [ ORBIT TRACKED PLUGIN ] ----------------------------------------------------
--- Stub registration for the Tracked plugin. The previous frame/layout/ticker
--- implementation was deleted during the redesign “Ç
--- registered so saved spell data in OrbitDB.SpecData stays reachable, then the
--- new design builds frames and visuals on top.
+-- Spawns user-authored cooldown containers (icon grids) and single-spell bars
+-- from tabs added to Blizzard's CooldownViewerSettings frame. Each container is
+-- a flat record under OrbitDB.GlobalSettings.TrackedContainers, keyed by a
+-- monotonic counter id that doubles as the system index. Records carry their
+-- own spec field; only records matching the current spec get a live frame.
 --
--- What survives:
---   * Plugin identity (Orbit_Tracked) and spec-scoped system index range
---   * Per-character, per-spec GetSetting/SetSetting overrides
---   * Spec store seeding so writes don't need nil-checks at every level
---   * Drag-and-drop cursor helpers (moved to Core/Shared/CooldownDragDrop.lua,
---     namespace Orbit.CooldownDragDrop, shared with CooldownManager/ViewerInjection)
+-- Architecture:
+--   * Container records live in a flat global table “å
+--   * Counter ensures ids are sparse and never reused
+--   * mode = "icons" or "bar" “Ç
+--   * Per-spec caps (MaxIconContainers / MaxBars) enforced at create time
+--   * Settings are stored on the record itself, not in the layout DB, so
+--     GetSetting/SetSetting are overridden to redirect by systemIndex “å
+--   * Tab buttons are click-to-spawn (not panel switchers); registered with
+--     Orbit_CooldownViewerExtensions which owns the Blizzard ADDON_LOADED hook
 local _, Orbit = ...
 
 local Constants = Orbit.Constants
+local DragDrop = Orbit.CooldownDragDrop
 
 -- [ CONSTANTS ] ---------------------------------------------------------------
-local TRACKED = Constants.Tracked.SystemIndex.Tracked
-local TRACKED_BAR = Constants.Tracked.SystemIndex.TrackedBar
-
--- [ SPEC-SCOPED KEYS ] --------------------------------------------------------
--- Keys that must be stored per-character per-spec instead of the global profile.
--- Mirrors the saved-data contract the drag/drop helpers produce.
-local SPEC_SCOPED_KEYS = {
-    ["TrackedItems"] = true,
-    ["TrackedBarSpell"] = true,
-    ["TrackedBarChildren"] = true,
-    ["Position"] = true,
-    ["Anchor"] = true,
-}
-
-local function IsSpecScopedIndex(sysIdx)
-    if sysIdx >= Constants.Tracked.SystemIndex.Tracked and sysIdx <= (Constants.Tracked.SystemIndex.Tracked + Constants.Tracked.MaxChildFrames - 1) then
-        return true
-    end
-    if sysIdx >= Constants.Tracked.SystemIndex.TrackedBar and sysIdx <= (Constants.Tracked.SystemIndex.TrackedBar + Constants.Tracked.MaxBarChildren - 1) then
-        return true
-    end
-    return false
-end
+local MAX_ICON_CONTAINERS = Constants.Tracked.MaxIconContainers
+local MAX_BARS = Constants.Tracked.MaxBars
+local SYSTEM_ID_BASE = Constants.Tracked.SystemIndexBase
+local TAB_ATLAS = "communities-chat-icon-plus"
+local TAB_ID_ICONS = "Orbit_Tracked.Icons"
+local TAB_ID_BARS = "Orbit_Tracked.Bars"
+local DEFAULT_ICON_OFFSET_Y = 0
+local DEFAULT_BAR_OFFSET_X = 250
 
+-- [ PLUGIN REGISTRATION ] -----------------------------------------------------
+-- settingsArePerSpec: opts Tracked OUT of Persistence's spec-data routing for
+-- saved Anchor/Position. Each Tracked record carries its own `spec` field and
+-- record.settings is already per-spec at the storage layer, so a second per-
+-- spec partition (PluginMixin.SpecData) would silently desync from
+-- record.settings on subsequent edits and would not survive a profile export.
 local Plugin = Orbit:RegisterPlugin("Tracked Items", "Orbit_Tracked", {
     liveToggle = true,
-    viewerMap = {},
-    IsSpecScopedIndex = function(self, sysIdx) return IsSpecScopedIndex(sysIdx) end,
+    settingsArePerSpec = true,
+    containers = {}, -- live frames keyed by id
+    defaults = {
+        IconSize = Constants.Cooldown.DefaultIconSize,
+        IconPadding = Constants.Cooldown.DefaultPadding,
+        aspectRatio = "1:1",
+        Opacity = 100,
+        Width = 200,
+        Height = 20,
+    },
     OnLoad = function(self)
-        self:SeedAllSpecSpatialData()
+        self:EnsureStore()
+        self:RegisterTabs()
+        self:RefreshForCurrentSpec()
+        Orbit.EventBus:On("PLAYER_ENTERING_WORLD", function() self:RefreshForCurrentSpec() end, self)
+        Orbit.EventBus:On("ACTIVE_TALENT_GROUP_CHANGED", function() self:RefreshForCurrentSpec() end, self)
+        Orbit.EventBus:On("PLAYER_SPECIALIZATION_CHANGED", function() self:RefreshForCurrentSpec() end, self)
     end,
 })
 
--- [ SEED SPEC SPATIAL DATA ] --------------------------------------------------
--- Ensure every spec has an empty node for each tracked system index so the
--- redesigned consumer can write into the store without nil-checking every level.
-function Plugin:SeedAllSpecSpatialData()
-    local numSpecs = GetNumSpecializations()
-    if not numSpecs or numSpecs == 0 then return end
-    local store = self:GetCharSpecStore()
-
-    local indices = { TRACKED }
-    for s = 0, Constants.Tracked.MaxChildFrames - 1 do
-        table.insert(indices, Constants.Tracked.SystemIndex.Tracked_ChildStart + s)
-    end
-    table.insert(indices, TRACKED_BAR)
-    for s = 0, Constants.Tracked.MaxBarChildren - 1 do
-        table.insert(indices, Constants.Tracked.SystemIndex.TrackedBar_ChildStart + s)
-    end
-
-    for i = 1, numSpecs do
-        local specID = GetSpecializationInfo(i)
-        if specID then
-            if not store[specID] then store[specID] = {} end
-            for _, sysIdx in ipairs(indices) do
-                if not store[specID][sysIdx] then store[specID][sysIdx] = {} end
-            end
+-- [ STORE BOOTSTRAP ] ---------------------------------------------------------
+function Plugin:EnsureStore()
+    local gs = Orbit.db.GlobalSettings
+    if not gs.TrackedContainers then gs.TrackedContainers = {} end
+    if not gs.NextTrackedContainerId then gs.NextTrackedContainerId = SYSTEM_ID_BASE end
+end
+
+function Plugin:GetStore()
+    return Orbit.db.GlobalSettings.TrackedContainers
+end
+
+function Plugin:AllocateId()
+    local gs = Orbit.db.GlobalSettings
+    local id = gs.NextTrackedContainerId
+    gs.NextTrackedContainerId = id + 1
+    return id
+end
+
+-- [ RECORD QUERIES ] ----------------------------------------------------------
+function Plugin:GetContainerRecord(id)
+    return self:GetStore()[id]
+end
+
+function Plugin:ContainersForSpec(specID, mode)
+    local out = {}
+    for _, record in pairs(self:GetStore()) do
+        if record.spec == specID and (not mode or record.mode == mode) then
+            table.insert(out, record)
         end
     end
+    return out
 end
 
--- [ SETTINGS OVERRIDES ] ------------------------------------------------------
--- Redirect spec-scoped keys into the per-character spec store. Data-only keys
--- (TrackedBarSpell / TrackedItems / TrackedBarChildren) return nil instead of
--- falling through to the global profile, so legacy global values can't leak
--- into an empty spec.
+function Plugin:CountForSpec(specID, mode)
+    local n = 0
+    for _, record in pairs(self:GetStore()) do
+        if record.spec == specID and record.mode == mode then n = n + 1 end
+    end
+    return n
+end
+
+-- [ SETTINGS REDIRECT ] -------------------------------------------------------
+-- Container records own their settings inline; redirect by systemIndex “å
+-- Anything that isn't a container falls through to the standard layout DB so
+-- shared keys (Texture, Font, etc) keep flowing through global inheritance.
 local OriginalGetSetting = Orbit.PluginMixin.GetSetting
 local OriginalSetSetting = Orbit.PluginMixin.SetSetting
 
 function Plugin:GetSetting(systemIndex, key)
-    if IsSpecScopedIndex(systemIndex) and SPEC_SCOPED_KEYS[key] then
-        local specVal = self:GetSpecData(systemIndex, key)
-        if specVal ~= nil then return specVal end
-
-        if key == "TrackedBarSpell" or key == "TrackedItems" or key == "TrackedBarChildren" then
-            return nil
-        end
+    local record = self:GetContainerRecord(systemIndex)
+    if record then
+        local val = record.settings and record.settings[key]
+        if val ~= nil then return val end
+        if self.defaults and self.defaults[key] ~= nil then return self.defaults[key] end
+        return nil
     end
     return OriginalGetSetting(self, systemIndex, key)
 end
 
 function Plugin:SetSetting(systemIndex, key, value)
-    if IsSpecScopedIndex(systemIndex) and SPEC_SCOPED_KEYS[key] then
-        self:SetSpecData(systemIndex, key, value)
+    local record = self:GetContainerRecord(systemIndex)
+    if record then
+        record.settings = record.settings or {}
+        record.settings[key] = value
         return
     end
     OriginalSetSetting(self, systemIndex, key, value)
 end
+
+-- [ GLOBAL FONT ] -------------------------------------------------------------
+-- Returns the resolved path for GlobalSettings.Font (LSM-fetched). Tracked
+-- icons and bars call this on every Apply pass so changing the global font in
+-- the settings panel propagates without needing a full rebuild.
+function Plugin:GetGlobalFont()
+    local LSM = LibStub and LibStub("LibSharedMedia-3.0", true)
+    local fontName = Orbit.db and Orbit.db.GlobalSettings and Orbit.db.GlobalSettings.Font
+    if fontName and LSM then
+        return LSM:Fetch("font", fontName) or STANDARD_TEXT_FONT
+    end
+    return STANDARD_TEXT_FONT
+end
+
+-- [ DROP HINT VISIBILITY ] ----------------------------------------------------
+-- Drop hints (icons-mode neighbor zones, bars-mode empty bar hint) are shown
+-- whenever the user could plausibly want to see where their tracked frames are:
+-- (1) actively dragging a cooldown ability, (2) the cooldown viewer settings
+-- panel is open, or (3) edit mode is active AND the frame is empty. Cases (2)
+-- and (3) make empty containers discoverable “Ç
+-- is invisible and there's no way to find or delete it. The `isEmpty` argument
+-- gates the edit-mode case so populated frames don't sprout hints in edit mode.
+function Plugin:ShouldShowDropHints(isEmpty)
+    if DragDrop and DragDrop:IsDraggingCooldownAbility() then return true end
+    if CooldownViewerSettings and CooldownViewerSettings:IsShown() then return true end
+    if isEmpty and Orbit:IsEditMode() then return true end
+    return false
+end
+
+-- [ TAB REGISTRATION ] --------------------------------------------------------
+function Plugin:RegisterTabs()
+    local CVE = Orbit:GetPlugin("Orbit_CooldownViewerExtensions")
+    if not CVE then return end
+
+    CVE:RegisterTab({
+        id = TAB_ID_ICONS,
+        atlas = TAB_ATLAS,
+        tooltipText = "Add a new Tracked Icon container",
+        onClick = function() self:CreateIconContainer() end,
+    })
+
+    CVE:RegisterTab({
+        id = TAB_ID_BARS,
+        atlas = TAB_ATLAS,
+        tooltipText = "Add a new Tracked Bar",
+        onClick = function() self:CreateBar() end,
+    })
+end
+
+-- [ CONTAINER CREATION ] ------------------------------------------------------
+function Plugin:CreateIconContainer()
+    local specID = self:GetCurrentSpecID()
+    if not specID then return end
+    if self:CountForSpec(specID, "icons") >= MAX_ICON_CONTAINERS then
+        Orbit:Print("Tracked: max icon containers reached for this spec (" .. MAX_ICON_CONTAINERS .. ")")
+        return
+    end
+
+    local id = self:AllocateId()
+    local record = {
+        id = id,
+        mode = "icons",
+        spec = specID,
+        grid = {},
+        settings = {},
+    }
+    self:GetStore()[id] = record
+    local frame = self:BuildContainer(record)
+    self:SetContainerActive(frame, true)
+    return record
+end
+
+function Plugin:CreateBar()
+    local specID = self:GetCurrentSpecID()
+    if not specID then return end
+    if self:CountForSpec(specID, "bar") >= MAX_BARS then
+        Orbit:Print("Tracked: max bars reached for this spec (" .. MAX_BARS .. ")")
+        return
+    end
+
+    local id = self:AllocateId()
+    local record = {
+        id = id,
+        mode = "bar",
+        spec = specID,
+        spell = nil,
+        settings = {},
+    }
+    self:GetStore()[id] = record
+    local frame = self:BuildContainer(record)
+    self:SetContainerActive(frame, true)
+    return record
+end
+
+-- Builds a frame for the record, restores its anchor/position into the
+-- AnchorGraph, and applies its settings. Does NOT toggle enabled/disabled
+-- state “Ç
+-- built once for EVERY record across all specs (not just the current one)
+-- so that the anchor graph has a node for each frame; chain reconciliation
+-- relies on disabled frames remaining in the graph as skipped nodes so it
+-- can promote their children up to the nearest non-skipped ancestor.
+--
+-- RefreshContainerVirtualState is called BEFORE any explicit RestorePosition
+-- so that the first SetFrameVirtual flip happens against a frame that's never
+-- yet been positioned. RefreshContainerVirtualState calls RestorePosition
+-- internally as part of its undo-the-park step, so the frame ends up at its
+-- saved Anchor/Position with the virtual flag already correct. ApplySettings
+-- then runs Apply “å
+-- circuited by the `_isVirtual == isEmpty` guard. One restore, one reconcile.
+function Plugin:BuildContainer(record)
+    local frame
+    local defX, defY = 0, 0
+    if record.mode == "icons" then
+        frame = Orbit.TrackedContainer:Build(self, record)
+        defX, defY = 0, DEFAULT_ICON_OFFSET_Y
+    elseif record.mode == "bar" then
+        frame = Orbit.TrackedBar:Build(self, record)
+        defX, defY = DEFAULT_BAR_OFFSET_X, 0
+    end
+    if not frame then return end
+    frame.defaultPosition = { point = "CENTER", relativeTo = UIParent, relativePoint = "CENTER", x = defX, y = defY }
+    self.containers[record.id] = frame
+    self:RefreshContainerVirtualState(frame)
+    if self.ApplySettings then self:ApplySettings(frame) end
+    return frame
+end
+
+-- [ ENABLE / DISABLE ] --------------------------------------------------------
+-- Toggles a container's participation in the layout via the AnchorGraph's
+-- skipped-frame mechanism (Anchor:SetFrameDisabled). Disabling parks the
+-- frame at its defaultPosition, marks it skipped in the graph, and schedules
+-- ReconcileChain which promotes any anchored children up to the nearest
+-- non-skipped ancestor (their logical parent pointer is preserved via
+-- skipLogical=true on the physical re-anchor). Re-enabling clears the skip
+-- flag and re-applies the saved Anchor via RestorePosition (because
+-- ParkFrame only changed the visual SetPoint, not the graph entry); the
+-- next ReconcileChain pulls promoted children back home via
+-- RestoreLogicalChildren. This is the same pattern PlayerResources /
+-- PlayerPower use for plugin enable/disable.
+function Plugin:SetContainerActive(frame, active)
+    if not frame then return end
+    if active then
+        Orbit.Engine.FrameAnchor:SetFrameDisabled(frame, false)
+        self:_SyncOrbitDisabledFlag(frame)
+        Orbit.Engine.Frame:RestorePosition(frame, self, frame.recordId)
+        frame:Show()
+    else
+        Orbit.Engine.FrameAnchor:SetFrameDisabled(frame, true)
+        frame:Hide()
+    end
+end
+
+-- [ ORBIT-DISABLED FLAG SYNC ] ------------------------------------------------
+-- Anchor:SetFrameVirtual / SetFrameDisabled both write
+--     frame.orbitDisabled = Graph:IsSkipped(frame)
+-- which conflates the virtual axis (content-empty) and the disabled axis
+-- (off-spec / plugin off) into a single flag. The Selection module reads
+-- frame.orbitDisabled to decide whether to render the edit-mode selection
+-- highlight “Ç
+-- pin empty Tracked containers as un-clickable in edit mode.
+--
+-- We override the flag to reflect ONLY the disabled axis, so virtual-but-
+-- on-spec frames stay selectable. Disabled frames (off-spec, plugin off)
+-- still get the flag because IsDisabled returns true for them.
+function Plugin:_SyncOrbitDisabledFlag(frame)
+    frame.orbitDisabled = Orbit.Engine.AnchorGraph:IsDisabled(frame)
+end
+
+-- [ VIRTUAL STATE ] -----------------------------------------------------------
+-- An empty tracked container (no grid items / no spell) must not be a valid
+-- anchor TARGET. If FrameA > TrackedIcons (empty) > FrameC, FrameC needs to
+-- promote up to FrameA so it doesn't end up anchored to a frame with no
+-- content. We mark empty frames "virtual" in the AnchorGraph; ReconcileChain
+-- treats virtual frames the same as disabled ones “Ç
+-- the nearest non-skipped ancestor and snaps them back when the frame becomes
+-- non-virtual again.
+--
+-- BUT: the empty frame must still be selectable/movable in edit mode so the
+-- user can position it BEFORE adding content. Three things are needed:
+--   (1) Position: SetFrameVirtual(true) parks the frame at defaultPosition.
+--       We immediately call RestorePosition to put it back at its saved
+--       Anchor/Position. The graph keeps the skip flag (children still
+--       promote past), but the frame's physical SetPoint is restored.
+--   (2) Selection visibility: SetFrameVirtual sets frame.orbitDisabled = true,
+--       which the Selection module uses to hide the edit-mode highlight.
+--       _SyncOrbitDisabledFlag overrides it to reflect only the disabled
+--       axis so the frame stays clickable.
+--   (3) No-snap as CHILD: an empty container must also not become an anchored
+--       child of another frame (the user can drag it, but it shouldn't snap
+--       and create an anchor “Ç
+--       puts the Drag module into precision-mode for this frame (Drag.lua:143
+--       skips snap detection, Drag.lua:344 saves a raw point/x/y instead of
+--       creating an anchor). Cleared when the frame becomes non-empty so it
+--       can re-join the chain.
+--
+-- Hooked into Container:Apply and Bar:Apply (which both run on every content
+-- mutation: drop, item removal, spell clear, spec swap). The plugin-level
+-- _isVirtual guard avoids redundant SetFrameVirtual calls “Ç
+-- Apply runs on every settings change too, and SetFrameVirtual(true) re-parks
+-- unconditionally per the engine's idempotency rule.
+function Plugin:RefreshContainerVirtualState(frame)
+    if not frame or not frame.recordId then return end
+    local record = self:GetContainerRecord(frame.recordId)
+    if not record then return end
+    local isEmpty
+    if record.mode == "icons" then
+        isEmpty = not record.grid or next(record.grid) == nil
+    else
+        isEmpty = not record.spell or not record.spell.id
+    end
+    if frame._isVirtual == isEmpty then return end
+    frame._isVirtual = isEmpty
+    frame.orbitNoSnap = isEmpty
+    Orbit.Engine.FrameAnchor:SetFrameVirtual(frame, isEmpty)
+    self:_SyncOrbitDisabledFlag(frame)
+    -- Restore position regardless of direction. On the True transition,
+    -- this undoes the ParkFrame side effect so the empty container stays
+    -- where the user put it. On the False transition it's a no-op-ish
+    -- re-anchor that ensures the frame is correctly attached before the
+    -- scheduled ReconcileChain runs RestoreLogicalChildren to pull
+    -- promoted children back home.
+    Orbit.Engine.Frame:RestorePosition(frame, self, frame.recordId)
+end
+
+-- [ FLUSH CURRENT SPEC ] ------------------------------------------------------
+-- Walks the store and removes every record matching the current spec, tearing
+-- down live frames as it goes. Used to recover from a desync where the store
+-- holds dormant records the user can't see (e.g. records seeded by an earlier
+-- dev iteration that never got built into frames). Records for OTHER specs are
+-- left untouched. Returns the number of records removed.
+function Plugin:FlushCurrentSpec()
+    local specID = self:GetCurrentSpecID()
+    if not specID then return 0 end
+    local toRemove = {}
+    for id, record in pairs(self:GetStore()) do
+        if record.spec == specID then toRemove[#toRemove + 1] = id end
+    end
+    for _, id in ipairs(toRemove) do self:DeleteContainer(id) end
+    Orbit:Print("Tracked: flushed " .. #toRemove .. " container(s) for spec " .. specID)
+    return #toRemove
+end
+
+-- [ CONTAINER DELETION ] ------------------------------------------------------
+-- Detach physical and logical descendants before tearing down the frame.
+-- Frames in WoW can't be destroyed (Lua reference + name persist), so without
+-- this cleanup any frame that was anchored to (or logically routed past) the
+-- deleted Tracked container would still resolve `_G[oldName]` on the next
+-- /reload and re-attach to a hidden, no-longer-in-store ghost frame.
+--
+-- Physical children: BreakAnchor removes the graph entry and clears their
+-- logical anchor. We then wipe their saved `Anchor` setting so /reload doesn't
+-- replay the broken reference. Both the global plugin setting AND the current
+-- spec's spec-data slot need to be checked, because Tracked containers carry
+-- `orbitAnchorTargetPerSpec` and may have routed the consumer's saved anchor
+-- to either store. Only clear an entry if its `target` actually names the
+-- deleted frame “Ç
+-- graph entry was made, and we don't want to nuke an unrelated anchor.
+--
+-- Logical children: frames whose logical parent was this container but who are
+-- currently physically routed past it (because the container was virtual).
+-- Their physical anchor is fine; we only need to drop the dangling logical
+-- intent so RestoreLogicalChildren never tries to pull them home.
+local function ClearChildSavedAnchorIfTargets(child, deletedName)
+    local p = child.orbitPlugin
+    if not p or not child.systemIndex or not deletedName then return end
+    if p.GetSpecData and p.SetSpecData then
+        local sa = p:GetSpecData(child.systemIndex, "Anchor")
+        if sa and sa.target == deletedName then
+            p:SetSpecData(child.systemIndex, "Anchor", nil)
+        end
+    end
+    if p.GetSetting and p.SetSetting then
+        local ga = p:GetSetting(child.systemIndex, "Anchor")
+        if ga and ga.target == deletedName then
+            p:SetSetting(child.systemIndex, "Anchor", nil)
+        end
+    end
+end
+
+function Plugin:DeleteContainer(id)
+    local record = self:GetStore()[id]
+    if not record then return end
+    local frame = self.containers[id]
+    if frame then
+        local deletedName = frame:GetName()
+        local Anchor = Orbit.Engine.FrameAnchor
+        if Anchor then
+            for _, child in ipairs(Anchor:GetAnchoredChildren(frame)) do
+                Anchor:BreakAnchor(child, true)
+                ClearChildSavedAnchorIfTargets(child, deletedName)
+            end
+            for _, lchild in ipairs(Anchor:GetLogicalChildren(frame)) do
+                Anchor:ClearLogicalAnchor(lchild)
+                ClearChildSavedAnchorIfTargets(lchild, deletedName)
+            end
+            Anchor:BreakAnchor(frame, true)
+        end
+        frame:Hide()
+        self.containers[id] = nil
+    end
+    self:GetStore()[id] = nil
+end
+
+-- [ SPEC REFRESH ] ------------------------------------------------------------
+-- Old behavior tore down frames whose record didn't match the current spec and
+-- rebuilt them on swap-back. That severed any anchor chain that ran THROUGH a
+-- tracked frame: a child of the destroyed frame got orphaned at its old
+-- position and never re-attached when the frame came back, because the
+-- AnchorGraph entry for the destroyed frame was gone too.
+--
+-- New behavior keeps a live frame in the graph for every record across all
+-- specs and uses Anchor:SetFrameDisabled to flip off-spec frames into the
+-- "skipped" state. Skipped frames stay in the graph as routing nodes:
+-- ReconcileChain promotes their children to the nearest non-skipped ancestor
+-- (e.g. FrameA > TrackedIcon > FrameC collapses to FrameA > FrameC when the
+-- TrackedIcon's spec doesn't match), and snaps them back when the swap brings
+-- the frame home (RestoreLogicalChildren walks the original logical anchor).
+function Plugin:RefreshForCurrentSpec()
+    local specID = self:GetCurrentSpecID()
+    if not specID then return end
+
+    -- Build a live frame for any record that doesn't have one yet. This is
+    -- the first-load path AND the path for records discovered mid-session
+    -- (e.g. created on another character with a shared profile).
+    for _, record in pairs(self:GetStore()) do
+        if not self.containers[record.id] then
+            self:BuildContainer(record)
+        end
+    end
+
+    -- Two-pass toggle: disable off-spec frames FIRST, then enable on-spec
+    -- frames. Without the split there's a window where two frames at the
+    -- "same logical slot" (one per spec) are both live anchor targets “Ç
+    -- intermediate ReconcileChain or saved-anchor lookup that runs in that
+    -- window could route a child to the wrong target. Records whose store
+    -- entry vanished are torn down in the first pass too (treated like
+    -- disabled-and-then-removed).
+    for id, frame in pairs(self.containers) do
+        local record = self:GetStore()[id]
+        if not record then
+            if Orbit.Engine.FrameAnchor then
+                Orbit.Engine.FrameAnchor:BreakAnchor(frame, true)
+            end
+            frame:Hide()
+            self.containers[id] = nil
+        elseif record.spec ~= specID then
+            self:SetContainerActive(frame, false)
+        end
+    end
+    for id, frame in pairs(self.containers) do
+        local record = self:GetStore()[id]
+        if record and record.spec == specID then
+            self:SetContainerActive(frame, true)
+        end
+    end
+end
+
+function Plugin:GetFrameBySystemIndex(systemIndex)
+    return self.containers[systemIndex]
+end
+
+-- [ APPLY SETTINGS ] ----------------------------------------------------------
+-- Dispatch to the per-mode renderer. Each mode is independently responsible for
+-- its own apply path; this plugin just routes by record.mode. The settings
+-- dialog passes a wrapper object ({systemFrame, systemIndex, system}) instead
+-- of the live container frame, so we always resolve via GetFrameBySystemIndex.
+function Plugin:ApplySettings(frame)
+    if not frame then
+        for _, f in pairs(self.containers) do self:ApplySettings(f) end
+        return
+    end
+    local resolved = self:GetFrameBySystemIndex(frame.systemIndex)
+    if resolved then frame = resolved end
+    if not frame or not frame.systemIndex then return end
+
+    local record = self:GetContainerRecord(frame.systemIndex)
+    if not record then return end
+    if record.mode == "icons" and Orbit.TrackedContainer then
+        Orbit.TrackedContainer:Apply(self, frame, record)
+    elseif record.mode == "bar" and Orbit.TrackedBar then
+        Orbit.TrackedBar:Apply(self, frame, record)
+    end
+end
