local API = rawget(_G, "LIDCOUNCIL_API")
local INTERNAL_API = type(API) == "table" and type(API.__internal) == "table" and API.__internal or nil
if type(INTERNAL_API) ~= "table" then
  error("LidCouncil missing internal API registry.")
end

local RequireApi = INTERNAL_API.LIDCOUNCIL_RequireApi_Impl
local RegisterTutorialTexture = INTERNAL_API.LIDCOUNCIL_RegisterTutorialTexture_Impl
local BindMouseWheelScroll = INTERNAL_API.LIDCOUNCIL_BindMouseWheelScroll_Impl
if type(RequireApi) ~= "function"
  or type(RegisterTutorialTexture) ~= "function"
  or type(BindMouseWheelScroll) ~= "function" then
  error("LidCouncil missing shared editor page helpers.")
end

local ApplyFrameSkin = RequireApi("LIDCOUNCIL_ApplyFrameSkin", _G.LIDCOUNCIL_ApplyFrameSkin)
local RootDB = RequireApi("LIDCOUNCIL_RootDB", _G.LIDCOUNCIL_RootDB)
local CARD_W = RequireApi("LIDCOUNCIL_CARD_W", _G.LIDCOUNCIL_CARD_W)
local CARD_H = RequireApi("LIDCOUNCIL_CARD_H", _G.LIDCOUNCIL_CARD_H)
local GetEditorThemePalette = type(INTERNAL_API.LIDCOUNCIL_GetEditorThemePalette_Impl) == "function"
  and INTERNAL_API.LIDCOUNCIL_GetEditorThemePalette_Impl
  or nil

local CHECKLIST_DEVELOPER_SECTIONS = {
  SyncWorkflow2 = true,
  SyncWorkflow3 = true,
  SyncWorkflow4 = true,
  SyncWorkflow5 = true,
  SyncWorkflow6 = true,
  SyncWorkflow7 = true,
  SyncWorkflow8 = true,
  SyncWorkflow10 = true,
  SyncWorkflow11 = true,
  SyncWorkflow12 = true,
  SyncWorkflow13 = true,
  SyncWorkflow14 = true,
  SyncWorkflow15 = true,
  PossibleFeatures = true,
  Preflight = true,
}

local function ChecklistDeveloperPagesVisible()
  local root = RootDB()
  local ui = type(root) == "table" and root.ui or nil
  return type(ui) == "table" and ui.developerMode == true and ui.checklistDev == true
end

local function GetDeveloperAccentHex()
  local palette = GetEditorThemePalette and GetEditorThemePalette() or nil
  local color = type(palette) == "table" and type(palette.aboutTitle) == "table"
    and palette.aboutTitle
    or { 0.6588, 0.4784, 0.9098, 1.0 }
  local function ToHex(component)
    local value = tonumber(component) or 1
    value = math.max(0, math.min(255, math.floor((value * 255) + 0.5)))
    return string.format("%02x", value)
  end
  return ToHex(color[1]) .. ToHex(color[2]) .. ToHex(color[3])
end

local function ApplyDeveloperAccentMarkup(text)
  return tostring(text or ""):gsub("|cffa87ae8", "|cff" .. GetDeveloperAccentHex())
end

local function RefreshDeveloperAccentMarkup(root)
  local visited = {}

  local function ApplyToFontString(fontString)
    if not fontString or visited[fontString] then
      return
    end
    visited[fontString] = true

    local sourceText = fontString.__developerAccentSourceText
    if type(sourceText) ~= "string" then
      local currentText = fontString.GetText and fontString:GetText() or nil
      if type(currentText) == "string" and currentText:find("|cffa87ae8", 1, true) then
        sourceText = currentText
        fontString.__developerAccentSourceText = currentText
      end
    end
    if type(sourceText) == "string" then
      fontString:SetText(ApplyDeveloperAccentMarkup(sourceText))
    end
  end

  local function WalkFrame(frame)
    if not frame or visited[frame] then
      return
    end
    visited[frame] = true
    if frame.GetObjectType and frame:GetObjectType() == "FontString" then
      ApplyToFontString(frame)
      return
    end
    if type(frame.GetRegions) == "function" then
      for _, region in ipairs({ frame:GetRegions() }) do
        if region and region.GetObjectType and region:GetObjectType() == "FontString" then
          ApplyToFontString(region)
        end
      end
    end
    if type(frame.GetChildren) == "function" then
      for _, child in ipairs({ frame:GetChildren() }) do
        WalkFrame(child)
      end
    end
  end

  WalkFrame(root)
end

local function CreateTutorialSyncAuditTable(parent, rowDefs)
  local frame = CreateFrame("Frame", nil, parent)
  frame.__rowDefs = type(rowDefs) == "table" and rowDefs or {}
  frame.__rows = {}

  frame.header = CreateFrame("Frame", nil, frame, "BackdropTemplate")
  ApplyFrameSkin(frame.header)
  if frame.header.SetBackdropColor then
    frame.header:SetBackdropColor(0.07, 0.07, 0.10, 0.98)
  end
  frame.header.labels = {}
  for index, labelText in ipairs({ "Transfer", "Score", "Delivery / receipt", "Failure handling", "Assessment" }) do
    local label = frame.header:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    label:SetJustifyH(index == 2 and "CENTER" or "LEFT")
    label:SetText("|cffa87ae8" .. labelText .. "|r")
    frame.header.labels[index] = label
  end

  for index, rowDef in ipairs(frame.__rowDefs) do
    local row = CreateFrame("Frame", nil, frame, "BackdropTemplate")
    ApplyFrameSkin(row)
    if row.SetBackdropColor then
      local shade = index % 2 == 0 and 0.045 or 0.06
      row:SetBackdropColor(shade, shade, shade + 0.015, 0.98)
    end
    row.cells = {}
    for cellIndex, value in ipairs({
      rowDef.transfer,
      tostring(rowDef.score or 0) .. "%",
      rowDef.delivery,
      rowDef.failure,
      rowDef.assessment,
    }) do
      local cell = row:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
      cell:SetJustifyH(cellIndex == 2 and "CENTER" or "LEFT")
      cell:SetJustifyV("MIDDLE")
      cell:SetWordWrap(true)
      cell:SetText(tostring(value or ""))
      if cellIndex == 2 then
        local score = tonumber(rowDef.score) or 0
        if score >= 90 then
          cell:SetTextColor(0.20, 0.88, 0.32, 1)
        elseif score >= 75 then
          cell:SetTextColor(0.40, 0.72, 1.00, 1)
        elseif score >= 60 then
          cell:SetTextColor(1.00, 0.82, 0.28, 1)
        else
          cell:SetTextColor(0.95, 0.32, 0.26, 1)
        end
      end
      row.cells[cellIndex] = cell
    end
    frame.__rows[index] = row
  end

  frame.LayoutForWidth = function(self, width)
    width = math.max(620, math.floor(tonumber(width) or 620))
    self:SetWidth(width)
    local scoreW = 62
    local transferW = math.max(145, math.floor(width * 0.18))
    local deliveryW = math.max(180, math.floor(width * 0.23))
    local failureW = math.max(170, math.floor(width * 0.22))
    local assessmentW = math.max(120, width - transferW - scoreW - deliveryW - failureW)
    local widths = { transferW, scoreW, deliveryW, failureW, assessmentW }

    self.header:ClearAllPoints()
    self.header:SetPoint("TOPLEFT", 0, 0)
    self.header:SetSize(width, 28)
    local x = 0
    for index, label in ipairs(self.header.labels) do
      label:ClearAllPoints()
      label:SetPoint("LEFT", x + (index == 2 and 0 or 8), 0)
      label:SetWidth(widths[index] - (index == 2 and 0 or 12))
      x = x + widths[index]
    end

    local y = -30
    for _, row in ipairs(self.__rows) do
      row:ClearAllPoints()
      row:SetPoint("TOPLEFT", 0, y)
      row:SetSize(width, 72)
      x = 0
      for index, cell in ipairs(row.cells) do
        cell:ClearAllPoints()
        cell:SetPoint("TOPLEFT", x + (index == 2 and 2 or 8), -5)
        cell:SetSize(widths[index] - (index == 2 and 4 or 14), 62)
        x = x + widths[index]
      end
      y = y - 74
    end
    self:SetHeight(math.max(1, -y))
  end

  return frame
end

local function CreateTutorialSyncAuditSection(parent, createSection)
  local scroll, content, header, cards = createSection(parent, nil, {})
  local section = {
    scroll = scroll,
    frame = content,
    header = header,
    cards = cards,
  }

  section.intro = content:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
  section.intro:SetJustifyH("LEFT")
  section.intro:SetText("Sync Workflow 2 - Code Audit")

  section.summary = content:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  section.summary:SetJustifyH("LEFT")
  section.summary:SetWordWrap(true)
  section.summary:SetText(
    "|cff4adf6bCurrent status - 11 August 2026|r\n" ..
    "Historical code-audit baseline: 82%. Remediation checklist: 8 / 8 complete (100%). Release Preflight: 1658 / 1658. The complete standard two-client pre-raid path has now passed: first sync, unchanged rejoin, bounded checksum proof, P1/P2/P3 indicators, Test Raid Items, precise leaf preview/pull, and idle convergence. Actual raid loot, fault injection, and larger groups remain outside this result.\n\n" ..
    "|cffa87ae8Historical assessment basis - 1 August 2026|r\n" ..
    "These percentages rate the protocol design visible in code: authority checks, payload bounds, receipt or apply acknowledgement, bounded retry, convergence proof, and diagnostics. They are not live-test pass rates. " ..
    "The strongest paths are session bootstrap and critical live decisions. The principal release risk is complete P3 archive convergence for large Loot Lists and long Session History profiles."
  )

  section.findings = content:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  section.findings:SetJustifyH("LEFT")
  section.findings:SetWordWrap(true)
  section.findings:SetText(
    "|cffa87ae8Historical architecture findings that drove the completed remediation|r\n" ..
    "Critical live actions use direct receipts or authoritative mirrors. Completed sessions and existing Loot List members are represented by dynamic leaves, and precise comparison uses the union of host and member leaves, so a fresh member can discover host-only history. " ..
    "Fresh-member P3 is intentionally queued six seconds after P1/P2 and is split into Fun Awards, Loot List archive, and Session History scopes. Each snapshot can return an APPLIED ACK, but final P3 state is inferred later through member PSTATE, host MEMBER replication, and local UI derivation. " ..
    "That indirect chain is the principal weakness: there is no durable per-scope transaction proving that the exact host archive generation was applied and then acknowledged by every member. " ..
    "Both current Boss Voter write paths reject Real Simulation items in code; the reported award leak therefore needs regression verification on the current build."
  )

  section.matrix = CreateTutorialSyncAuditTable(content, {
    {
      transfer = "Session invite and join",
      score = 93,
      delivery = "Host -> member START; member IACK receipt; JOIN/JACK confirms admission.",
      failure = "Invite timeout, one resend, rejection reason, stale tracking cleanup.",
      assessment = "Correct design. Explicit receipt separates delivery from acceptance.",
    },
    {
      transfer = "Session begin and stop",
      score = 86,
      delivery = "Host-authoritative BEGIN/STOP broadcast updates every joined client.",
      failure = "Validation and state refresh exist; no all-member apply receipt for the transition.",
      assessment = "Appropriate for small control packets, but host cannot prove every client applied it.",
    },
    {
      transfer = "Joined-member roster",
      score = 82,
      delivery = "Host broadcasts MEMBER snapshots; clients accept host state.",
      failure = "Dedupe, coalescing, payload fallback; no per-client apply ACK.",
      assessment = "Good for small state, but refresh and receipt timing can temporarily differ by client.",
    },
    {
      transfer = "P1 roll-ready bootstrap",
      score = 90,
      delivery = "Chunked PSYNC snapshot with QUEUED and APPLIED acknowledgements.",
      failure = "Chunk bounds, identity checks, 60s ACK timeout, revalidation and repair.",
      assessment = "Strong and appropriately prioritized. Matches successful live tests.",
    },
    {
      transfer = "P2 profile/settings delta",
      score = 91,
      delivery = "Host/member PDELTA with PDELTA_ACK; PDELTA_NACK requests recovery.",
      failure = "Sequence/checksum validation, pending ACK tracking, bounded fallback repair.",
      assessment = "Strong. Appropriate for rights, rolls, points, and profile edits.",
    },
    {
      transfer = "Loot List live edits",
      score = 87,
      delivery = "Member request -> host authority -> delta or LL_* broadcast.",
      failure = "Validation, combat staging, host fallback; legacy broadcast has no all-client ACK.",
      assessment = "Reliable for low-volume edits, with eventual snapshot repair as safety net.",
    },
    {
      transfer = "Loot item discovery",
      score = 89,
      delivery = "Member ITEM_REPORT -> host; authoritative ITEM_OPEN acts as receipt.",
      failure = "Four delayed retries, safe-field fallback, later board reconciliation.",
      assessment = "Strong. A missed report is visible and recoverable.",
    },
    {
      transfer = "Player roll choice",
      score = 94,
      delivery = "CHOICE -> host; exact RCHOICE confirms the chooser, while council recipients return RCHOICE_ACK.",
      failure = "Immutable sequence validation, direct-only replay receipts, and bounded retries for missing council mirrors.",
      assessment = "Best critical path. Suitable for simultaneous raid decisions.",
    },
    {
      transfer = "Council vote",
      score = 91,
      delivery = "VOTE -> host; direct canonical VOTE mirror confirms the voter.",
      failure = "Sequence validation, pending receipt retries, board recovery fallback.",
      assessment = "Protocol is strong. Simulation votes must remain excluded from awards.",
    },
    {
      transfer = "Award and trade state",
      score = 88,
      delivery = "Host broadcasts; recipient returns identical AWARD/TRADE receipt.",
      failure = "Recipient retry tracking and board-sync reconciliation if unconfirmed.",
      assessment = "Good authority model; recipient proof is stronger than peer-wide proof.",
    },
    {
      transfer = "Live Board recovery",
      score = 78,
      delivery = "BSYNC start/items/selections/votes/end snapshot, host -> one member.",
      failure = "Field truncation and drop counts; no receiver completion ACK or selective resend.",
      assessment = "Usable recovery tool, but not a fully proven delivery transaction.",
    },
    {
      transfer = "Precise compare and repair",
      score = 76,
      delivery = "PSUB request/state and patch-manifest request/state use multipart safe payloads.",
      failure = "Union comparison finds host-only leaves; request/response retry and final convergence remain indirect.",
      assessment = "Good diagnostic foundation. It needs a simpler transaction outcome for automatic recovery.",
    },
    {
      transfer = "P3 Loot List / item archive",
      score = 62,
      delivery = "Scoped chunked snapshots; four archive checksums report convergence.",
      failure = "Queued repair and ACK exist, but scope scheduling/coalescing can obscure completion.",
      assessment = "Critical gap. Large historical copies are not yet reliably converging in tests.",
    },
    {
      transfer = "P3 Session History / awards",
      score = 54,
      delivery = "Deferred session/stat subdomain snapshots after P1/P2 traffic.",
      failure = "Chunk ACK exists, but authoritative overwrite and per-scope completion are indirect.",
      assessment = "Highest risk. Long histories reproduce the current incomplete-copy failure.",
    },
    {
      transfer = "P1/P2/P3 status display",
      score = 64,
      delivery = "Member PSTATE -> host; host MEMBER snapshot -> every client.",
      failure = "Checksums and fallbacks exist; clients can still derive transient local states.",
      assessment = "Needs one host-owned status matrix so all clients display the same receipts.",
    },
    {
      transfer = "Combat-deferred apply",
      score = 87,
      delivery = "Data is received in combat; risky mutation/UI work is staged.",
      failure = "QUEUED ACK then APPLIED ACK after combat; merged refresh on regen.",
      assessment = "Sound design. Continue auditing every new opcode for staging policy.",
    },
  })

  section.actions = content:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  section.actions:SetJustifyH("LEFT")
  section.actions:SetWordWrap(true)
  section.actions:SetText(
    "|cffa87ae8Prioritized implementation actions - 8 / 8 complete (100%)|r\n" ..
    "|cff4adf6b1. COMPLETE|r - Each P3 scope has persistent generation, manifest, applied ACK, checksum receipt, bounded retry, and terminal state.\n" ..
    "|cff4adf6b2. COMPLETE|r - Host push semantics replace only the selected authoritative replica scope and preserve unrelated/local data.\n" ..
    "|cff4adf6b3. COMPLETE|r - Loot List, Item History, Session History, and Fun Awards schedule independently.\n" ..
    "|cff4adf6b4. COMPLETE|r - The host publishes one authoritative per-member status matrix.\n" ..
    "|cff4adf6b5. COMPLETE|r - Large control/state responses use lossless bounded multipart transport.\n" ..
    "|cff4adf6b6. COMPLETE|r - BSYNC has completion receipts and selective missing-row recovery.\n" ..
    "|cff4adf6b7. COMPLETE|r - Simulation votes remain excluded from The Boss Voter.\n" ..
    "|cff4adf6b8. COMPLETE|r - Deterministic capacity, boundary, ACK-loss, exhaustion, and archive convergence fixtures are in Preflight."
  )

  section.exitCriteria = content:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  section.exitCriteria:SetJustifyH("LEFT")
  section.exitCriteria:SetWordWrap(true)
  section.exitCriteria:SetText(
    "|cffa87ae8Archive completion rule|r\n" ..
    "P3 may become complete only after the receiver applied the exact host generation and returned matching Loot List, Item History, Session History, and Fun Awards checksums. " ..
    "A queued message, a successful SendAddonMessage call, or an aggregate revision match is not sufficient proof."
  )

  section.Layout = function(self)
    local width = math.max(620, (self.frame:GetWidth() or 0) - 36)
    local x = 18
    local y = -18
    for _, fontString in ipairs({ self.intro, self.summary, self.findings }) do
      fontString:ClearAllPoints()
      fontString:SetPoint("TOPLEFT", x, y)
      fontString:SetWidth(width)
      y = y - ((fontString.GetStringHeight and fontString:GetStringHeight()) or 0) - 14
    end
    self.matrix:ClearAllPoints()
    self.matrix:SetPoint("TOPLEFT", x, y)
    self.matrix:LayoutForWidth(width)
    y = y - self.matrix:GetHeight() - 18
    for _, fontString in ipairs({ self.actions, self.exitCriteria }) do
      fontString:ClearAllPoints()
      fontString:SetPoint("TOPLEFT", x, y)
      fontString:SetWidth(width)
      y = y - ((fontString.GetStringHeight and fontString:GetStringHeight()) or 0) - 18
    end
    local contentHeight = math.max(400, -y + 90)
    self.frame:SetHeight(contentHeight)
    if self.scroll and self.scroll.UpdateScrollChildRect then
      self.scroll:UpdateScrollChildRect()
    end
    return contentHeight
  end

  scroll:Hide()
  return section
end

local function CreateTutorialSyncProgressSection(parent, createSection)
  local scroll, content, header, cards = createSection(parent, nil, {})
  local section = {
    scroll = scroll,
    frame = content,
    header = header,
    cards = cards,
  }

  section.intro = content:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
  section.intro:SetJustifyH("LEFT")
  section.intro:SetText("Sync Workflow 3 - Implementation Progress")

  section.summary = content:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  section.summary:SetJustifyH("LEFT")
  section.summary:SetWordWrap(true)
  section.summary:SetText(
    "|cff4adf6bCurrent status - 11 August 2026|r\n" ..
    "Implementation: 12 / 12 complete (100%). Release Preflight: 1658 / 1658. The standard two-client pre-raid workflow passed on matching builds, including fresh sync, unchanged rejoin, archive convergence, Test Raid Items, and precise member-leaf pull. Real-raid and multi-member scale evidence remain separate gates.\n\n" ..
    "|cffa87ae8Living implementation record - started 1 August 2026|r\n" ..
    "This page preserves the synchronization hardening checklist and records continuous code progress. " ..
    "The Workflow 2 audit remains the immutable 82% baseline. After all eight Workflow 3 tasks and all four post-Preflight live repairs, the new code-only audit rates overall theoretical sync readiness at 96%. This is an architecture assessment, not an in-game pass rate."
  )

  section.current = content:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  section.current:SetJustifyH("LEFT")
  section.current:SetWordWrap(true)
  section.current:SetText(
    "|cffa87ae8Current implementation focus|r\n" ..
    "|cff4adf6bPOST-PREFLIGHT LIVE REPAIR 4 / 4 COMPLETE - 100% - precise comparison lifecycle; two-client pre-raid gate passed.|r\n" ..
    "Every explicit precise comparison now records its token, requested domains, received domains, deadline, completion, and failure reason. The Leaf Scope Picker distinguishes idle, waiting, failed, actionable, and completed-with-no-differences states. " ..
    "Waiting or failed comparisons cannot dispatch a manual repair, stale rows from an older comparison stay hidden, and a conclusive empty result disables silent broad-domain fallback for the active session. Explicit forced refreshes bypass stale loaded-state shortcuts, while profile revision changes invalidate the proof immediately. These lifecycle checks are included in the current 1658 / 1658 Release Preflight pass."
  )

  section.auditSummary = content:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  section.auditSummary:SetJustifyH("LEFT")
  section.auditSummary:SetWordWrap(true)
  section.auditSummary:SetText(
    "|cffa87ae8Post-implementation code audit - 1 August 2026|r\n" ..
    "Scores use the same criteria as Workflow 2: authority, payload safety, receipt or apply acknowledgement, bounded retry, convergence proof, combat handling, and diagnostics. " ..
    "Overall theoretical readiness: |cff4adf6b96%|r. Critical roll and council paths are 96-97%; P3 archives cover authoritative records and visible derived metadata; precise comparison now has explicit terminal proof. Remaining deductions are mainly broadcast lifecycle proof, mixed-version behavior, reload recovery, and large-file maintainability."
  )

  section.matrix = CreateTutorialSyncAuditTable(content, {
    {
      transfer = "Session invite and join",
      score = 94,
      delivery = "Host START; member IACK receipt; JOIN/JACK confirms accepted admission.",
      failure = "Timeout, bounded resend, rejection reason, stale-tracker cleanup and diagnostics.",
      assessment = "Release-grade control flow. Delivery and user acceptance remain correctly separated.",
    },
    {
      transfer = "Session begin and stop",
      score = 88,
      delivery = "Host-authoritative BEGIN/STOP updates joined clients and session snapshots repair state.",
      failure = "Validation and refresh exist; no all-member apply receipt for every lifecycle broadcast.",
      assessment = "Correct for small control packets. An epoch reconciliation proof would close the remaining gap.",
    },
    {
      transfer = "Joined-member roster",
      score = 94,
      delivery = "Host MEMBER snapshots include authoritative P1, P2 and four P3-family verdicts.",
      failure = "Opcode-specific 45 encoded-field ceiling, byte fallback, host-only acceptance and refresh signatures.",
      assessment = "The live-test field-count rejection is fixed in contract v5; multi-client verification is pending.",
    },
    {
      transfer = "P1 roll-ready bootstrap",
      score = 95,
      delivery = "Prioritized chunked PSYNC snapshot with QUEUED and APPLIED acknowledgements.",
      failure = "Safe chunks, identity checks, ACK timeout, checksum validation and bounded repair.",
      assessment = "Strong raid-critical path with deterministic 20-member fanout coverage.",
    },
    {
      transfer = "P2 profile/settings delta",
      score = 95,
      delivery = "Host/member PDELTA with PDELTA_ACK; PDELTA_NACK requests authoritative recovery.",
      failure = "Sequence and checksum checks, ACK tracking, retry budget and snapshot fallback.",
      assessment = "Strong two-way synchronization for rights, rolls, points and profile edits.",
    },
    {
      transfer = "Loot List live edits",
      score = 93,
      delivery = "Member request -> host authority -> delta/broadcast, followed by archive checksum proof.",
      failure = "Input validation, combat staging, host fallback and exact-scope repair.",
      assessment = "Reliable for live edits; full replica replacement now repairs missing historical rows.",
    },
    {
      transfer = "Loot item discovery",
      score = 91,
      delivery = "Member ITEM_REPORT -> host; authoritative ITEM_OPEN acts as the decision receipt.",
      failure = "Delayed retries, payload-safe fallback and later Live Board reconciliation.",
      assessment = "Sound low-volume path. A missed report remains visible and recoverable.",
    },
    {
      transfer = "Player roll choice",
      score = 97,
      delivery = "CHOICE -> host; exact RCHOICE confirms the chooser, while council recipients return RCHOICE_ACK.",
      failure = "Immutable sequence rejection, direct-only replay receipts, and bounded council-mirror repair.",
      assessment = "Best critical path. Production rules pass deterministic 100-choice burst coverage.",
    },
    {
      transfer = "Council vote",
      score = 96,
      delivery = "VOTE -> host; canonical VOTE mirror confirms the voter and board state.",
      failure = "Sequence validation, receipt retries, board recovery and simulation-vote exclusion.",
      assessment = "Strong decision path. Test Simulation no longer contributes to Boss Voter awards.",
    },
    {
      transfer = "Award and trade state",
      score = 91,
      delivery = "Host broadcasts award/trade; recipient returns matching authoritative receipt.",
      failure = "Recipient retry tracking and Live Board reconciliation when confirmation is missing.",
      assessment = "Good authority model. Recipient proof is intentionally stronger than peer-wide proof.",
    },
    {
      transfer = "Live Board recovery",
      score = 96,
      delivery = "BSYNC transaction carries token, board ID, indexed rows, row counts and checksum receipt.",
      failure = "Selective missing-row requests, three repair rounds, END retry and bounded full rebuild.",
      assessment = "Now a proven recovery transaction rather than a best-effort snapshot.",
    },
    {
      transfer = "Precise compare and repair",
      score = 97,
      delivery = "Token-correlated multipart PSUB replies track every requested and received domain.",
      failure = "Explicit timeout/send failure, retry state, exact manifest validation and fail-closed repair gating.",
      assessment = "Empty, waiting, failed and actionable results are distinct; zero-difference proof cannot trigger broad fallback.",
    },
    {
      transfer = "P3 Loot List / item archive",
      score = 97,
      delivery = "Independent generations and exact manifests for Loot List and Item History families, including class/spec identity.",
      failure = "Applied ACK, checksum receipt, bounded retry, terminal state and authoritative replacement.",
      assessment = "Rows, linked metadata, notes, points, class, spec and icons now share one verifiable family result.",
    },
    {
      transfer = "P3 Session History / awards",
      score = 96,
      delivery = "Independent Session History and Fun Awards scheduling after critical traffic.",
      failure = "Generation proof, exact checksum, bounded repair, 70-session replacement and compact-event fixtures.",
      assessment = "Large history and derived award inputs now converge under canonical and compact event schemas.",
    },
    {
      transfer = "P1/P2/P3 status display",
      score = 95,
      delivery = "Host publishes one seven-verdict MEMBER matrix to every joined client.",
      failure = "Only verified host packets update it; contract v5 admits the complete matrix packet.",
      assessment = "Authority is consistent in code. The next client test must confirm the matrix now arrives.",
    },
    {
      transfer = "Combat-deferred apply",
      score = 94,
      delivery = "Data can arrive in combat while protected mutation and UI refresh are staged.",
      failure = "QUEUED then APPLIED acknowledgement, explicit receipt ownership, generation replay and three bounded post-combat retries.",
      assessment = "Strong deterministic recovery. Reload-during-combat and every newly added opcode still require policy discipline.",
    },
  })

  section.nextActions = content:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  section.nextActions:SetJustifyH("LEFT")
  section.nextActions:SetWordWrap(true)
  section.nextActions:SetText(
    "|cffa87ae8Post-Preflight live repair checklist|r\n" ..
    "|cff4adf6b1. COMPLETE|r - Align the expanded MEMBER sender and receiver contract without raising limits for unrelated opcodes. Exact 44-value MEMBER payloads are accepted as 45 encoded fields including the opcode; a 45th payload value and generic overflow are rejected.\n" ..
    "|cff4adf6b2. COMPLETE|r - Separate receipt from applied state across combat. A staged transfer reports QUEUED, imports once after combat, commits its generation, and then queues APPLIED receipts; bounded retries prevent one failed regen flush from leaving 100% awaiting receipt indefinitely.\n" ..
    "|cff4adf6b3. COMPLETE|r - Verify all four P3 family checksums and derived metadata after apply. Loot List class/spec identity and compact Everything Is BiS event markers are now checksum-bearing leaves with targeted convergence validation.\n" ..
    "|cff4adf6b4. COMPLETE|r - Precise requests now retain token-correlated requested/received domain evidence, explicit completion and timeout states, safe retry, and fail-closed zero-difference repair gating.\n" ..
    "All four code repairs are complete. Release Preflight passes 1658 / 1658 and the same-build two-client fresh-join/rejoin path is green. The next evidence step is a genuine raid event and then a larger group."
  )

  section.checklist = content:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  section.checklist:SetJustifyH("LEFT")
  section.checklist:SetWordWrap(true)
  section.checklist:SetText(
    "|cffa87ae8Permanent implementation checklist|r\n" ..
    "|cff4adf6b1. COMPLETE|r - Give each P3 scope a persistent generation, manifest, applied ACK, checksum receipt, bounded retry, and terminal failed state. All four archive families now participate; the existing precise union continues to include host-only and member-only leaves.\n" ..
    "|cff4adf6b2. COMPLETE|r - Explicit host repair for one selected Loot List or Session History family sends a checksummed complete manifest and replaces only that member replica scope. Invalid authority or payload evidence fails closed; unrelated and local-only data remains untouched.\n" ..
    "|cff4adf6b3. COMPLETE|r - Schedule Loot List, Item History, Session History, and Fun Awards independently from their four checksums so one queued scope never suppresses another.\n" ..
    "|cff4adf6b4. COMPLETE|r - Publish one host-authoritative per-member status matrix. The seven-byte MEMBER field carries P1, P2, and four archive-family verdicts; only verified host packets can update it, and older clients retain checksum fallback behavior.\n" ..
    "|cff4adf6b5. COMPLETE|r - Variable-size subchecksum and patch-manifest responses use multipart transport; snapshots are chunked; oversized deltas route to chunked repair; oversized PSTATE now uses bounded lossless PSTATE_PART reassembly. No reduced state is accepted as archive convergence proof.\n" ..
    "|cff4adf6b6. COMPLETE|r - Live Board snapshots use a transaction token, board ID, indexed item/selection/vote rows, row counts, and checksum receipt. Missing rows are requested individually and resent selectively before a bounded full rebuild.\n" ..
    "|cff4adf6b7. COMPLETE|r - Host-local, received, and legacy-history Real Simulation votes are excluded from The Boss Voter, with deterministic validation preserving real-vote counting.\n" ..
    "|cff4adf6b8. COMPLETE|r - Validation suite v90 covers deterministic 20-member joins and fanout, 100 simultaneous choices, payload boundaries, ACK loss and late recovery, retry exhaustion, real 70-session authoritative archive convergence, derived P3 metadata, and precise request lifecycle terminals."
  )

  section.log = content:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  section.log:SetJustifyH("LEFT")
  section.log:SetWordWrap(true)
  section.log:SetText(
    "|cffa87ae8Progress log|r\n" ..
    "1 August 2026 - Completed the full code audit and recorded the baseline in Sync Workflow 2.\n" ..
    "1 August 2026 - Verified that snapshot transport already has chunk bounds, QUEUED/APPLIED ACKs, 60-second timeout detection, checksum revalidation, and bounded repair dispatch. Identified the missing layer as persisted scope-level transaction state.\n" ..
    "1 August 2026 - Completed Task 1 for Loot List and Session History. Added persisted scope transactions, compact manifest proof, exact checksum confirmation through PSTATE, three-attempt retry exhaustion, send-failure state, and integration with the existing timeout recovery path.\n" ..
    "1 August 2026 - Completed Task 2. Explicit host archive pushes now use a versioned complete-manifest replacement contract for Loot List or completed Session History. Receiver-only rows in that selected family are removed only after authority, lineage, checksum, and payload validation; bounded ACK retries retain the same contract.\n" ..
    "1 August 2026 - Completed Task 3. Split deferred P3 scheduling into four checksum-gated families with independent timer keys, exact leaf scopes, transfer reasons, persisted transactions, checksum confirmation, and bounded retry state.\n" ..
    "1 August 2026 - Completed Task 4. Added a versioned seven-byte host status matrix to MEMBER transport, host-only derivation, checksum-excluded runtime retention, refresh signatures, and authoritative Joined Members rendering for P1, P2, Loot List, Item History, Session History, Fun Awards, and aggregate P3.\n" ..
    "1 August 2026 - Completed Task 5. Removed lossy PSTATE fallback, added bounded PSTATE_PART transport and identity-checked reassembly, registered combat/session/queue handling, and gated archive reconciliation on complete four-family checksum proof. Existing variable responses remain multipart, while oversized deltas continue through chunked snapshot repair.\n" ..
    "1 August 2026 - Completed Task 6. Added BSYNC transaction manifests and completion receipts, deterministic indexed row proof, bounded per-row missing requests, three selective repair rounds, END receipt retry, duplicate-END receipt replay, and one terminal full-rebuild fallback.\n" ..
    "1 August 2026 - Completed Task 7. Hardened Boss Voter reconstruction against simulation boards, row markers, and TESTSIM keys, then added automated host-local, received, legacy-history, and real-vote control checks.\n" ..
    "1 August 2026 - Completed Task 8. Added production-rule choice sequencing stress for 20 members and five items, idempotent lost-receipt replay, stale update rejection, and an end-to-end 70-session authoritative export/import convergence fixture. Existing protocol and transport suites provide exact payload-boundary, ACK timeout, late-ACK, and retry-exhaustion proof.\n" ..
    "1 August 2026 - Fixed the Task 8 harness loader boundary by explicitly terminating consecutive validation calls, removing ambiguous function-call syntax in the WoW Lua parser.\n" ..
    "1 August 2026 - Added the post-implementation Workflow 3 code audit: 16 directly comparable transfer scores, 94% overall theoretical readiness, updated assessments, and eight prioritized follow-up improvements.\n" ..
    "2 August 2026 - Wired the deterministic 20-member choice-burst check into Release Preflight without duplicating protocol/transport suites, and added a dedicated Preflight tutorial page with safety gates, procedure, coverage, result meanings, and limitations.\n" ..
    "2 August 2026 - Split Developer Mode validation into two explicit tools: Release Preflight with detailed failed assertions, and Synthetic Sync Rehearsal with separate source/scenario/raid-size controls, output, and cleanup.\n" ..
    "2 August 2026 - Added a copyable Release Preflight report window so complete validation results can be selected and copied without transcribing the Developer Mode output.\n" ..
    "2 August 2026 - Recorded baseline Preflight result 1167/1178: Communication protocol 2 failures, Communication transport 3, Profile synchronization 2, and Release preflight aggregator 4.\n" ..
    "2 August 2026 - Corrected the full baseline set: explicit immediate roster defer state, ACK recovery false-result validation, timeout tests derived from the production deadline, P3 session-history leaf-manifest convergence, and shared preflight sanitizer scope. The aggregator now reaches all 44 planned checks, so the next total is expected to increase by four.\n" ..
    "2 August 2026 - Resolved the remaining 1181/1182 schema-contract failure by accepting every bounded numeric report version in structural shape validation, including the sanitizer's intentional malformed-version clamp fixture.\n" ..
    "2 August 2026 - Release Preflight confirmed PASS: 1182/1182 checks across five suites in 0.13 seconds. Deterministic protocol, transport, profile convergence, startup health, and preflight aggregation gates are green.\n" ..
    "2 August 2026 - Added a copyable Synthetic Sync Rehearsal result window and separated result actions from source, scenario, and raid-size execution controls.\n" ..
    "2 August 2026 - Began Developer Mode dashboard cleanup with reusable independent collapsible panels for Header Monitoring Preview and Profile Sync Tuning, compact header statuses, and a fixed two-column tuning form.\n" ..
    "2 August 2026 - Migrated Release Preflight into the dashboard panel system with a compact authoritative result card, persistent failure details, one primary run action, and quieter copy and clear controls.\n" ..
    "2 August 2026 - Completed the Developer Mode dashboard migration by consolidating Synthetic Sync Rehearsal into an independent collapsible panel with source, scenario, and raid-size selectors, one primary run action, compact state, copyable results, and bounded cleanup.\n" ..
    "2 August 2026 - Live two-client testing exposed a structural MEMBER rejection: the expanded host roster packet contains 44 payload values plus the opcode. Added protocol contract v5 with a MEMBER-only 45 encoded-field maximum, preserved the generic 40-field and 240-byte guards, and extended the existing payload-boundary invariant to accept 44 and reject 45 payload values.\n" ..
    "2 August 2026 - Completed post-Preflight repair 2. Combat-staged snapshots now defer APPLIED receipt ownership until import and generation commit, replay receipts idempotently for already-applied generations, and retry a failed post-combat flush three times with coalescing and stale-timer invalidation. Validation suite v88 adds eight profile-sync assertions.\n" ..
    "2 August 2026 - Completed post-Preflight repair 3. Class/spec-only Loot List metadata is now included in P3 payloads and checksums; compact roll and vote event aliases use the same eligibility rules as award reconstruction. Loot List roster discovery normalizes localized clients through the shared English class/spec resolver before touching checksum-bearing metadata. Validation suite v89 adds nine targeted archive assertions, including the real Everything Is BiS ranking reader.\n" ..
    "2 August 2026 - Completed post-Preflight repair 4. Precise requests now retain token, scope, receipt count, deadline and terminal state; forced reloads bypass stale loaded-state skips; stale rows stay hidden during newer requests; unchanged replies still complete the request; timeout and send failure remain retryable; and completed zero-difference results cannot silently dispatch broad repair. Validation suite v90 adds seven lifecycle assertions; the expected green total is 1206/1206."
  )

  section.exitCriteria = content:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  section.exitCriteria:SetJustifyH("LEFT")
  section.exitCriteria:SetWordWrap(true)
  section.exitCriteria:SetText(
    "|cffa87ae8Current exit status|r\n" ..
    "Code repair, 1658 / 1658 Preflight, and the two-client pre-raid proof/repair path are complete. Remaining release evidence: one genuine raid-loot event, full Profile Transfer export/import, and larger-group capacity testing."
  )

  section.Layout = function(self)
    local width = math.max(620, (self.frame:GetWidth() or 0) - 36)
    local y = -18
    for _, fontString in ipairs({ self.intro, self.summary, self.current, self.auditSummary }) do
      fontString:ClearAllPoints()
      fontString:SetPoint("TOPLEFT", 18, y)
      fontString:SetWidth(width)
      y = y - ((fontString.GetStringHeight and fontString:GetStringHeight()) or 0) - 18
    end
    self.matrix:ClearAllPoints()
    self.matrix:SetPoint("TOPLEFT", 18, y)
    self.matrix:LayoutForWidth(width)
    y = y - self.matrix:GetHeight() - 18
    for _, fontString in ipairs({ self.nextActions, self.checklist, self.log, self.exitCriteria }) do
      fontString:ClearAllPoints()
      fontString:SetPoint("TOPLEFT", 18, y)
      fontString:SetWidth(width)
      y = y - ((fontString.GetStringHeight and fontString:GetStringHeight()) or 0) - 18
    end
    local contentHeight = math.max(400, -y + 90)
    self.frame:SetHeight(contentHeight)
    if self.scroll and self.scroll.UpdateScrollChildRect then
      self.scroll:UpdateScrollChildRect()
    end
    return contentHeight
  end

  scroll:Hide()
  return section
end

local function CreateTutorialSyncWorkflow4Section(parent, createSection)
  local scroll, content, header, cards = createSection(parent, nil, {})
  local section = {
    scroll = scroll,
    frame = content,
    header = header,
    cards = cards,
  }

  section.intro = content:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
  section.intro:SetJustifyH("LEFT")
  section.intro:SetText("Sync Workflow 4 - Live Test Findings")

  section.summary = content:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  section.summary:SetJustifyH("LEFT")
  section.summary:SetWordWrap(true)
  section.summary:SetText(
    "|cff4adf6bCurrent status - 11 August 2026|r\n" ..
    "Implementation: 11 / 11 complete (100%). Release Preflight: 1658 / 1658. Fresh sync, account-wide rejoin, exact Fun Awards/archive proof, Test Raid Items, and close/reopen all passed in the latest two-client pre-raid test. Combat-only presentation and a genuine raid drop remain live-raid checks.\n\n" ..
    "|cffa87ae8Historical live evidence baseline - 2 August 2026|r\n" ..
    "A fresh character completed synchronization in the preparation lobby. Profile configuration, roles and rights, Current Session, Session History, Auto-Pass, the complete Loot List, character/class identity, and Item History matched the host. " ..
    "This proves that the fresh-profile transport and the large archive path can converge in a real two-client session. A later live retest also proved that an account-wide replica can rejoin on another character in about 30 seconds instead of downloading the complete archive again. The remaining live defects were isolated to Fun Awards archive acknowledgement/recovery and Test Raid Items during the session-start transition. " ..
    "Workflow 4 Task 1 now makes Fun Awards an authoritative event archive: the host sends the complete canonical roll/vote event manifest, member-only events are removed, and the family checksum must be re-advertised before completion. " ..
    "Task 2 adds deterministic convergence coverage for complete event replacement and every affected derived award. " ..
    "Task 3 gives Test Raid Items explicit refusal codes, per-target dispatch tokens, receipt tracking, timeout failure state, and visible editor feedback. " ..
    "Task 4 reuses a proven account-wide replica: JOIN records that the profile already existed, complete PSTATE family checksums gate archive work, and mismatches require a precise manifest before any repair can be queued. " ..
    "Task 5 adds deterministic routing coverage for aligned replicas, precise new-character leaves, and fail-closed missing-proof behavior. " ..
    "Task 6 separates current prefix health from historical registration counters and from invite receipt delivery. Registry proof, successful registration returns, safe side-effect returns, and actual successful sends now recover the active state. " ..
    "Task 7 separates malformed packets, admission drops, rate limits, stale traffic, handler failures, recovered fallbacks, and unresolved outbound failures. Historical recovered events remain visible without lowering current health. " ..
    "Task 8's code audit confirms that the live verification paths use the production implementations covered by Preflight: simulation votes are excluded at record and rebuild time, authoritative Fun Awards roll/vote leaves update only their own identity instead of erasing previously imported leaves, and Test Raid Items uses per-target board receipts. The latest test found three additional integration defects now fixed in code: a forward-reference nil call in join-manifest proof, Fun Awards leaves being presented as Pull Member despite host authority, and board snapshots being rejected or held while the member crossed PREPARING to ACTIVE or remained in combat. Archive retry attempts are now isolated by session and explicit host repair receives a fresh bounded budget. The subsequent 1271 / 1273 Preflight isolated one final checksum mutation: opening Fun Awards downgraded a newer loot-roll schema and changed every per-event P3 checksum. Event leaves no longer include the profile-wide schema version, readers/importers preserve newer schemas, and the current 1658 / 1658 Preflight plus two-client test supersede that pending gate. " ..
    "The historical weighted code-readiness estimate was |cff4adf6b96%|r overall and |cff4adf6b96%|r for the raid-critical P1/P2 path; today's implementation checklist is complete and the executed two-client pre-raid scenario passed."
  )

  section.findings = content:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  section.findings:SetJustifyH("LEFT")
  section.findings:SetWordWrap(true)
  section.findings:SetText(
    "|cffa87ae8Confirmed findings|r\n" ..
    "1. Fun Awards are rebuilt from canonical lootRollStats.events and voteEvents. The different Luckiest, Worst, Everything is BiS, and Forgotten values therefore prove that some roll-event leaves are absent on the member; this is not a rounding or UI-cache issue.\n" ..
    "2. Test Raid Items previously returned nil for every runtime refusal and ignored the Live Board dispatch result. Task 3 now returns a structured code and message, records attempted/sent/failed targets and transaction tokens, and updates the editor when BSYNC receipts are confirmed or exhaust recovery.\n" ..
    "3. LidCouncilDB and LidLootDB are account-wide SavedVariables. Task 4 records that an incoming character already owns the shared profile, verifies matching lineage plus all four archive-family checksums, and requests precise subchecksums for genuine differences. A missing or delayed manifest now remains pending and never broadens into a full archive snapshot.\n" ..
    "4. Task 6 corrects the prefix telemetry defect. A successful registry query, registration return, compatible nil side-effect return, or real outbound send confirms the current prefix state and clears an active alarm. Historical attempts remain diagnostic counters, while missing invite receipts are reported only by the independent invite-delivery tracker.\n" ..
    "5. The member's 12 rejected packets were followed by recovered payload fallbacks and a healthy terminal state. Task 7 now classifies rate limits separately at the recording source, exposes malformed/admission/stale/handler categories, and requires exact opcode, target, and timestamp proof before an outbound failure is considered recovered.\n" ..
    "6. The failed Fun Awards transfer sent every chunk but exhausted its APPLIED acknowledgement. Its exhausted attempt counter was retained on the member record without a session identity, so a new lobby could remain Recovery deferred. Transactions now reset across session IDs, while explicit host repair resets only the selected archive scope.\n" ..
    "7. The Leaf Scope Picker used generic revision direction for canonical Fun Awards events. On the host, lrsr/lrsv/lre/lve leaves are now always Push Host; selecting them enables an authoritative background replacement instead of pulling an empty or divergent member archive.\n" ..
    "8. Test Raid Items could arrive while the member still considered the session PREPARING and was rejected before its handler. Board snapshots are now admitted through that transition. They commit and acknowledge during combat while the existing UI refresh layer waits until combat ends.\n" ..
    "9. The reprepare crash at LootSessionComm_SessionFlow.lua:4234 was a Lua lexical forward-reference: the delayed closure called a later local helper as a nil global. The proof-domain check is now self-contained at the call site.\n" ..
    "10. The 1271 / 1273 Preflight failure proved that a dynamic Fun Awards event checksum included the profile-wide loot-roll schema version. The awards reader forced that version back to 3, so merely rebuilding awards changed the member checksum after a correct import. Event leaves now hash only their owned event, and all schema consumers preserve a newer compatible version instead of downgrading it."
  )

  section.matrix = CreateTutorialSyncAuditTable(content, {
    {
      transfer = "Invite and lobby join",
      score = 96,
      delivery = "Host START; member receipt and popup; JOIN/JACK confirms admission.",
      failure = "Bounded retry, rejection reason and stale invite cleanup.",
      assessment = "Live pass. Fresh member joined and the host roster refreshed correctly.",
    },
    {
      transfer = "P1 roll-ready bootstrap",
      score = 97,
      delivery = "Prioritized chunked snapshot with QUEUED and APPLIED acknowledgement.",
      failure = "Safe chunks, identity checks, ACK timeout and authoritative repair.",
      assessment = "Live pass. Required profile and roll-ready state converged first.",
    },
    {
      transfer = "P2 session continuity",
      score = 96,
      delivery = "Host/member deltas use ACK/NACK and authoritative host recovery.",
      failure = "Sequence, checksum, retry budget and snapshot fallback.",
      assessment = "Live pass. Current Session and Auto-Pass matched both clients.",
    },
    {
      transfer = "Profile, rights and rules",
      score = 97,
      delivery = "Checksummed profile leaves and acknowledged deltas.",
      failure = "Authority validation, combat staging and targeted repair.",
      assessment = "Live pass. Every Profile subsection was identical.",
    },
    {
      transfer = "Loot List replica",
      score = 96,
      delivery = "Independent P3 manifest, chunk apply ACK and checksum receipt.",
      failure = "Authoritative replacement, retry and terminal scope state.",
      assessment = "Live pass. All characters and class/spec identity arrived.",
    },
    {
      transfer = "Item History",
      score = 96,
      delivery = "Independent archive-family manifest and checksum proof.",
      failure = "Bounded scope retry and host replacement repair.",
      assessment = "Live pass. Item assignment history matched completely.",
    },
    {
      transfer = "Session History",
      score = 96,
      delivery = "Deferred P3 session records with chunk ACK and family checksum.",
      failure = "70-session replacement path, retries and terminal state.",
      assessment = "Live pass. The complete long history reached the fresh client.",
    },
    {
      transfer = "Fun Awards inputs",
      score = 91,
      delivery = "Authoritative P3 snapshot carries the complete canonical roll/vote event manifest.",
      failure = "Chunk/apply ACK plus exact family checksum; receiver-only events are removed.",
      assessment = "Live defect isolated to apply receipt/retry and wrong repair direction. Both paths are fixed; exact live replacement must be retested.",
    },
    {
      transfer = "Live Board and rolls",
      score = 95,
      delivery = "Tokened board sync, row receipts, selective resend and rebuild.",
      failure = "Critical choice/vote receipts and bounded board recovery.",
      assessment = "Previously live-proven. Normal raid workflow remains strong.",
    },
    {
      transfer = "Test Raid Items",
      score = 91,
      delivery = "Host builds TESTSIM items and dispatches the normal board sync.",
      failure = "Structured refusal/partial result plus per-target BSYNC receipt and timeout state.",
      assessment = "Start-transition and combat receipt races are fixed in code. Popup and all-target receipts require a two-client retest.",
    },
    {
      transfer = "Character-switch reuse",
      score = 97,
      delivery = "JOIN records replica reuse; PSTATE proves lineage and all four family checksums.",
      failure = "Manifest-first retry is bounded; missing proof suppresses broad archive fallback.",
      assessment = "Live pass. Switching the same account to a new character reused the existing replica and completed in about 30 seconds.",
    },
    {
      transfer = "Combat-deferred apply",
      score = 93,
      delivery = "Data arrives in combat; mutation/UI waits; APPLIED follows commit.",
      failure = "Generation replay and three bounded post-combat retries.",
      assessment = "Live behavior is consistent with members entering combat.",
    },
    {
      transfer = "Payload fallback",
      score = 94,
      delivery = "Oversized state falls back to bounded multipart/chunk transport.",
      failure = "Exact opcode, target and timestamp recovery proof; unresolved state remains actionable.",
      assessment = "Task 7 complete. Recovered fallback history no longer degrades current health.",
    },
    {
      transfer = "Sync diagnostics",
      score = 95,
      delivery = "Health, queue, traffic, delivery and recent events are visible.",
      failure = "Current health is isolated from historical counters; rejection categories and invite receipts are independent.",
      assessment = "Tasks 6-7 complete. Current failures remain actionable while recovered and protective drops stay forensic.",
    },
  })

  section.actions = content:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  section.actions:SetJustifyH("LEFT")
  section.actions:SetWordWrap(true)
  section.actions:SetText(
    "|cffa87ae8Workflow 4 implementation checklist|r\n" ..
    "Code progress: |cff4adf6b11 / 11 complete (100%); Preflight and standard two-client pre-raid verification passed|r.\n" ..
    "|cff4adf6b1. COMPLETE|r - Fun Awards now use an authoritative canonical roll/vote event archive. The receiver removes events absent from the host manifest, applies every host event through chunk transport, and only converges after the exact family checksum is advertised back. Empty host archives also clear stale member events.\n" ..
    "|cff4adf6b2. COMPLETE|r - The 70-session archive validation now also repairs a divergent multi-event Fun Awards archive and proves exact manifest convergence, average dice, first-option selections, need rolls without loot, real-vote retention, simulation exclusion, missing host leaves, and removal of receiver-only stale leaves.\n" ..
    "|cff4adf6b3. COMPLETE|r - Test Raid Items returns explicit success/refusal codes, displays the outcome in Live Board, records every attempted target and board token, and changes awaiting state to acknowledged or failed from the existing BSYNC receipt/recovery transaction.\n" ..
    "|cff4adf6b4. COMPLETE|r - Character-switch bootstrap now recognizes an existing account-wide replica, requires matching profile lineage and complete family checksums, requests precise manifests for mismatched families, and suppresses broad archive fallback when proof is late. Roster/P1 and genuine new-character leaves still transfer.\n" ..
    "|cff4adf6b5. COMPLETE|r - Automated validation now proves an aligned account-wide replica queues no archive or full snapshot, a new character requests only the loot manifest and selects only llr/lmr character leaves, and missing precise proof leaves the repair pending without enabling broad archive fallback.\n" ..
    "|cff4adf6b6. COMPLETE|r - Prefix state now accepts registry proof, direct success, compatible side-effect returns, and actual successful sends. Only an unresolved current failure can block health; historical failures remain counters, and invite confirmation/no-receipt status is displayed independently. Deterministic protocol validation covers all six outcomes.\n" ..
    "|cff4adf6b7. COMPLETE|r - Inbound rejection telemetry now distinguishes rate limits, malformed packets, stale traffic, admission drops, unknown opcodes, and handler failures. Outbound health uses exact unresolved/recovered state by opcode, target, and timestamp; historical recovered counters remain visible without lowering current health.\n" ..
    "|cff4adf6b8. COMPLETE|r - Audited the production implementations behind simulation exclusion, authoritative event-leaf replacement, board transaction receipts, character-switch replica reuse, and precise archive proof; all are exercised by the Release Preflight suites.\n" ..
    "|cff4adf6b9. COMPLETE IN CODE|r - Archive transactions now include their session ID. A new lobby cannot inherit an exhausted retry budget, and a deliberate host repair resets only its selected authoritative archive scope.\n" ..
    "|cff4adf6b10. COMPLETE IN CODE|r - Host Fun Awards leaves are classified as Push Host, authoritative manual repair uses BACKGROUND replacement, and Test Raid Items is admitted through PREPARING-to-ACTIVE and acknowledged during combat while UI work remains deferred. The join-manifest proof nil call is also removed.\n" ..
    "|cff4adf6b11. COMPLETE IN CODE|r - Dynamic Fun Awards leaves no longer depend on the profile-wide schema version. Opening/rebuilding awards and importing compatible event leaves preserve newer schema versions, so identical roll/vote events retain identical P3 checksums.\n" ..
    "|cff4adf6bLIVE PASS|r - Matching-build Test Raid Items, exact archive indicators, and precise selected-leaf recovery passed. |cffffcc00RAID TODO|r - Repeat once during combat and validate one genuine raid-loot event."
  )

  section.nextTarget = content:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  section.nextTarget:SetJustifyH("LEFT")
  section.nextTarget:SetWordWrap(true)
  section.nextTarget:SetText(
    "|cffa87ae8Next validation target|r\n" ..
    "The standard two-client pre-raid gate is complete. During a real raid, verify one genuine loot event and one combat-deferred popup/receipt, then repeat the established flow with more than two clients."
  )

  section.Layout = function(self)
    local width = math.max(620, (self.frame:GetWidth() or 0) - 36)
    local y = -18
    for _, fontString in ipairs({ self.intro, self.summary, self.findings }) do
      fontString:ClearAllPoints()
      fontString:SetPoint("TOPLEFT", 18, y)
      fontString:SetWidth(width)
      y = y - ((fontString.GetStringHeight and fontString:GetStringHeight()) or 0) - 18
    end
    self.matrix:ClearAllPoints()
    self.matrix:SetPoint("TOPLEFT", 18, y)
    self.matrix:LayoutForWidth(width)
    y = y - self.matrix:GetHeight() - 18
    for _, fontString in ipairs({ self.actions, self.nextTarget }) do
      fontString:ClearAllPoints()
      fontString:SetPoint("TOPLEFT", 18, y)
      fontString:SetWidth(width)
      y = y - ((fontString.GetStringHeight and fontString:GetStringHeight()) or 0) - 18
    end
    local contentHeight = math.max(400, -y + 90)
    self.frame:SetHeight(contentHeight)
    if self.scroll and self.scroll.UpdateScrollChildRect then
      self.scroll:UpdateScrollChildRect()
    end
    return contentHeight
  end

  scroll:Hide()
  return section
end

local function CreateTutorialSyncWorkflow5Section(parent, createSection)
  local scroll, content, header, cards = createSection(parent, nil, {})
  local section = {
    scroll = scroll,
    frame = content,
    header = header,
    cards = cards,
  }

  section.intro = content:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
  section.intro:SetJustifyH("LEFT")
  section.intro:SetText("Sync Workflow 5 - Test Rolls and Live Roll Gate")

  section.baseline = content:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  section.baseline:SetJustifyH("LEFT")
  section.baseline:SetWordWrap(true)
  section.baseline:SetText(
    "|cff4adf6bCurrent status - 11 August 2026|r\n" ..
    "Code checklist: 10 / 10 complete (100%). Release Preflight: 1658 / 1658. Test Raid Items completed end to end on two matching clients, including close/reopen checksum behavior. The only unexecuted transport branch is Blizzard's genuine raid-loot event intake.\n\n" ..
    "|cffa87ae8Historical baseline - 4 August 2026|r\n" ..
    "Release Preflight passed |cff4adf6b1335 / 1335|r checks on both current clients. Two-client testing then proved that the full profile archive and production-style ITEM_OPEN delivery converge. " ..
    "The remaining findings were a Test Raid Items BSYNC failure and a stale P3 Loot List scope despite visibly identical archive content."
  )

  section.diagnosis = content:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  section.diagnosis:SetJustifyH("LEFT")
  section.diagnosis:SetWordWrap(true)
  section.diagnosis:SetText(
    "|cffa87ae8What is confirmed, and what is not|r\n" ..
    "1. ITEM_OPEN transport was not the latest failure: the member returned its receipt and Sync Management showed 1 / 1 acknowledged. The receiver admitted the item but did not retain a usable local pending choice, so the shared popup gate removed it as no_pending_choices.\n" ..
    "2. ITEM_OPEN and completed BSYNC now create or normalize the local pending choice before queuing the popup. An accepted local session is also sufficient when the roster spelling or case has not refreshed yet. A committed roll is never replaced.\n" ..
    "3. The repeated P3 Fun Awards ACK timeout had a separate cause. The authoritative archive scope existed in the exported snapshot but was dropped by the receiver preview. That made authoritative replacement unauthorized, so apply failed and no APPLIED receipt closed the transfer. The preview now preserves LOOT_LIST, SESSION_HISTORY, and FUN_AWARDS archive scope, including an intentionally empty Fun Awards archive.\n" ..
    "4. Test Raid Items uses BSYNC while Test Live Delivery and genuine loot use ITEM_OPEN. Both now converge on the same pending-choice and popup gate after admission. Test Live Delivery therefore remains the direct network rehearsal for real-item delivery; one genuine drop still validates Blizzard event intake.\n" ..
    "5. Green P1/P2/P3 checks prove profile readiness and archive agreement, not that a new item popup opened. Sync Management must separately show Item Delivery acknowledged and Roll Popup opened.\n" ..
    "6. The Test Raid Items root cause was a transport-contract mismatch: pending selections had no selectedAt timestamp, while BSYNC_SEL rejected every row without one. Pending rows now receive deterministic transport metadata, and current receivers also accept legacy pending rows by deriving a safe timestamp.\n" ..
    "7. P3 scope feedback now accepts and reconciles any independently reported archive checksum. One missing scope can no longer prevent a valid Loot List, Item History, Session History, or Fun Awards proof from replacing stale host state.\n" ..
    "8. The BSYNC receipt deadline previously started when BSYNC_END entered the outbound queue, not when it reached the receiver. A five-item board sent to a large raid can spend minutes behind earlier targets, so later targets could time out before their transaction was physically sent. Receipt tracking now starts only after the transport confirms the matching BSYNC_END send; queued retries and rebuilds follow the same rule, and terminal END send failure closes the transaction explicitly."
  )

  section.matrix = CreateTutorialSyncAuditTable(content, {
    {
      transfer = "Test item construction",
      score = 98,
      delivery = "Host creates five TESTSIM items with joined-member pending selections.",
      failure = "Explicit refusal codes cover session, profile, host, combat and existing-live-item gates.",
      assessment = "Deterministic and covered locally; not the observed weak point.",
    },
    {
      transfer = "Test board dispatch",
      score = 98,
      delivery = "Per-target BSYNC transaction with token, row counts and checksum.",
      failure = "Missing-row resend, end retry, bounded rebuild and terminal receipt state. Receipt deadlines begin after physical END transmission, never while a target is waiting in the shared queue.",
      assessment = "Pending-selection rows satisfy the receiver contract, compact 18-field items are explicitly admitted, and queued large-raid targets no longer expire prematurely. Fully ACTIVE two-client delivery remains the final proof.",
    },
    {
      transfer = "Member popup gate",
      score = 98,
      delivery = "Admitted ITEM_OPEN and completed BSYNC normalize or create the local pending choice before queueing it.",
      failure = "Committed choices are preserved; P1/profile/session gates, exact queue reason, and combat UI deferral remain visible.",
      assessment = "ITEM_OPEN is live-proven. BSYNC now preserves every pending member row needed to populate the same popup queue; two-client proof remains required.",
    },
    {
      transfer = "Choice submission",
      score = 97,
      delivery = "Immutable member choice identity with host confirmation and retry.",
      failure = "Idempotent replay, stale-sequence rejection and bounded receipt retry.",
      assessment = "Preflight proves a 20-member by five-item burst without lost authoritative rows.",
    },
    {
      transfer = "Council vote and assignment",
      score = 96,
      delivery = "Shared runtime selection, vote and authoritative assignment flow.",
      failure = "Sequence checks, board recovery and critical receipt diagnostics.",
      assessment = "Already live-proven after items reach the board.",
    },
    {
      transfer = "Real loot item delivery",
      score = 95,
      delivery = "Blizzard loot event becomes ITEM_REPORT, then tokened per-recipient ITEM_OPEN before entering the shared runtime.",
      failure = "One group ITEM_OPEN carries a dispatch token; each joined recipient returns ITEM_ACK after data admission, and only missing recipients receive bounded 5/8/12/16-second targeted retries.",
      assessment = "Sync Management identifies acknowledged, pending, and terminally failed recipients. Duplicate delivery is idempotent and combat defers only UI.",
    },
    {
      transfer = "Test-to-live equivalence",
      score = 94,
      delivery = "Popup, choices, voting and assignment are shared; initial item delivery is different.",
      failure = "Test Raid Items proves BSYNC; Test Live Delivery proves tokened ITEM_OPEN plus recipient ACK/retry. Only Blizzard's START_LOOT_ROLL event remains exclusive to a genuine drop.",
      assessment = "The two developer tests cover both network branches without persistent simulation data and now expose exact member delivery proof.",
    },
    {
      transfer = "P3 Fun Awards closure",
      score = 96,
      delivery = "Authoritative FUN_AWARDS snapshots preserve their replacement scope through receiver preview and apply.",
      failure = "Empty archives are valid replacements, stale events are removed, and successful apply returns the transfer receipt.",
      assessment = "Fix directly targets repeated 3/3-chunk ACK timeouts on new profiles. Preflight and two-client receipt closure remain required.",
    },
  })

  section.actions = content:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  section.actions:SetJustifyH("LEFT")
  section.actions:SetWordWrap(true)
  section.actions:SetText(
    "|cffa87ae8Workflow 5 implementation checklist|r\n" ..
    "|cff4adf6b1. COMPLETE|r - Admit BSYNC through PREPARING-to-ACTIVE and commit/acknowledge board data during combat while deferring only UI refresh.\n" ..
    "|cff4adf6b2. COMPLETE|r - Track each Test Raid Items target through dispatch, missing rows, receipt, retry, rebuild and terminal failure.\n" ..
    "|cff4adf6b3. COMPLETE|r - Record bounded BSYNC receiver admission, row counts, end/checksum result, ACK send state, choice queue, popup opened, exact gate, and combat UI deferral; expose Board Receive and Roll Popup in Sync Management. COMPLETE is now sent after board persistence but before editor/popup refresh, so a UI error cannot hide a successful data receipt from the host.\n" ..
    "|cff4adf6b4. COMPLETE|r - Added Test Live Delivery: one TESTSIM item uses the production ITEM_OPEN encoder, active-session receiver admission, local choice queue and popup gate. Host send acceptance and member admission are exposed separately, while TESTSIM persistence remains excluded from Loot List history and Fun Awards.\n" ..
    "|cff4adf6b5. COMPLETE|r - Real and transport-test ITEM_OPEN delivery now sends one tokened group packet, then tracks every joined recipient independently. Members acknowledge immediately after runtime admission, before UI refresh; only missing members receive bounded whole-packet replay. The host accepts duplicate ACKs, records terminal timeout per character, and shows exact receipt state in Sync Management. Untokened 13/14-field packets remain valid inside protocol v7.\n" ..
    "|cff4adf6b6. COMPLETE|r - Deterministic receiver replay validation now proves first admission, a lost first ACK, duplicate ITEM_OPEN classification, preservation of an already-submitted roll, a second ACK intent without item readmission, host convergence after the repeated ACK, safe acceptance of a restarted dispatch generation, and a bounded 64-item receiver replay cache.\n" ..
    "|cff4adf6b7. COMPLETE|r - Repair the observed receiver contradiction: ITEM_OPEN/BSYNC admission now creates or normalizes a pending local choice, accepts the locally confirmed session while roster identity catches up, preserves submitted rolls, and reports the exact queue gate. Authoritative P3 metadata now survives preview so empty or populated Fun Awards replacements can apply and acknowledge.\n" ..
    "|cff4adf6b8. COMPLETE|r - Two-client testing proved full profile/archive convergence and production-style Test Live Delivery. The remaining BSYNC failure was traced to pending rows without selectedAt metadata; sender and receiver compatibility are now hardened. P3 accepts independent scope proofs so a missing sibling checksum cannot retain stale Loot List feedback.\n" ..
    "|cff4adf6b9. COMPLETE|r - Start BSYNC receipt and retry deadlines only after the transaction END marker is physically sent. Outbound metadata links the queue result to the exact board token and has its own transport validation; terminal END failure closes the receipt tracker. Compact 18-field item fallback is now part of the declared receiver schema.\n" ..
    "|cff4adf6b10. CODE + TWO-CLIENT PRE-RAID PASS|r - Preflight and Test Raid Items passed on matching clients; board delivery, choices, and later checksum convergence behaved correctly. |cffffcc00RAID TODO|r - Finish with one genuine loot drop for Blizzard event intake.\n\n" ..
    "|cffa87ae8Code progress: 10 / 10 complete (100%); genuine raid-event validation remains|r"
  )

  section.nextTarget = content:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  section.nextTarget:SetJustifyH("LEFT")
  section.nextTarget:SetWordWrap(true)
  section.nextTarget:SetText(
    "|cffa87ae8Next validation target|r\n" ..
    "Use one genuine raid loot drop to validate Blizzard event intake through ITEM_REPORT/ITEM_OPEN. The synthetic Test Raid Items branch is already green on two clients."
  )

  section.Layout = function(self)
    local width = math.max(620, (self.frame:GetWidth() or 0) - 36)
    local y = -18
    for _, fontString in ipairs({ self.intro, self.baseline, self.diagnosis }) do
      fontString:ClearAllPoints()
      fontString:SetPoint("TOPLEFT", 18, y)
      fontString:SetWidth(width)
      y = y - ((fontString.GetStringHeight and fontString:GetStringHeight()) or 0) - 18
    end
    self.matrix:ClearAllPoints()
    self.matrix:SetPoint("TOPLEFT", 18, y)
    self.matrix:LayoutForWidth(width)
    y = y - self.matrix:GetHeight() - 18
    for _, fontString in ipairs({ self.actions, self.nextTarget }) do
      fontString:ClearAllPoints()
      fontString:SetPoint("TOPLEFT", 18, y)
      fontString:SetWidth(width)
      y = y - ((fontString.GetStringHeight and fontString:GetStringHeight()) or 0) - 18
    end
    local contentHeight = math.max(400, -y + 90)
    self.frame:SetHeight(contentHeight)
    if self.scroll and self.scroll.UpdateScrollChildRect then
      self.scroll:UpdateScrollChildRect()
    end
    return contentHeight
  end

  scroll:Hide()
  return section
end

local function CreateTutorialSyncWorkflow6Section(parent, createSection)
  local scroll, content, header, cards = createSection(parent, nil, {})
  local section = {
    scroll = scroll,
    frame = content,
    header = header,
    cards = cards,
  }

  section.intro = content:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
  section.intro:SetJustifyH("LEFT")
  section.intro:SetText("Sync Workflow 6 - 30-Player Capacity Hardening")

  section.baseline = content:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  section.baseline:SetJustifyH("LEFT")
  section.baseline:SetWordWrap(true)
  section.baseline:SetText(
    "|cff4adf6bCurrent status - 11 August 2026|r\n" ..
    "Code capacity checklist: 8 / 8 complete (100%). Release Preflight: 1658 / 1658, including deterministic 20- and 30-member coverage. The production two-client pre-raid path passed, but no real 10-, 20-, or 30-player group has been tested; those remain environment gates rather than known code defects.\n\n" ..
    "|cffa87ae8Historical baseline - 4 August 2026|r\n" ..
    "The current synchronization architecture is retained. Session priorities, bounded payloads, acknowledgements, idempotent replay, checksum convergence, targeted recovery, combat-safe data admission, and P3 yielding already form the correct reliability model. " ..
    "Workflow 6 does not replace that model. It adds capacity control so 20-30 players can share the same finite addon-message channel without a large board transaction occupying the complete queue or causing receipt deadlines that ignore real queue pressure."
  )

  section.design = content:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  section.design:SetJustifyH("LEFT")
  section.design:SetWordWrap(true)
  section.design:SetText(
    "|cffa87ae8What remains unchanged|r\n" ..
    "1. ITEM_OPEN remains one initial group delivery with one receipt per joined recipient and targeted retries only for missing recipients.\n" ..
    "2. CHOICE remains an immutable member decision. The host accepts the newest sequence, mirrors it as the receipt, and safely ignores duplicate or stale replays.\n" ..
    "3. P1 session traffic remains ahead of P2/P3 data. Archive traffic continues yielding whenever live or control traffic needs the channel.\n" ..
    "4. Profile snapshots remain chunked, acknowledged, checksum-verified, recoverable, and combat-safe.\n" ..
    "5. Terminal failure remains visible. LidCouncil never calls an exhausted or rejected transfer successful.\n\n" ..
    "|cffa87ae8Why additional handling is needed|r\n" ..
    "A normal live item scales well: one group ITEM_OPEN, per-member ITEM_ACK, per-member CHOICE, and host RCHOICE receipts. The original 30-player five-item full snapshot required 4,553 BSYNC messages before votes. With 29 raider recipients, initial delivery now sends each recipient the five immutable item rows plus only that recipient's five pending choice rows: 348 messages total, a reduction of 4,205 messages. Submitted responses and council votes remain shared, and recipients with voting rights retain the complete pending/candidate matrix so council readiness stays exact. The successful-path live reserve remains 1,315 messages. In the all-receipts-lost fault bound, four remote council members can add 1,200 targeted RCHOICE repairs, producing a 2,515-message reliable-live bound; validation therefore treats overlap with a complete BSYNC as pressure that must be staged and prioritized rather than assumed to fit."
  )

  section.matrix = CreateTutorialSyncAuditTable(content, {
    {
      transfer = "Session and P1 bootstrap",
      score = 96,
      delivery = "Priority-one roster/control followed by bounded roll-ready profile data.",
      failure = "Receipts, profile state, retries and visible terminal failure.",
      assessment = "The correct model for 30 players. Capacity work must preserve this lane and its queue headroom.",
    },
    {
      transfer = "Real ITEM_OPEN delivery",
      score = 95,
      delivery = "One group packet, independent recipient receipts, targeted replay for missing members.",
      failure = "Duplicate-safe admission and bounded 5/8/12/16-second recipient recovery.",
      assessment = "Scales approximately linearly and remains the strongest 30-player live path.",
    },
    {
      transfer = "Roll choice submission",
      score = 92,
      delivery = "Member whisper, direct host mirror receipt, then shared authoritative mirror.",
      failure = "Four attempts, immutable identity, stale-sequence rejection and local unconfirmed state.",
      assessment = "Both state semantics and bounded 30-player retry pressure are deterministic. Admission control is the remaining protection against overlap.",
    },
    {
      transfer = "P3 archive convergence",
      score = 91,
      delivery = "Chunked background snapshots and scoped repairs paced across targets.",
      failure = "Checksum proof, ACK recovery, bounded retries and archive yielding.",
      assessment = "Expected to converge but may take much longer at 30 players. It must never delay live rolls.",
    },
    {
      transfer = "Full board / Test Raid Items",
      score = 94,
      delivery = "Scoped BSYNC for raiders; full pending/candidate state for council-capable recipients.",
      failure = "Checksummed receipt, selective retry, and automatic promotion to the complete snapshot for bounded rebuild.",
      assessment = "The normal 30-player five-item path fell from 4,553 to 348 messages while retaining the proven full transaction as recovery fallback.",
    },
    {
      transfer = "Queue and observability",
      score = 97,
      delivery = "Priority queue with 6,000 normal slots and 64 critical overflow slots.",
      failure = "Throttle retry, API retry, overflow rejection and Sync Management diagnostics.",
      assessment = "Production boundaries are validated. Sync Management now shows projected BSYNC load, live-safe headroom, active and staged targets, delayed receipts, and a pre-overflow capacity warning.",
    },
  })

  section.actions = content:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  section.actions:SetJustifyH("LEFT")
  section.actions:SetWordWrap(true)
  section.actions:SetText(
    "|cffa87ae8Workflow 6 implementation checklist|r\n" ..
    "|cff4adf6b1. COMPLETE + VERIFIED|r - Parameterized the choice-burst validator with bounded raid and item counts. Release Preflight requires separate 20-member and 30-member five-item runs covering unique receipts, replay recovery, corrections, stale rejection, and unchanged authoritative row counts. In-game Release Preflight passed 1353 / 1353 checks across five suites in 0.13 seconds.\n" ..
    "|cff4adf6b2. COMPLETE + VERIFIED|r - Production transport validation covers a 30-player five-item load: 145 ITEM_ACKs per delivery pass, 580 bounded CHOICE attempts, 580 direct chooser receipts, 150 authoritative mirrors, 600 remote-council RCHOICE_ACK receipts, and at most 1,200 targeted council-mirror repairs. It also covers the 4,553-message baseline BSYNC, 5,278-message populated board, selective-repair pressure, the 6,000 + 64 queue boundary, and actual MEMBER / ITEM_OPEN / RCHOICE_ACK / RCHOICE / BSYNC priority insertion. Both populated-board overlap and the full 2,515-message receipt-repaired live bound are explicitly recognized as unsafe without staging.\n" ..
    "|cff4adf6b3. COMPLETE + VERIFIED|r - Added atomic BSYNC admission planning before any local restore or remote enqueue. It counts exact target, item, selection and vote rows; reads current queue depth and limits; reserves bounded ITEM_OPEN and CHOICE receipt pressure; records the plan; and returns capacity_deferred before sending when safe headroom is unavailable. Deterministic coverage includes safe 20-player and 30-player baselines, populated-board overflow, busy-queue deferral and fail-closed queue-status handling.\n" ..
    "|cff4adf6b4. COMPLETE + VERIFIED|r - Added recipient-staged BSYNC delivery with a maximum of two active targets. Remaining recipients wait without occupying queue slots; each acknowledgement or terminal failure releases one slot; every next target is re-admitted against current queue depth while preserving the original 30-player live-traffic reserve. Session cleanup cancels staged work and invalidates delayed pumps.\n" ..
    "|cff4adf6b5. COMPLETE + VERIFIED|r - Replaced redundant all-member pending-row replication with role-aware BSYNC transactions. Raiders receive complete item definitions, their own pending rows, and all submitted responses/votes; council-capable recipients retain the full pending/candidate matrix required for accurate readiness and voting. The 29-raider initial five-item baseline falls from 4,553 to 348 messages while preserving the full 1,315-message live reserve. One remote council raises that example to 493 messages. Checksum or receipt escalation promotes a scoped recipient to the original complete snapshot.\n" ..
    "|cff4adf6b6. COMPLETE + VERIFIED|r - Choice receipt recovery now recognizes recent matching host ITEM_OPEN / BSYNC activity and local priority-1/2 queue pressure. The original four-send limit remains unchanged; proven live pressure starts one fixed 12-second receipt-only grace period with no fifth transmission, no indefinite extension, and the existing local reconciliation fallback at the hard deadline. Archive-only pressure and mismatched transactions fail closed.\n" ..
    "|cff4adf6b7. COMPLETE + VERIFIED|r - Sync Management now exposes queue headroom, the live-traffic reserve, projected BSYNC messages, active and staged target counts, acknowledgement progress, failed targets, and delayed live receipts. Capacity protection raises an explicit warning before queue overflow and distinguishes automatic staged resumption from an initial action that must be retried. Four deterministic checks cover the displayed admission and staged-dispatch source data. In-game Release Preflight passed 1426 / 1426 checks across five suites in 0.12 seconds.\n" ..
    "|cff4adf6b8. COMPLETE + VERIFIED|r - Added eight aggregate 30-member fault-matrix gates for one item, five items, simultaneous choices, mixed combat, one missing receipt, disconnect/rejoin, active P3 archive, and a late join. The gates compose the production choice, receipt, combat inbox, roster, queue-yield, archive-transaction, and 70-session convergence validators. They require no silent row loss, collision-free receipts, bounded retries, visible terminal failures, immediate roster transitions, live traffic ahead of P3, and authoritative Session History and Fun Awards convergence. In-game Release Preflight passed 1434 / 1434 checks across five suites in 0.13 seconds. A subsequent two-client environment test succeeded with a new character across two profiles: all profile data and roll popups converged correctly. The large history profile required about 10 minutes for its first full synchronization, then completed with correct data and working roll options.\n\n" ..
    "|cffa87ae8Code progress: 8 / 8 complete (100%)|r\n" ..
    "Verified baseline: 1658 / 1658 checks. Two-client fresh-profile, unchanged rejoin, large-history synchronization, precise leaf pull, and roll delivery are confirmed. The code-side Workflow 6 gate is complete; larger raid-scale testing remains the final environment step."
  )

  section.nextTarget = content:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  section.nextTarget:SetJustifyH("LEFT")
  section.nextTarget:SetWordWrap(true)
  section.nextTarget:SetText(
    "|cffa87ae8Next implementation target|r\n" ..
    "Scale the successful two-client workflow to a larger raid, ideally 20-30 players: one and five items, simultaneous choices, mixed combat, one missing receipt, disconnect/rejoin, active P3 archive, and a late join. Require no silent loss, visible bounded recovery, and eventual archive convergence. Segment: Workflow 6 / final environment validation."
  )

  section.Layout = function(self)
    local width = math.max(620, (self.frame:GetWidth() or 0) - 36)
    local y = -18
    for _, fontString in ipairs({ self.intro, self.baseline, self.design }) do
      fontString:ClearAllPoints()
      fontString:SetPoint("TOPLEFT", 18, y)
      fontString:SetWidth(width)
      y = y - ((fontString.GetStringHeight and fontString:GetStringHeight()) or 0) - 18
    end
    self.matrix:ClearAllPoints()
    self.matrix:SetPoint("TOPLEFT", 18, y)
    self.matrix:LayoutForWidth(width)
    y = y - self.matrix:GetHeight() - 18
    for _, fontString in ipairs({ self.actions, self.nextTarget }) do
      fontString:ClearAllPoints()
      fontString:SetPoint("TOPLEFT", 18, y)
      fontString:SetWidth(width)
      y = y - ((fontString.GetStringHeight and fontString:GetStringHeight()) or 0) - 18
    end
    local contentHeight = math.max(400, -y + 90)
    self.frame:SetHeight(contentHeight)
    if self.scroll and self.scroll.UpdateScrollChildRect then
      self.scroll:UpdateScrollChildRect()
    end
    return contentHeight
  end

  scroll:Hide()
  return section
end

local function CreateTutorialSyncWorkflow7Section(parent, createSection)
  local scroll, content, header, cards = createSection(parent, nil, {})
  local section = {
    scroll = scroll,
    frame = content,
    header = header,
    cards = cards,
  }

  section.intro = content:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
  section.intro:SetJustifyH("LEFT")
  section.intro:SetText("Sync Workflow 7 - Release Risk and Host Performance")

  section.baseline = content:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  section.baseline:SetJustifyH("LEFT")
  section.baseline:SetWordWrap(true)
  section.baseline:SetText(
    "|cffa87ae8Current status - 11 August 2026|r\n" ..
    "Release Preflight passes 1658 / 1658 checks across seven suites. The standard two-client pre-raid flow is reliable: exact small/large synchronization, unchanged rejoin, Test Raid Items, bounded archive proof, precise leaf pull, and idle convergence passed. Reliability remediation is complete. Performance work remains open because large-profile synchronization can still drive the host near 90% CPU and sharply reduce FPS; this is a future optimization item, not evidence of a new retry loop when counters plateau and the queue drains. Workflow 7 retains the historical 0.9.12 risk classifications below.\n\n" ..
    "|cffa87ae8Risk scale|r\n" ..
    "Very Low: presentation, documentation, or guarded diagnostics only. Low: narrow runtime behavior with no persistence or protocol change. Medium: scheduling, caching, or state-lifecycle behavior that requires focused regression checks. High: wire contract, authoritative merge, acknowledgement, or retry semantics. Very High: incompatible protocol or persistence redesign."
  )

  section.riskRegister = content:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  section.riskRegister:SetJustifyH("LEFT")
  section.riskRegister:SetWordWrap(true)
  section.riskRegister:SetText(
    "|cffa87ae80.9.12 changelog risk register|r\n" ..
    "|cff8bdc9aVERY LOW|r - In Progress. Release-status text only; no functional path is changed.\n" ..
    "|cff8bdc9aVERY LOW|r - UI-only sync-banner crash bugfix. Sanitizes incomplete destination labels before rendering; queue, payload, acknowledgement, and profile data are untouched.\n" ..
    "|cffb7df88LOW|r - Fixed tooltips sometimes showing item level 44 instead of the session item level. The sender's explicit session item level now wins over a receiver's unresolved or base hyperlink level. Roll choices, points, awards, persistence, and delivery receipts are unchanged.\n" ..
    "|cff8bdc9aVERY LOW|r - Added Sync Workflow 7 risk tracking and host performance checklist. Tutorial and changelog UI only.\n" ..
    "|cff8bdc9aVERY LOW|r - Added local sync CPU timing diagnostics for snapshot, queue, and Editor work. Sampled counters and developer-facing display only; transport ordering, pacing, payloads, retries, and acknowledgements are unchanged.\n" ..
    "|cffffcc00MEDIUM|r - Optimized high-depth sync queue insertion, selection, and obsolete-transfer cleanup. Wire payloads, priorities, per-transfer order, target pacing, retries, and acknowledgement rules remain unchanged; regression focus is live traffic preemption and multi-target archive fairness.\n" ..
    "|cffffcc00MEDIUM|r - Added revision-safe prepared snapshot reuse and bounded snapshot preparation bursts. Cache identity includes profile revision, exact scope, session context, and chunk size; row patches and member-specific patch repairs are excluded. Regression focus is immediate invalidation after profile changes and exact archive convergence.\n" ..
    "|cff8bdc9aVERY LOW|r - Added a copyable sync performance report with measured warning thresholds. This reads existing counters only and does not change synchronization behavior."
  )

  section.performance = content:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  section.performance:SetJustifyH("LEFT")
  section.performance:SetWordWrap(true)
  section.performance:SetText(
    "|cffa87ae8Host CPU assessment|r\n" ..
    "The transport already sends one queued addon message per scheduled flush, prioritizes P1/live traffic, stages recipients, coalesces superseded work, and makes P3 archive traffic yield. A global delay is therefore easy to add and has low protocol risk, but it directly lengthens an already long first synchronization and does not address the 114% session-open spike.\n\n" ..
    "The measured opening spike comes from synchronous profile export and archive manifest construction, reaching roughly 275 ms in the large-profile test. During upload, queue selection and flush reached roughly 20-33 ms at depths above 1,500 because the original path repeatedly scanned the full queue. Sync Management itself can spend roughly 400-650 ms rebuilding its detailed Editor view; this is intentionally left unchanged because navigating away removes that optional presentation cost.\n\n" ..
    "Reducing the profile chunk size from 120 to 80 did not improve the measured frame cost and produced more queued messages. Chunk size remains user-configurable and is not changed by this workflow; use 120 for the first before-and-after comparison. A later 190-byte test is valid only after Preflight and two-client delivery remain clean.\n\n" ..
    "The implemented safe path targets transport CPU without adding a blanket slowdown: equal-priority chunks append without a full scan, ordinary flushes use an already-ready head, a single archive target does not trigger repeated alternate-target searches, and obsolete cleanup runs when terminal work reaches selection. Immutable prepared snapshots are reused only for an identical revision, scope, session context, and chunk size. P1, ITEM_OPEN, CHOICE, receipts, target pacing, retry budgets, and acknowledgement deadlines are unchanged.\n\n" ..
    "5 August live evidence: the small-profile report exceeded no threshold, with snapshot preparation averaging 7.7 ms and queue flush averaging 0.4 ms. The large profile synchronized completely; with Sync Management closed, host addon CPU fell to roughly 3% and the member remained near 0.6%. Keeping the host monitor open remains presentation-bound because each detailed refresh can cost hundreds of milliseconds. Editor opening and main-tab dispatch now refresh only the visible workflow page instead of rebuilding every hidden workflow page, duplicate same-page layout passes were removed, and dynamic button state events are coalesced instead of each starting a full-Editor theme pass. Large-profile Test Raid Items and local roll choices still cause a visible profile-size-dependent pause and are the next 0.9.14 performance target."
  )

  section.actions = content:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  section.actions:SetJustifyH("LEFT")
  section.actions:SetWordWrap(true)
  section.actions:SetText(
    "|cffa87ae8Workflow 7 checklist|r\n" ..
    "|cff4adf6b1. COMPLETE|r - Created the 0.9.12 functional-risk register and recorded the 1436 / 1436 Preflight baseline.\n" ..
    "|cff4adf6b2. COMPLETE - VERY LOW RISK|r - Added separate snapshot export, checksum/manifest, chunk creation, queue insertion/prune/select/remove/flush, and Editor refresh timing. High-frequency queue phases are sampled every 20 calls to keep diagnostic overhead bounded.\n" ..
    "|cff4adf6b3. COMPLETE - MEDIUM RISK|r - Added stable tail append and binary priority insertion, a ready-head flush path, single-target archive selection caching, and terminal-only obsolete cleanup. Exact priority and per-transfer ordering remain enforced.\n" ..
    "|cff8bdc9a4. CLOSED - NO CODE CHANGE|r - Sync Management remains intentionally live and detailed. The user can leave the page to avoid its known Editor-rendering cost; transport performance is optimized independently.\n" ..
    "|cff4adf6b5. COMPLETE - MEDIUM RISK|r - Added immutable prepared-snapshot reuse keyed by profile revision, exact domain/subdomain scope, session context, authoritative archive scope, and chunk size. Revision changes immediately prevent reuse.\n" ..
    "|cffffcc006. PARTIAL - MEDIUM RISK|r - Limited each snapshot batch callback to one previously unprepared export. A single large Lua serialization remains synchronous; deeper incremental serialization is deferred unless post-change measurements still show unacceptable preparation spikes.\n" ..
    "|cffffcc007. OPTIONAL - LOW TO MEDIUM RISK|r - Add adaptive P3-only pacing if measured frame pressure remains high. Do not slow P1/live traffic and include pacing delay in receipt/recovery deadlines.\n" ..
    "|cff4adf6b8. COMPLETE - VERY LOW RISK|r - Added a copyable CPU report covering tuning, queue depth, throughput, prepared-snapshot cache activity, all measured CPU phases, and evidence-based warning thresholds.\n" ..
    "|cff4adf6b9. COMPLETE - TWO-CLIENT PRE-RAID GATE PASSED|r - Release Preflight passes 1658 / 1658. Small and large profiles converge, Test Raid Items works, precise repair converges, and the queue becomes idle. Full Profile Transfer, genuine raid events, and raid-scale CPU remain separate tests.\n\n" ..
    "|cffa87ae8Progress: 7 / 9 resolved; step 6 remains partial and step 7 optional|r"
  )

  section.nextTarget = content:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  section.nextTarget:SetJustifyH("LEFT")
  section.nextTarget:SetWordWrap(true)
  section.nextTarget:SetText(
    "|cffa87ae8Next verification target|r\n" ..
    "Profile Transfer export/import remains unverified between two clients. In the first real raid, measure host CPU/FPS during a large archive upload and genuine loot delivery with Sync Management closed; then decide whether incremental serialization or adaptive P3-only pacing is needed. Do not change the proven P1/live receipt path without new evidence."
  )

  section.Layout = function(self)
    local width = math.max(620, (self.frame:GetWidth() or 0) - 36)
    local y = -18
    for _, fontString in ipairs({ self.intro, self.baseline, self.riskRegister, self.performance, self.actions, self.nextTarget }) do
      fontString:ClearAllPoints()
      fontString:SetPoint("TOPLEFT", 18, y)
      fontString:SetWidth(width)
      y = y - ((fontString.GetStringHeight and fontString:GetStringHeight()) or 0) - 18
    end
    local contentHeight = math.max(400, -y + 90)
    self.frame:SetHeight(contentHeight)
    if self.scroll and self.scroll.UpdateScrollChildRect then
      self.scroll:UpdateScrollChildRect()
    end
    return contentHeight
  end

  scroll:Hide()
  return section
end

local function CreateTutorialSyncWorkflow8Section(parent, createSection)
  local scroll, content, header, cards = createSection(parent, nil, {})
  local section = {
    scroll = scroll,
    frame = content,
    header = header,
    cards = cards,
  }

  section.intro = content:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
  section.intro:SetJustifyH("LEFT")
  section.intro:SetText("Sync Workflow 8 - Editor and Large-Profile Performance")

  section.baseline = content:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  section.baseline:SetJustifyH("LEFT")
  section.baseline:SetWordWrap(true)
  section.baseline:SetText(
    "|cff4adf6bCurrent status - 11 August 2026|r\n" ..
    "Workflow gates completed: 11 / 14 (78.6%). Release Preflight: 1658 / 1658. Step 12's two-client small/large regression is now a live pass for prepare, first join, unchanged rejoin, P1/P2/P3, Test Raid Items, precise leaf pull, and idle convergence. Complete/scoped Profile Transfer remains step 11; real 10/20/30-player scale remains steps 13-14.\n\n" ..
    "|cffa87ae8Starting point - 5 August 2026|r\n" ..
    "Small and large profiles have completed exact two-client synchronization, P1/P2/P3 convergence, Test Raid Items, and Test Live Delivery. With Sync Management closed, the measured large-profile host load fell from severe sustained load to roughly 3%, while the member remained near 0.6%. The remaining performance complaints are local and profile-size dependent: opening data-heavy Editor pages can briefly reach 100% addon CPU, and creating or answering large-profile test rolls can pause the client for about one second.\n\n" ..
    "The first code pass now refreshes only the visible main page, removes duplicate same-page layout work, and coalesces dynamic button theme refreshes. The follow-up Profile pass rebuilds only the active subsection. Direct page timing then isolated repeated developer Profile Size serialization; a revision-keyed cache reduced the measured Profile page from roughly 90ms average to 31.9ms, with Rolls at 30.4ms and Points at 29.5ms. Runtime phase diagnostics then proved that Test Raid Items spent 331.6ms of 381.4ms in duplicate Editor refresh work and local choices spent about 292.1ms of 297.0ms in board/Editor refresh work. The verified optimization reduced Test Raid Items to 49.7ms and local choices to 14.2ms average / 34.6ms maximum. Test creation now reuses its already-rendered board, and local choices refresh the board plus a lightweight summary without rebuilding the full Editor page. Workflow 8 does not change the proven transport, priority, receipt, retry, or combat-defer model. All implementation limits and acceptance criteria use 30 simultaneous raiders as the engineering target so a normal 20-player raid retains operational headroom."
  )

  section.readiness = content:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  section.readiness:SetJustifyH("LEFT")
  section.readiness:SetWordWrap(true)
  section.readiness:SetText(
    "|cffa87ae8Current readiness estimates|r\n" ..
    "These are code-and-evidence estimates, not a substitute for a 30-player live test. Twenty players are the expected operating case; 30 players are the capacity and release-design bar.\n\n" ..
    "|cff4adf6b100%|r - Automated validation for the current build: 1658 / 1658 checks across seven suites.\n" ..
    "|cff4adf6b99%|r - P1 roll-ready bootstrap. Explicit delivery, receipts, bounded recovery, and successful two-client small/large-profile evidence.\n" ..
    "|cff4adf6b99%|r - P2 session continuity. Current session, roster, roles, rights, and live state converged across fresh join and unchanged rejoin.\n" ..
    "|cff4adf6b99%|r - P3 archive convergence in the tested two-client case. Loot List, item history, session history, and Fun Awards converged; multi-member live scale remains pending.\n" ..
    "|cff4adf6b98%|r - Live Board and roll delivery. Test Raid Items passes with the proven receipt/retry model; a genuine loot event and 20-30 simultaneous responses remain unverified.\n" ..
    "|cff4adf6b98%|r - Synchronization architecture overall. Per-target transactions, priority lanes, receipts, recovery, combat deferral, and archive fairness have no known correctness blocker in the tested path.\n" ..
    "|cffffcc0070%|r - Host performance under large-profile transfer. Reliability is bounded, but reported CPU can still approach 90% with sharp FPS loss; incremental serialization or P3-only pacing remains future work.\n" ..
    "|cff6fb8ff92%|r - Profile Transfer and targeted recovery. Seven-suite validation covers package contracts and recovery paths; complete and scoped two-client import/export testing is still pending.\n" ..
    "|cff6fb8ff93%|r - Editor opening and main-page navigation. Visible-page dispatch is confirmed improved, and Profile subsection scoping is implemented; its stale-state and large-profile timing gate remains.\n" ..
    "|cff4adf6b97%|r - Large-profile roll responsiveness. Test Raid Items now completes in 49.7ms and local choice submission averages 14.2ms after removing duplicate full-page refresh work; multi-member scale remains the final evidence gate.\n" ..
    "|cff6fb8ff90%|r - 20-player readiness. This is the expected operating case and should remain comfortably inside limits designed for 30 targets; live concurrency has not yet been demonstrated.\n" ..
    "|cffffcc0084%|r - 30-player design-target readiness. No architectural rewrite is indicated, but queue pressure, burst receipts, frame time, fairness, and bounded recovery must pass at this size before release confidence is raised."
  )

  section.findings = content:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  section.findings:SetJustifyH("LEFT")
  section.findings:SetWordWrap(true)
  section.findings:SetText(
    "|cffa87ae8Assessment from the latest feedback and code scan|r\n" ..
    "1. Editor construction is eager only once at login; repeated opening cost came primarily from refreshing hidden data pages, not repeatedly loading addon modules.\n" ..
    "2. The old Editor open path refreshed Profile, Session, Live Board, Loot List, and Loot Presets together. It now dispatches only the visible page.\n" ..
    "3. Several page refreshers immediately called a second layout that could refresh the same page again. The duplicate page-layout pass is now skipped.\n" ..
    "4. Button state events could each trigger a full-Editor theme scan. These requests are now deferred and coalesced into one pass.\n" ..
    "5. Profile now follows the same visible-subsection model as Session, Live Board, and Loot List. Hidden role matrices, member rows, archive counters, galleries, roll editors, point editors, and backup lists are deferred until their subsection opens.\n" ..
    "6. The first complete Profile timing set measured Overview at 89.1ms average / 103.8ms maximum and Rolls at 96.9ms. Caching only the developer Profile Size serialization reduced Profile to 31.9ms average, Overview to 36.1ms, Rolls to 30.4ms, and Points to 29.5ms while preserving revision invalidation.\n" ..
    "7. Loot List and Loot Presets originally reported 0.0ms because their direct refresh paths bypassed the navigation-level timer. Timers now wrap the actual refresh methods, including direct Loot List selection refreshes.\n" ..
    "8. The large-profile roll pause was local rather than network throughput. The measured Test Raid Items run spent 331.6ms of 381.4ms in its duplicate Editor refresh; local choices spent about 292.1ms of 297.0ms in board/Editor refresh while recording and dispatch remained below 7ms. The verified optimization reduced Test Raid Items to 49.7ms and local choices to 14.2ms average / 34.6ms maximum.\n" ..
    "9. The remaining 582.9ms Session-page spike belongs to the already-known detailed Sync Management presentation path. Transport continues normally after navigating away, so the user selected navigation-away behavior instead of adding another refresh-rate setting.\n" ..
    "10. The navigation follow-up measured Live Board at 179.3ms average / 202.6ms maximum. New diagnostics split this into Editor state, attach, controls, layout and scroll work, then split board attachment into embedded layout, render and finalize phases; board rendering is further divided into layout, item list, Loot List totals, and candidate rows. This pass changes no behavior.\n" ..
    "11. The phase report isolated Live Board state/readiness at 108.0ms average while board attachment averaged 11.5ms and board rendering averaged 15.1ms. The page was constructing the full developer runtime-diagnostics report merely to read three simulation-status fields. It now uses a minimal read-only status API; full diagnostics remain unchanged for diagnostic consumers. Verification reduced state/readiness to 3.5ms average and the complete Live Board page from 128.0ms to 23.6ms average, with a 38.4ms maximum.\n" ..
    "12. The first diagnostic wiring exceeded WoW Lua's 60-upvalue limit in the already-large board constructor. Performance hooks now resolve inside the measured refresh and attach functions, preserving every counter without adding constructor upvalues or changing board behavior.\n" ..
    "13. Session History and Sync Management builders are already conditional and do not run while Current Session is selected. The first phase report isolated 102.5ms of the 121.0ms Session average in full repair-candidate reconstruction. Removing that page-level work reduced the candidates phase to 0.0ms, but an active one-member session exposed the same diagnostic fallback inside host-row P1/P2 resolution, moving roughly 492.3ms into Current Session render. Existing authoritative host/self READY decisions now run before that fallback. Verification reduced Current Session render to 0.3ms average / 0.6ms maximum and the Session page to 19.5ms average. Remote members still use the replicated P1/P2/P3 status matrix and archive receipt checksums, while full divergence and repair metadata remains in Sync Management.\n" ..
    "14. Do not trade the proven live behavior for cosmetic speed. P1/live queue priority, ITEM_OPEN/CHOICE receipts, retry deadlines, combat deferral, and authoritative apply rules stay unchanged unless measurements identify one of them as the bottleneck."
  )

  section.actions = content:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  section.actions:SetJustifyH("LEFT")
  section.actions:SetWordWrap(true)
  section.actions:SetText(
    "|cffa87ae8Workflow 8 implementation checklist|r\n" ..
    "|cff4adf6b1. COMPLETE - VERY LOW RISK|r - Release Preflight passes 1498 / 1498 checks after the visible-page and coalesced-theme changes.\n" ..
    "|cff4adf6b2. COMPLETE - VERY LOW RISK|r - Large-profile Editor opening and visible main-page dispatch are confirmed noticeably better after the first refresh pass.\n" ..
    "|cff4adf6b3. COMPLETE - MEDIUM RISK|r - Profile refresh is split by the active Overview, Roles & Rights, Image, Rolls, Points, Backups, or Transfer subsection. Release Preflight passes 1498 / 1498 and the revised page behavior is confirmed improved.\n" ..
    "|cff4adf6b4. COMPLETE - LOW RISK|r - Main-page, Profile-subsection, Profile-layout, and full-theme timing counters passed 1498 / 1498 Preflight. The repeat report populated Loot List at 24.0ms and Loot Presets at 29.3ms, isolating Profile Overview at 84.6ms while layout and theme remained small.\n" ..
    "|cff4adf6b5. COMPLETE - MEDIUM RISK|r - The developer-only Profile Size display now uses a profile-identity and revision keyed cache. The verified report reduced Profile from roughly 90ms average to 31.9ms, Rolls to 30.4ms, and Points to 29.5ms; functional data remains uncached.\n" ..
    "|cff4adf6b6. COMPLETE - LOW RISK|r - Huge-profile phase timings isolated the pause: Test Raid Items spent 331.6ms of 381.4ms in duplicate Editor refresh work, and local choices spent about 292.1ms of 297.0ms in board/Editor refresh. Networking, persistence, popup rendering, and choice recording were not the bottleneck.\n" ..
    "|cff4adf6b7. COMPLETE - MEDIUM RISK|r - Preflight passes 1498 / 1498. Test Raid Items dropped from 381.4ms to 49.7ms, and local choices dropped from 297.0ms average to 14.2ms while retaining the board, summary, popup, persistence, dispatch, receipt, retry, and combat-defer paths.\n" ..
    "|cff4adf6b8. CLOSED WITHOUT CODE - VERY LOW RISK|r - Sync Management remains an intentionally detailed opt-in diagnostic view. Because transport continues normally after navigating away, no display-rate preference is added; users can leave the page to remove its presentation cost.\n" ..
    "|cff4adf6b9. COMPLETE - VERY LOW RISK|r - Added read-only Current Session phase diagnostics after confirming hidden Session History and Sync Management builders are already skipped.\n" ..
    "|cff4adf6b10. COMPLETE - LOW RISK|r - Removed full repair-candidate reconstruction from normal Current Session refreshes, then moved the existing authoritative host/self P1/P2 decisions ahead of their diagnostic fallback. Preflight passes 1498 / 1498 and active-session verification reduced Current Session render from about 492ms to 0.3ms average. Replicated remote-member readiness and archive receipt checksums remain authoritative; full divergence and repair metadata still builds in Sync Management, and developer synthetic rehearsals retain the prior path.\n" ..
    "|cffffcc0011. TEST - MEDIUM RISK|r - Validate Complete Shared Profile plus scoped Loot List, item-history, session-history, and Fun Awards export/import between two clients. Verify identity, recovery backup, dependencies, and exact checksums.\n" ..
    "|cff4adf6b12. TWO-CLIENT PRE-RAID PASS - LOW RISK|r - Small/large prepare, first join, unchanged rejoin, P1/P2/P3, Test Raid Items, precise leaf pull, and checksum/queue convergence passed. Genuine loot, combat, and multi-client voting remain raid gates.\n" ..
    "|cffffcc0013. SCALE GATE - MEDIUM RISK|r - Progress through 10 and 20 players while measuring per-target progress, archive fairness, delayed receipts, retries, queue depth, and host frame time. Twenty players must operate with clear headroom rather than merely avoid failure.\n" ..
    "|cffffcc0014. RELEASE GATE - HIGH EVIDENCE NEED|r - Run the 30-player design-target burst test. Gate: no lost roll window or choice, bounded delayed delivery, correct assignments, P1 priority preserved, fair per-target service, controlled host frame time, and P3 resumes after live traffic.\n\n" ..
    "|cffa87ae8Progress: 11 / 14 Workflow 8 gates completed (78.6%); Profile Transfer and raid-scale verification remain|r"
  )

  section.order = content:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  section.order:SetJustifyH("LEFT")
  section.order:SetWordWrap(true)
  section.order:SetText(
    "|cffa87ae8Execution order|r\n" ..
    "Steps 1-10 and the standard two-client regression in step 12 are complete. Next validate Complete Shared Profile plus scoped Loot List, item-history, session-history, and Fun Awards export/import between two clients, then advance through 10 and 20 to the required 30-player design target."
  )

  section.Layout = function(self)
    local width = math.max(620, (self.frame:GetWidth() or 0) - 36)
    local y = -18
    for _, fontString in ipairs({ self.intro, self.baseline, self.readiness, self.findings, self.actions, self.order }) do
      fontString:ClearAllPoints()
      fontString:SetPoint("TOPLEFT", 18, y)
      fontString:SetWidth(width)
      y = y - ((fontString.GetStringHeight and fontString:GetStringHeight()) or 0) - 18
    end
    local contentHeight = math.max(400, -y + 90)
    self.frame:SetHeight(contentHeight)
    if self.scroll and self.scroll.UpdateScrollChildRect then
      self.scroll:UpdateScrollChildRect()
    end
    return contentHeight
  end

  scroll:Hide()
  return section
end

local function CreateTutorialSyncWorkflow10Section(parent, createSection)
  local scroll, content, header, cards = createSection(parent, nil, {})
  local section = {
    scroll = scroll,
    frame = content,
    header = header,
    cards = cards,
  }

  section.intro = content:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
  section.intro:SetJustifyH("LEFT")
  section.intro:SetText("Sync Workflow 10 - Receipt Truth, Returning Members, and Live Performance")

  section.evidence = content:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  section.evidence:SetJustifyH("LEFT")
  section.evidence:SetWordWrap(true)
  section.evidence:SetText(
    "|cff4adf6bCurrent status - 11 August 2026|r\n" ..
    "Implementation corrections: 23 / 23 complete (100%); the five non-code safety/test/scale tasks remain gates. Release Preflight: 1658 / 1658. The standard matching-build two-client pre-raid workflow now passes first join, unchanged rejoin, P1/P2/P3 convergence, Test Raid Items, precise leaf preview/pull, and idle queue convergence. Eight of the nine focused UI/live validation gates below are complete (88.9%); only an equal-build Refresh Readiness check immediately after a completed repair remains. Auto-Pass OFF/ON, full Profile Transfer, genuine raid loot/combat, and 10/20/30-player scale remain separate live gates.\n\n" ..
    "|cffa87ae8Historical test evidence - 6-9 August 2026|r\n" ..
    "A large profile completed its transfer and the host and member found no visible Profile, Loot List, item-history, session-history, or Fun Awards mismatch. Despite that, the host still displayed P3 Archive and Loot List as Syncing. This strongly indicates that payload convergence and visible receipt/checkpoint state can disagree. P3 must not request another full payload merely because an acknowledgement or status proof is stale.\n\n" ..
    "The member initially displayed the host Auto-Pass state as ON while the host was OFF. Toggling the host setting ON and OFF corrected it. The join confirmation now carries the host's authoritative Auto-Pass value and applies it while the joined session is created; the existing targeted Auto-Pass notice remains as a repair path. OFF and ON still require the postponed two-client verification.\n\n" ..
    "A broad Push Repair during an active session sent Session Data, then waited for a receiver ACK and entered timeout recovery. P2 temporarily became not ready although the visible data still matched. A scoped Targeted Host Override for character details, Loot List, and item history transferred successfully and eventually restored the readiness display, but host receipt closure lagged behind the member's completed state.\n\n" ..
    "The Current Session presentation now treats canonical checksum alignment and transfer delivery as separate facts. A checksum-aligned P2 or P3 keeps its green Ready checkmark even while a receipt, recovery, combat apply, or newer delivery remains open; the row text and tooltip report that secondary delivery condition without changing the data result. Queue admission, ACK correlation, timeout recovery, and checksums are unchanged. Local Preflight coverage was added; two-client confirmation is postponed until another member is available.\n\n" ..
    "After closing and reopening the lobby with the same member, the old checkmarks were correctly not trusted as proof for the new session. However, the member then remained Bootstrap Pending and the host recorded one inbound protocol handler failure for JOIN. JOIN acknowledgement and durable local roster admission are now committed before profile comparison, and optional returning-member recovery is contained so it cannot invalidate an accepted JOIN. Sync Management now prints the bounded handler error detail if a core handler still fails. Live reproduction is postponed until another member is available. Canonical Current Session checksum fixtures now clone their input independently of production copy helpers, receive the production checksum-source builder through the internal API registry, and distinguish a false assertion from an actual fixture exception. This is validation wiring only and does not change the checksum contract or transport.\n\n" ..
    "A later role promotion produced a genuine Permissions divergence. Broad force-push attempts did not immediately restore P1 and repeated precise requests timed out, but a later precise response exposed Member Roles as the differing leaf. Previewing that leaf and applying Push Host Repair restored P1 and P2. This confirms that some question marks represent real bootstrap-critical permission differences, while also showing that precise proof delivery can be intermittent and should not require a broad force push to become available.\n\n" ..
    "Returning-member archive validation is now explicitly bound to the fresh session ID, join-proof token, profile name, host profile identity, member identity, and the member's persisted replica identity. While exact Loot Data or Session Data proof is in flight, the host republishes the existing MEMBER status matrix with a blue Validating state instead of an ambiguous question mark. Additional proof scopes merged into the same transaction are republished immediately. Completion or fail-closed cleanup republishes the authoritative checksum-derived state. This reuses the existing MEMBER message and adds no profile payload, queue lane, retry, or acknowledgement pressure.\n\n" ..
    "8 August two-client evidence completed 5031 of 5031 outbound attempts with zero throttles, zero failures, and an empty final queue. Profile, item, session, and award content visibly converged, but Loot List and P2/P3 presentation could remain pending. The cause was local state derivation: P2 still accepted a broad Loot-domain candidate, while existing replicas did not always send a delayed exact proof after an authoritative apply. P2 is now strictly Current Session State, archive scopes remain independent, and every successful authoritative apply schedules one coalesced delayed proof so the existing host MEMBER matrix can republish the result to every client.\n\n" ..
    "The same test exposed two unrelated local UI/runtime defects. Profile Overview role rows were populated from an unordered permissions table, so selecting or refreshing could reshuffle members; Council and Raider rows are now sorted deterministically. A joined member could also retain default session roll options even though the synchronized Profile page showed the host template. Once shared-profile bootstrap is ready, the roll runtime now resolves options from the synchronized profile first and uses session-cached options only as a fallback. Both changes are local and require a two-client confirmation.\n\n" ..
    "An accepted exact member proof can arrive after a transfer ACK has already advanced the host's predicted row. In that case the data is correct and the host is green, but a change-only publisher had no reason to send another compact MEMBER row to the other clients. Accepted exact PSTATE proof now forces one immediate compact status-row publication. It does not resend profile data and is still protected by the existing inbound state gate, so unchanged duplicate proofs remain ignored.\n\n" ..
    "Closing a session can legitimately produce short member-to-host traffic: STOP acknowledgements plus compact profile-state proof allow the host to finalize the session record and publish its new Session History checksum. This is not a full member profile upload unless PSYNC_CHUNK traffic is also visible.\n\n" ..
    "Performance evidence at chunk size 190 first showed Test Raid Items at 315.0ms, with 251.5ms in dispatch preparation. The follow-up subphase report isolated the issue decisively: 538.1ms total, 442.1ms dispatch preparation, and 427.5ms in local board restore/persist/full-Editor refresh. Serialization, recipient selection, queue admission, target planning, receipt bookkeeping, and initial enqueue together remained small. The test path now reuses its already-persisted and already-rendered local board only when session ID, profile name, board ID, board revision, item count, and ordered item keys exactly match the outgoing snapshot. Any mismatch falls back to the original restore/persist/refresh path. Normal board resends, recipient processing, queue admission, delivery, acknowledgements, and retries are unchanged. Local roll choices averaged 12.9ms and were smooth after the initial burst. Chunk size 190 also raised measured host CPU to roughly 30%, while chunk size 80 previously stayed near 7-8% during steady P3 transfer. Chunk size 80 remains the safer 30-player default. Live remeasurement is pending.\n\n" ..
    "Session close now creates a nonblocking finalization transaction before local runtime cleanup. The host sends one compact tokenized STOP to the group, accepts per-member STOP_ACK receipts after the active session is gone, retries only missing members by whisper twice, and then defers unresolved final history to normal P3 repair. Duplicate STOP packets are idempotent and return ALREADY_APPLIED instead of applying the final record twice.\n\n" ..
    "9 August repeat-lobby evidence showed that the synchronized profile and roll template remained usable, but P2, Item History, and Session History checkmarks could change after each new lobby or reload even when precise comparison found no differing leaves. The host's compact MEMBER status matrix was not bound to the lobby that produced it, so presentation evidence from an earlier session could be reused by a later session. Status matrices are now accepted only for their exact active session ID; archive readiness prefers exact host/member checksums and otherwise remains Pending instead of trusting stale presentation state.\n\n" ..
    "The same test reproduced the host Auto-Pass OFF state appearing as ON before the first explicit Auto-Pass update. Missing local Auto-Pass evidence now defaults to OFF in Current Session presentation. The authoritative JACK/APNOTICE paths remain unchanged and still replace the fallback as soon as evidence arrives. Targeted Host Override also now includes Current Session State, which resolves the active canonical P2 leaf at dispatch time instead of mapping P2 repair to Session History.\n\n" ..
    "Targeted Host Override could remain locked at Override In Progress when a selected scope was accepted but queued zero transfers, or when no observable transport record appeared. The recovery panel now tracks only scopes that actually queued transport, immediately unlocks zero-queue operations with an explicit diagnostic, treats exhausted ACK recovery as terminal, and expires a missing transport observation after a bounded window. These changes affect recovery-panel state only; transfer payloads, queue lanes, acknowledgements, and retry budgets are unchanged. Release Preflight and a same-build two-client repeat-lobby test are pending for this change set."
  )

  section.assessment = content:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  section.assessment:SetJustifyH("LEFT")
  section.assessment:SetWordWrap(true)
  section.assessment:SetText(
    "|cffa87ae8Historical engineering-confidence assessment|r\n" ..
    "The percentages below record the confidence estimates made while these defects were still open; they are retained as diagnosis history, not today's pass rates. Today's objective figures are 100% implementation, 1658 / 1658 Preflight, 100% of the executed standard two-client pre-raid scenario, and 88.9% of the nine focused UI/live gates.\n\n" ..
    "|cff4adf6b97%|r - Visible full-profile data convergence. The compared large profile appeared complete on both clients.\n" ..
    "|cff6fb8ff96%|r - P1 and returning-member JOIN acceptance. ACK and roster publication are durable, and archive proof is now bound to the fresh session and replica identities; two-client rejoin verification is pending.\n" ..
    "|cff6fb8ff95%|r - P2 data-state truthfulness. P2 now requires exact Current Session State proof, status matrices are bound to the active session ID, and Targeted Host Override can repair the exact active P2 leaf; two-client verification is pending.\n" ..
    "|cff4adf6b95%|r - P3 payload convergence. Loot and history content matched in the latest test.\n" ..
    "|cff6fb8ff93%|r - P3 data-state truthfulness. Exact alignment owns the checkmark, stale delivery remains secondary, and fresh returning-member proof is explicitly shown as Validating; two-client verification is pending.\n" ..
    "|cff6fb8ff92%|r - Initial host Auto-Pass publication. The required join confirmation carries OFF or ON, and missing presentation evidence now defaults to OFF rather than ON; live verification is pending.\n" ..
    "|cff6fb8ff95%|r - Targeted Host Override delivery. Current Session State is now an exact scope, only queued transports create tracked operations, and zero-queue, exhausted, or missing transport states unlock deterministically.\n" ..
    "|cff6fb8ff92%|r - Member roll-template resolution. Synchronized profile options now take precedence after bootstrap; two-client popup verification is pending.\n" ..
    "|cff6fb8ff90%|r - Session finalization delivery. Compact STOP receipts and missing-member-only bounded retries are implemented; two-client dropped-packet and 30-player evidence remain pending.\n" ..
    "|cffffcc0082%|r - Large-profile live-action responsiveness. The initial Test Raid Items pause is measurable and concentrated in dispatch preparation.\n" ..
    "|cff6fb8ff89%|r - Expected 20-player readiness. The architecture is designed for per-target delivery, but the current JOIN and receipt-state defects must be resolved.\n" ..
    "|cffffcc0083%|r - 30-player design-target readiness. Live-scale evidence is still required after correctness fixes."
  )

  section.confirmed = content:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  section.confirmed:SetJustifyH("LEFT")
  section.confirmed:SetWordWrap(true)
  section.confirmed:SetText(
    "|cffff5555100% clear errors that require a fix|r\n" ..
    "- COMPLETE: Current Session State now uses an explicit canonical checksum source: session/profile identity, host, PREPARING or ACTIVE status, roll options, and member join status. Client-local presentation, timestamps, Auto-Pass, recovery metadata, and remote revision evidence no longer decide P1 or P2 convergence.\n" ..
    "- COMPLETE: Existing replicas now coalesce a fresh local PSTATE publication after join, roster, and session-start hydration. Host matrix rows still preserve the member's last reported checksum as valid remote evidence.\n" ..
    "- COMPLETE LOCALLY, LIVE TEST PENDING: Visible diagnostics now call PREPARING or ACTIVE canonical data Current Session State, completed records Session History, and queued/sending/receipt work Delivery. A receipt wait no longer reads like a historical-data mismatch.\n" ..
    "- IMPLEMENTED, LIVE TEST PENDING: Session close now retains a compact finalization tracker after local cleanup, requires per-member receipts, retries only missing members within a fixed budget, and leaves exhausted targets for later P3 archive repair.\n" ..
    "- IMPLEMENTED, LIVE TEST PENDING: The required JACK confirmation now includes the host's authoritative Auto-Pass value. New clients apply it during session creation, while APNOTICE remains a secondary repair path.\n" ..
    "- IMPLEMENTED, LIVE TEST PENDING: P2/P3 checksum alignment now remains Ready while delayed receipts, recovery, combat apply, and delivery activity are reported separately in row text and tooltips.\n" ..
    "- IMPLEMENTED, LIVE TEST PENDING: Precise-difference retries now retain their request token through queue coalescing. A stale queued or recently sent request can no longer suppress the replacement token, and unchanged checksum or patch-manifest data is resent when the request token changes.\n" ..
    "- IMPLEMENTED, LIVE TEST PENDING: JOIN acceptance now publishes the ACK and commits durable local roster admission before optional comparison/recovery. Returning-member recovery failures are contained and logged with their exact bounded Lua detail instead of invalidating the accepted JOIN.\n" ..
    "- IMPLEMENTED, LIVE TEST PENDING: Returning-member archive proof is scoped to the fresh session, token, profile, member, and replica identity. The host publishes Validating through the existing MEMBER matrix while exact proof is in flight, then republishes the checksum-derived result when validation ends."
    .. "\n- IMPLEMENTED, LIVE TEST PENDING: P2 Current Session State no longer consumes broad Loot-domain candidate state. Loot List, Item History, Session History, and Fun Awards remain independent P3 evidence.\n"
    .. "- IMPLEMENTED, LIVE TEST PENDING: Existing replicas now schedule a delayed exact PSTATE proof after every authoritative apply, including Targeted Host Override, so the existing host status matrix can update member views without waiting for session start.\n"
    .. "- IMPLEMENTED, LIVE TEST PENDING: Profile member rows use deterministic character sorting and synchronized profile roll options override stale/default session caches after bootstrap.\n"
    .. "- IMPLEMENTED, LIVE TEST PENDING: The host republishes one compact status row after accepting exact member proof even when its predicted state was already identical, preventing host-green/member-question-mark divergence.\n"
    .. "- IMPLEMENTED, PREFLIGHT AND LIVE TEST PENDING: Compact member readiness matrices are bound to their exact session ID, so closing and reopening a lobby cannot reuse an earlier lobby's P2/P3 presentation evidence.\n"
    .. "- IMPLEMENTED, PREFLIGHT AND LIVE TEST PENDING: Targeted Host Override exposes exact Current Session State for P2 and no longer remains locked when no transfer was queued, recovery was exhausted, or a transport record never appeared.\n"
    .. "- IMPLEMENTED, PREFLIGHT AND LIVE TEST PENDING: Missing host Auto-Pass presentation evidence defaults to OFF until authoritative JACK or APNOTICE state arrives."
  )

  section.unclear = content:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  section.unclear:SetJustifyH("LEFT")
  section.unclear:SetWordWrap(true)
  section.unclear:SetText(
    "|cffffcc00Still unclear because test results differed|r\n" ..
    "- The rejoin Current Session State mismatch may be a real active-session difference, an early checksum calculated before roster hydration, or a stale reported checksum. Identical Session History does not distinguish these cases.\n" ..
    "- There is no evidence that STOP was actually lost in the reported test. The missing STOP acknowledgement is a proven design gap, but it is not yet proven as the cause of this rejoin mismatch.\n" ..
    "- The original Lua line and state that caused the returning JOIN handler failure were not captured. New diagnostics will expose the exact bounded error detail if it recurs; live trigger verification remains postponed.\n" ..
    "- It is not yet clear why a broad force push sometimes precedes a successful precise reply. Possible causes include request-token replacement, manifest readiness, combat deferral, or stale request state.\n" ..
    "- The P2 ACK timeout may be receipt correlation, delayed member processing, combat deferral, or a superseded generation. Matching visible data alone cannot identify which path occurred.\n" ..
    "- The role promotion definitely changed Permissions. It is not proven that it also caused the separate Current Session State mismatch.\n" ..
    "- After session-bound status evidence is installed, any remaining fresh-lobby question mark may still be a delayed exact proof, cache-settle delay, or a genuine active-session difference. The latest two-client behavior is not yet available for this build.\n" ..
    "- Two-client recovery now works often enough to repair the profile, but reliable 10-, 20-, and 30-player close/rejoin behavior has not been demonstrated."
  )

  section.interpretation = content:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  section.interpretation:SetJustifyH("LEFT")
  section.interpretation:SetWordWrap(true)
  section.interpretation:SetText(
    "|cffa87ae8How to interpret the current states|r\n" ..
    "1. Data aligned means the compared canonical payload checksums match.\n" ..
    "2. Receipt pending means a sender has not closed the current acknowledged transfer transaction. It does not prove that the receiver lacks the payload.\n" ..
    "3. Revalidating means a returning member must prove the current profile identity, revision, and domain checksums for the new session. Old-session receipts must not be reused.\n" ..
    "4. Recovery deferred means no repair should run until the required proof or current higher-priority work completes. It must not remain indefinitely after exact proof arrives.\n" ..
    "5. A broad Push Repair can temporarily replace a Ready state with receipt-pending recovery. Use precise repair or Targeted Host Override when the affected scope is known.\n" ..
    "6. Sync Management now has a local display refresh selector plus Refresh Now. Hosts default to 1 second and members default to Manual unless the user saves another Manual/1-5 second preference. This changes presentation only, not queue speed, receipts, transport, snapshot creation, or manifest work."
  )

  section.tasks = content:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  section.tasks:SetJustifyH("LEFT")
  section.tasks:SetWordWrap(true)
  section.tasks:SetText(
    "|cffa87ae8Workflow 10 task list - 30-player design target|r\n" ..
    "|cff6fb8ff1. IMPLEMENTED, LIVE TEST PENDING - MEDIUM RISK|r - ACK and durable local roster admission now precede optional first-join comparison work, and optional returning-member recovery errors cannot invalidate an accepted JOIN. Sync Management exposes the bounded core handler error detail if admission still fails.\n" ..
    "|cff4adf6b2. COMPLETE LOCALLY, LIVE TEST PENDING - MEDIUM RISK|r - Audited the revisit handshake and bound exact archive proof to the fresh session ID, token, profile identity, member identity, replica identity, and requested domains. The host now publishes Validating through the existing MEMBER matrix at proof start and republishes the authoritative checksum result at completion or fail-closed cleanup. Preflight covers stale-context rejection and READY/REVIEW/PENDING precedence.\n" ..
    "|cff6fb8ff3. IMPLEMENTED, LIVE TEST PENDING - LOW RISK|r - JACK now publishes the host's current Auto-Pass value and the joiner applies it during session creation. Legacy JACK remains accepted and APNOTICE remains the repair path. Verify OFF and ON without requiring a toggle.\n" ..
    "|cff6fb8ff4. IMPLEMENTED, LIVE TEST PENDING - LOW RISK|r - Separated checksum alignment from transfer receipt presentation in P2/P3. Exact alignment keeps the Ready checkmark while receipt pending, timeout, recovery, combat apply, or active delivery remains visible as secondary status. Transport behavior is unchanged.\n" ..
    "|cff4adf6b5. COMPLETE LOCALLY, LIVE TEST PENDING - MEDIUM RISK|r - Hardened Push Repair and Targeted Host Override ACK correlation. The exact transfer ID anchors the transaction and its stored scope; target, session ID, profile name, generation, profile identity, and transaction chunk count are verified before a receipt can close it. Legacy empty metadata remains accepted, and a valid late ACK closes only its matching current or superseded transaction idempotently.\n" ..
    "|cff4adf6b6. COMPLETE - MEDIUM RISK|r - Replaced the Session Data checksum source with canonical Current Session State fields. Presentation and timing metadata are excluded; authoritative session status, roll options, and roster membership remain convergence-critical.\n" ..
    "|cff4adf6b7. OPTIMIZED LOCALLY, REMEASURE PENDING - LOW RISK|r - The measured 427.5ms Test Raid Items local restore/persist/full-Editor refresh is skipped only when the outgoing snapshot exactly matches the already-persisted and rendered local board. Any identity, revision, count, or item-order mismatch uses the original safe path. Normal resends and all transport, receiver, acknowledgement, and retry behavior are unchanged.\n" ..
    "|cff4adf6b8. COMPLETE LOCALLY, LIVE UI TEST PENDING - VERY LOW RISK|r - Added a local display-only Sync Management refresh rate with Manual and 1-5 second modes plus Refresh Now. Hosts default to 1 second; members default to Manual. The scheduler runs only while Sync Management is selected and does not alter transport, queue speed, diagnostics collection, checksums, or receipts.\n" ..
    "|cff6fb8ff9. SAFETY - LOW RISK|r - Keep chunk size 80 as the release default and repeat 190 only as a comparison. Verify live priority and host frame time at 10, 20, and 30 targets.\n" ..
    "|cff6fb8ff10. TEST - MEDIUM RISK|r - Complete two-client full-profile and scoped Profile Transfer import/export tests.\n" ..
    "|cff6fb8ff11. TEST - MEDIUM RISK|r - Repeat small- and large-profile sessions after tasks 1-6: first join, returning join, Auto-Pass OFF/ON, P1/P2/P3, Push Repair, Targeted Host Override, Test Raid Items, Test Live Delivery, choices, votes, assignment, combat defer, and reconnect.\n" ..
    "|cff4adf6b12. COMPLETE LOCALLY, LIVE TEST PENDING - MEDIUM RISK|r - Precise checksum and patch-manifest requests now publish their token into transport metadata. Queue/recent-send coalescing is token-scoped, and receiver response deduplication is also token-scoped, so a retry cannot wait on a reply for the superseded token. Preflight covers token retention, matching-token coalescing, replacement-token delivery, and response resend behavior.\n" ..
    "|cffff555513. RELEASE GATE - HIGH EVIDENCE NEED|r - Run 10-, 20-, and 30-player tests. Require no lost popup or choice, bounded ACK recovery, truthful per-target readiness, live traffic priority, fair P3 resumption, and controlled host frame time.\n" ..
    "|cff4adf6b14. COMPLETE - LOW RISK|r - Added a coalesced fresh current-session publication after join, roster, and start hydration. Remote host rows intentionally retain the member-reported checksum until that fresh report arrives.\n" ..
    "|cff4adf6b15. COMPLETE LOCALLY, LIVE TEST PENDING - MEDIUM RISK|r - Added nonblocking session finalization without reopening the lobby. One tokenized group STOP is followed by per-member receipts and at most two missing-member whisper retries; duplicate STOP is idempotent and exhausted targets defer to normal P3 archive repair.\n" ..
    "|cff4adf6b16. COMPLETE LOCALLY, LIVE TEST PENDING - LOW RISK|r - Separated canonical Current Session State, completed Session History, P3 archive data, and transfer/receipt status across Current Session, Sync Detail, and the joined-copy matrix. Existing readiness, checksums, receipts, retries, and transport behavior are unchanged.\n" ..
    "|cff6fb8ff17. TEST - MEDIUM RISK|r - Run a controlled close/rejoin matrix with no changes, rights-only changes, Auto-Pass OFF/ON, combat deferral, one intentionally dropped STOP, and one delayed precise reply. Compare canonical current-session checksums separately from Session History.\n" ..
    "|cff4adf6b18. COMPLETE LOCALLY, LIVE TEST PENDING - LOW RISK|r - Permission deltas still use their normal applied ACK, then schedule one coalesced canonical PSTATE proof from each receiver. Rapid role or rights edits collapse into one proof after 0.35 seconds. Settings, Loot Data, Session Data, snapshot transport, and live priority are unchanged."
    .. "\n|cff4adf6b19. COMPLETE LOCALLY, LIVE TEST PENDING - VERY LOW RISK|r - Sort Profile Overview Council and Raider rows by normalized character name so selection and sync refreshes cannot reshuffle an unchanged roster.\n"
    .. "|cff4adf6b20. COMPLETE LOCALLY, LIVE TEST PENDING - MEDIUM RISK|r - Make P2 consume only the canonical Current Session State checksum. Broad Loot or archive matches can no longer mark P2 Ready, and missing current-session proof remains non-ready.\n"
    .. "|cff4adf6b21. COMPLETE LOCALLY, LIVE TEST PENDING - LOW RISK|r - Schedule one coalesced delayed profile-state proof after every authoritative apply, not only first-time profile creation. This closes Targeted Host Override and repair presentation after checksum caches settle without adding payload snapshots.\n"
    .. "|cff4adf6b22. COMPLETE LOCALLY, LIVE TEST PENDING - MEDIUM RISK|r - Resolve member roll options from the synchronized profile after bootstrap, with the session cache and built-in defaults retained as compatibility fallbacks.\n"
    .. "|cff4adf6b23. COMPLETE LOCALLY, LIVE TEST PENDING - LOW RISK|r - Force one compact MEMBER status-row publication after the host accepts exact PSTATE proof. This closes host/member readiness presentation after repair without resending profile payloads.\n"
    .. "|cff4adf6b24. COMPLETE LOCALLY, PREFLIGHT PASS, LIVE UI TEST PENDING - VERY LOW RISK|r - Keep the Current Session member identity column capped at 220 pixels, expose P3 at compact widths, reserve stable space for Loot List, Items, Sessions, Awards, and Auto-Pass from practical split widths onward, and anchor blue transfer progress across the archive cells for host and member views. The production breakpoint calculator is covered by narrow, compact, split, and popout bounds checks. Release Preflight passed 1580/1580 on 8 August 2026. This changes presentation and validation only; readiness derivation, checksums, transport, acknowledgements, and retries are untouched.\n"
    .. "|cff6fb8ff25. IMPLEMENTED, PREFLIGHT AND LIVE TEST PENDING - MEDIUM RISK|r - Bind compact MEMBER status matrices to the exact active session, prefer exact archive checksums over presentation hints, and expose Current Session State as the exact Targeted Host Override scope for P2. This changes readiness evidence selection but not checksum construction or payload serialization.\n"
    .. "|cff6fb8ff26. IMPLEMENTED, PREFLIGHT AND LIVE TEST PENDING - LOW RISK|r - Track only override scopes that queued at least one transfer. Zero-queue dispatches unlock immediately, accepted dispatches remain locked while queue telemetry catches up, exhausted recovery is terminal, and a transport record that never appears expires after a bounded observation window. Transfer and retry behavior are unchanged."
    .. "\n|cff6fb8ff27. IMPLEMENTED, PREFLIGHT AND LIVE TEST PENDING - LOW RISK|r - Add a host-only Refresh Readiness action to Current Session. It republishes the current host matrix, requests fresh compact Settings, Permissions, Loot, and Session proofs from every joined member, and republishes each recalculated row when proof completes. A five-second session cooldown bounds 30-player bursts. No profile, Loot List, item-history, session-history, or Fun Awards payload is resent."
    .. "\n|cff6fb8ff28. IMPLEMENTED, PREFLIGHT AND LIVE TEST PENDING - MEDIUM RISK|r - Accept readiness only from an immutable exact-lobby PSTATE proof, publish compact readiness matrices with a monotonically increasing generation, and reject stale, conflicting equal-generation, or legacy generation-zero rewrites. A new lobby begins as Verifying without exact proof, its session ID invalidates the Current Session display cache, and a bounded 80-entry transition trace records accepted state changes. No profile or history payload is added."
  )

  section.fixChecklist = content:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  section.fixChecklist:SetJustifyH("LEFT")
  section.fixChecklist:SetWordWrap(true)
  section.fixChecklist:SetText(
    "|cffa87ae8Latest defect-to-fix checklist|r\n" ..
    "Status key: this defect ledger preserves the status recorded when each repair landed. Its older LIVE PENDING labels are superseded by the current status above and the checked validation list below.\n\n" ..
    "[CODE COMPLETE | LIVE PENDING] Profile members reshuffle after selection or sync refresh. Proper fix: sort Council and Raider character names deterministically before rendering; selection must never depend on Lua table iteration order.\n" ..
    "[CODE COMPLETE | LIVE PENDING] A Loot-only Targeted Host Override can make P2 appear Ready. Proper fix: derive P2 exclusively from the canonical Current Session State checksum. Loot List and all archive scopes remain independent P3 evidence.\n" ..
    "[CODE COMPLETE | LIVE PENDING] Host shows Ready after repair while the member still shows a question mark. Proper fix: every authoritative apply schedules delayed exact PSTATE proof, and the host force-publishes one compact MEMBER status row after accepting that proof. No profile snapshot is resent.\n" ..
    "[CODE COMPLETE | LIVE PENDING] Existing replicas do not close readiness after Targeted Host Override. Proper fix: publish exact post-apply checksums after caches settle instead of treating transfer completion alone as data convergence.\n" ..
    "[CODE COMPLETE | LIVE PENDING] Member receives the default WoW roll template while the synchronized Profile shows LidCouncil options. Proper fix: after shared-profile bootstrap, resolve roll options from the synchronized profile first; retain session-cached options and built-in defaults only as compatibility fallbacks.\n" ..
    "[CODE COMPLETE | PREFLIGHT + LIVE PENDING] Returning members can inherit green readiness or regress from stale compact status evidence. Proper fix: preserve an immutable accepted PSTATE proof for the exact active session, begin without that proof as Verifying, publish a monotonic matrix generation, and reject older, conflicting equal-generation, or legacy rewrites. Include the active session ID in the display signature so reopening always rerenders the rows.\n" ..
    "[CODE COMPLETE | LIVE PENDING] Session close appears to trigger member upload. Proper fix: close through compact tokenized STOP/STOP_ACK finalization and one compact state proof; unresolved final Session History moves to normal P3 recovery. Treat PSYNC_CHUNK traffic as a separate full-snapshot issue if it is observed.\n" ..
    "[CODE COMPLETE | LIVE PENDING] P2/P3 checkmarks regress while only an acknowledgement is delayed. Proper fix: checksum alignment owns the Ready checkmark; receipt, recovery, combat apply, and active delivery are secondary status text and must not rewrite data truth.\n" ..
    "[CODE COMPLETE | LIVE PENDING] Precise Difference retry waits on an obsolete request. Proper fix: carry request tokens through queue metadata, coalescing, receiver deduplication, and responses so replacement requests cannot be suppressed by an older token.\n\n" ..
    "[CODE COMPLETE | PREFLIGHT + LIVE PENDING] New lobbies reuse old P2/P3 checkmark evidence. Proper fix: bind every compact MEMBER status matrix to the session ID that produced it and ignore it for every other lobby.\n" ..
    "[CODE COMPLETE | PREFLIGHT + LIVE PENDING] P2 cannot be repaired directly from Targeted Host Override. Proper fix: add Current Session State and resolve the active canonical ssr:id:<session> leaf only when dispatch begins. Session History remains a separate P3 scope.\n" ..
    "[CODE COMPLETE | PREFLIGHT + LIVE PENDING] Targeted Host Override remains locked after a no-op or missing transport, or briefly permits a duplicate before queue telemetry appears. Proper fix: create operations only for scopes with queued transfers, keep accepted operations locked through the bounded observation window, and make zero-queue, exhausted, and unobserved transports terminal.\n" ..
    "[CODE COMPLETE | PREFLIGHT + LIVE PENDING] Host Auto-Pass OFF appears ON before initial evidence. Proper fix: use OFF as the missing-evidence display fallback; authoritative JACK/APNOTICE values still replace it.\n\n" ..
    "[CODE COMPLETE | PREFLIGHT + LIVE PENDING] Host and member readiness checkmarks remain stale after reload or manual repair. Proper fix: the host-only Refresh Readiness action requests fresh exact compact proof and republishes the checksum-derived matrix. It never copies visual checkmarks and never resends profile or history payloads.\n\n" ..
    "[CODE COMPLETE | LIVE UI PENDING] Current Session hides archive columns or transfer progress at some editor widths. Proper fix: cap the Raider identity column, reserve compact fixed minimums for every readiness/archive column, and render the existing transfer label across the archive group independently of host/member role. This is display-only.\n\n" ..
    "[CODE COMPLETE | PREFLIGHT PASS | LIVE UI PENDING] Responsive Current Session layout invariants validate the production breakpoints, 220-pixel identity cap, ordered archive columns, Auto-Pass boundary, and role-independent blue progress area. Release Preflight passed 1580/1580 on 8 August 2026.\n\n" ..
    "[CODE COMPLETE | PREFLIGHT PASS] The three P2 Preflight fixtures reported missing although runtime P2 logic was registered. Proper fix: resolve the Session Flow comparator lazily when validation runs because the developer harness is constructed earlier in addon initialization. This changes validation wiring only. Release Preflight passed 1571/1571 on 8 August 2026.\n\n" ..
    "|cffffcc00Required validation before more synchronization-state changes - 8 / 9 complete (88.9%)|r\n" ..
    "[x] Release Preflight passed 1658/1658 across seven suites on 11 August 2026.\n" ..
    "[x] Install and reload the identical build on host and member.\n" ..
    "[x] Rejoin without profile changes; P1 and P2 settle before session start.\n" ..
    "[x] Run a Loot-only targeted repair; only the selected Loot List/P3 evidence changes, never P2.\n" ..
    "[x] Require host and member readiness rows to agree before starting the session.\n" ..
    "[ ] Press Refresh Readiness as host after a reload and after one completed repair. Require compact proof traffic only, matching P1/P2/P3 rows on both clients, no profile/history snapshot transfer, and cooldown coalescing when pressed repeatedly.\n" ..
    "[x] Resize/observe Current Session at practical 1080p and 4K widths; archive columns and named blue progress remained understandable on both clients.\n" ..
    "[x] Run Test Raid Items; the synchronized profile roll template and later checksum state behave correctly on both clients.\n" ..
    "[x] Close and reopen the session; bounded compact state/proof traffic converges without an unbounded PSYNC_CHUNK or PSUB response loop."
  )

  section.order = content:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  section.order:SetJustifyH("LEFT")
  section.order:SetWordWrap(true)
  section.order:SetText(
    "|cffa87ae8Recommended execution order|r\n" ..
    "The current release baseline passes 1658/1658 and the standard two-client pre-raid scenario is green. Before changing synchronization state again, perform the one remaining equal-build check: complete one precise repair, press Refresh Readiness, and require both clients to converge through compact proof only. Then verify Auto-Pass OFF/ON and Profile Transfer independently; keep genuine raid events and 10/20/30-player scale last."
  )

  section.Layout = function(self)
    local width = math.max(620, (self.frame:GetWidth() or 0) - 36)
    local y = -18
    for _, fontString in ipairs({ self.intro, self.evidence, self.assessment, self.confirmed, self.unclear, self.interpretation, self.tasks, self.fixChecklist, self.order }) do
      fontString:ClearAllPoints()
      fontString:SetPoint("TOPLEFT", 18, y)
      fontString:SetWidth(width)
      y = y - ((fontString.GetStringHeight and fontString:GetStringHeight()) or 0) - 18
    end
    local contentHeight = math.max(400, -y + 90)
    self.frame:SetHeight(contentHeight)
    if self.scroll and self.scroll.UpdateScrollChildRect then
      self.scroll:UpdateScrollChildRect()
    end
    return contentHeight
  end

  scroll:Hide()
  return section
end

local function CreateTutorialSyncWorkflow11Section(parent, createSection)
  local scroll, content, header, cards = createSection(parent, nil, {})
  local section = {
    scroll = scroll,
    frame = content,
    header = header,
    cards = cards,
    blocks = {},
  }

  local function AddBlock(text, template)
    local block = content:CreateFontString(nil, "OVERLAY", template or "GameFontHighlightSmall")
    block:SetJustifyH("LEFT")
    block:SetWordWrap(true)
    block:SetText(text)
    section.blocks[#section.blocks + 1] = block
  end

  AddBlock("Sync Workflow 11 - Fresh Proof and Readiness Matrix Convergence", "GameFontHighlight")

  AddBlock(
    "|cff4adf6bCurrent status - 11 August 2026|r\n" ..
    "Proof-publication implementation: 9 / 9 complete (100%). Release Preflight: 1658 / 1658. Three consecutive matching-build two-client lobby phases now pass first proof, unchanged rejoin, P1/P2/P3 convergence, Test Raid Items, and idle queue closure. The historical defects below are resolved for the standard pre-raid path. First-render item-cache hydration, the equal-build Refresh Readiness-after-repair check, and 30-player evidence remain open."
  )

  AddBlock(
    "|cffa87ae8Two-client evidence - 9 August 2026|r\n" ..
    "Release Preflight passed 1591/1591 before the latest proof-publication change. A nearly empty profile transferred completely, the synchronized roll template worked, and Test Raid Items reached both clients in three consecutive sessions. The remaining question marks therefore reproduce without archive size, queue pressure, or roll-delivery failure.\n\n" ..
    "On the first lobby the host displayed every column Ready while the member displayed only P1, even after tab changes, /reload, and Refresh Readiness. On the second and third unchanged lobbies both clients repeatedly returned to P2, Item History, and Session History question marks. This proves that the transferred profile can be correct while the exact readiness evidence used by the matrix remains stale or unpublished.\n\n" ..
    "The root cause is now confirmed in code. Join and session-start sent an immediate exact PSTATE proof and scheduled another after 0.35 seconds, but the normal duplicate window suppressed identical proofs for one second. Archive apply used the same delayed path. Refresh Readiness requested PSUB leaf evidence, but the member answered with leaf checksums without following that response with the exact PSTATE proof that drives P2 and the four archive columns.\n\n" ..
    "On initial join, P1 and P2 appeared Ready before fresh synchronization was visibly complete. P2 then retracted briefly and returned after proof settled. This means cached or provisional evidence is still being presented as final readiness during the join transition. A new lobby must begin as Verifying unless fresh lobby-bound proof has already completed.\n\n" ..
    "After closing and reopening the same profile, P2, Item History, and later Session History returned to question marks. A scoped Item History override caused both Item History and P2 to become Ready, but later recovered delivery and another lobby changed those states again. This is evidence of proof-generation and publication coupling, not evidence that Item History belongs in P2. P2 remains Current Session State only.\n\n" ..
    "Refresh Readiness did not make the host and member matrices agree: the host could show Item History Ready while the member still showed a question mark. The action is sending proof requests, but the accepted proof and resulting compact MEMBER matrix are not yet guaranteed to converge on one session-bound generation on every client.\n\n" ..
    "The delayed ACK recovered and transmission continued. Transport retry behavior is working. Receipt state must remain secondary to checksum truth and must not invalidate an already-proven domain.\n\n" ..
    "The first Test Raid Items run briefly lacked item icons while the second run displayed them. Because delivery and the item rows were otherwise correct, this is most consistent with asynchronous WoW item-cache hydration. It is a separate UI refresh defect, not a profile synchronization failure."
  )

  AddBlock(
    "|cffa87ae8Historical engineering assessment before proof-publication fixes|r\n" ..
    "These values are engineering confidence, not measured success rates.\n\n" ..
    "|cff4adf6b98%|r - Full profile payload convergence. The small profile and roll template matched.\n" ..
    "|cff4adf6b97%|r - Live roll delivery and synchronized template selection.\n" ..
    "|cff4adf6b94%|r - Transport ACK recovery. The delayed receipt recovered without a failed transfer.\n" ..
    "|cff6fb8ff93%|r - P1 roll-ready truth after proof settles.\n" ..
    "|cffffcc0084%|r - P2 Current Session State truth during join and lobby reopen. Forced exact-proof publication is implemented; live validation remains.\n" ..
    "|cffffcc0085%|r - P3 archive readiness truth across repeated lobbies. Archive apply now forces one exact proof; live validation remains.\n" ..
    "|cffffcc0083%|r - Host/member readiness-matrix convergence after refresh or repair. Leaf responses now trigger exact proof publication; live validation remains.\n" ..
    "|cffffcc0083%|r - Refresh Readiness as a reliable cross-client convergence action. The request now closes with exact member proof instead of leaf evidence alone.\n" ..
    "|cffffcc0075%|r - First-render item icon hydration.\n" ..
    "|cffffcc0087%|r - 30-player readiness architecture. The new state is bounded and event-driven; live scale remains gated by proof-state correctness."
  )

  AddBlock(
    "|cffff5555Historical confirmed defects - resolved for the standard two-client path|r\n" ..
    "1. Join can display P1/P2 as final Ready before fresh lobby-scoped proof closes.\n" ..
    "2. P2 and archive columns can regress after an ACK, repair, reload, or new lobby even when payload data remains correct.\n" ..
    "3. Host and member can display different readiness results after Refresh Readiness.\n" ..
    "4. An unrelated scoped override can coincide with P2 closure, proving that proof/apply publication timing is still coupled across scopes.\n" ..
    "5. A recovered receipt can remain visible as a data-readiness regression instead of secondary delivery history.\n" ..
    "6. Initial Test Raid Items icons do not always refresh after item data enters the local cache.\n" ..
    "7. A newly joined member can still display the host Auto-Pass state as ON while the host is OFF until the host toggles it. This remains low priority because the local setting itself operates correctly."
  )

  AddBlock(
    "|cffffcc00Questions recorded at that stage|r\n" ..
    "- Closing a session legitimately creates a final Session History record. A Session History checksum change after close is not automatically a bug.\n" ..
    "- Test Raid Items may create or update item-history records. The canonical mutation path must be audited before treating every Item History change as false.\n" ..
    "- The third-lobby question marks may come from missing fresh proof, stale ACK correlation, an old matrix generation, or a genuine new session/archive leaf. Current telemetry does not identify which transition won.\n" ..
    "- The Item History override did not prove that P2 depends on Item History. It only showed that another completed apply/proof cycle could close P2 at the same time."
  )

  AddBlock(
    "|cffa87ae8Implementation checklist|r\n" ..
    "[CODE + PREFLIGHT + LIVE PASS] Readiness-transition history is bounded and exposes member, matrix, source, reason, session, and generation.\n" ..
    "[CODE + PREFLIGHT + LIVE PASS] Join and rejoin begin as Verifying and do not borrow green state from an older lobby.\n" ..
    "[CODE + PREFLIGHT + LIVE PASS] Checksum truth remains independent from receipt/recovery history.\n" ..
    "[CODE + PREFLIGHT + LIVE PASS] Compact matrices are bound to one session and monotonic generation; stale rewrites are rejected.\n" ..
    "[CODE + PREFLIGHT + LIVE PASS] Join, start, archive apply, and precise-proof completion publish coalesced exact PSTATE evidence.\n" ..
    "[CODE + LIVE PASS] Archive readiness survives unchanged rejoin only through exact canonical checksum proof.\n" ..
    "[CODE + LIVE PASS] Session close and Test Raid Items canonical mutations were diagnosed and normalized; TESTSIM history no longer creates a persistent archive warning.\n" ..
    "[CODE + LIVE PASS] Targeted precise member-leaf repair preserved scope isolation and converged the host copy.\n" ..
    "[UI TODO] Refresh first-render item icons asynchronously if a genuine raid item still appears uncached; do not resend the board.\n" ..
    "[PREFLIGHT PASS] Generation, proof isolation, duplicate suppression, forced proof, recovery, and matrix fixtures pass in the 1658-check suite.\n" ..
    "[TWO-CLIENT PASS | SCALE PENDING] Three consecutive lobby phases passed; the 30-target live-scale gate has not been run."
  )

  AddBlock(
    "|cffa87ae8Next validation gate|r\n" ..
    "Standard two-client convergence is complete. After one equal-build precise repair, press Refresh Readiness once and require both clients to display the same values using compact proof only. Then retain the first-render icon check for a genuine raid item and run the 30-target environment gate."
  )

  section.Layout = function(self)
    local width = math.max(620, (self.frame:GetWidth() or 0) - 36)
    local y = -18
    for _, block in ipairs(self.blocks) do
      block:ClearAllPoints()
      block:SetPoint("TOPLEFT", 18, y)
      block:SetWidth(width)
      y = y - ((block.GetStringHeight and block:GetStringHeight()) or 0) - 18
    end
    local contentHeight = math.max(400, -y + 90)
    self.frame:SetHeight(contentHeight)
    if self.scroll and self.scroll.UpdateScrollChildRect then
      self.scroll:UpdateScrollChildRect()
    end
    return contentHeight
  end

  scroll:Hide()
  return section
end

local function CreateTutorialSyncWorkflow12Section(parent, createSection)
  local scroll, content, header, cards = createSection(parent, nil, {})
  local section = {
    scroll = scroll,
    frame = content,
    header = header,
    cards = cards,
    blocks = {},
  }

  local function AddBlock(text, template)
    local block = content:CreateFontString(nil, "OVERLAY", template or "GameFontHighlightSmall")
    block:SetJustifyH("LEFT")
    block:SetWordWrap(true)
    block:SetText(text)
    section.blocks[#section.blocks + 1] = block
  end

  AddBlock("Sync Workflow 12 - Readiness Publication Contract", "GameFontHighlight")

  AddBlock(
    "|cff4adf6bCurrent status - 11 August 2026|r\n" ..
    "MEMBER field-contract correction: 4 / 4 complete (100%). Release Preflight: 1658 / 1658. Matching-build two-client first join, repeated lobby publication, P2/P3 rows, and Test Raid Items now pass. Auto-Pass OFF/ON publication, equal-build Refresh Readiness immediately after repair, and 30-target delivery remain focused follow-ups."
  )

  AddBlock(
    "|cffa87ae8Two-client evidence - 9 August 2026|r\n" ..
    "The first preparation lobby now behaves correctly on the host: every joined-member cell begins unknown and P1, P2, and each archive leaf become Ready as proof arrives. The profile, archive data, and Test Raid Items were functionally correct. This confirms that bootstrap, transfer, apply, and live-roll paths are not the source of the remaining display failure.\n\n" ..
    "The member still displayed only P1 while the host displayed the completed matrix. Refresh Readiness, tab changes, and /reload did not converge the member display. Reopened lobbies again showed P2, Item History, and Session History as unknown on the host even though the underlying profile remained aligned.\n\n" ..
    "The decisive monitor evidence is nine rejected MEMBER packets with comm_field_count_exceeds_limit while the queue was idle. The performance report is healthy and the profile is small, so CPU pressure, archive volume, and queue starvation are excluded from this failure."
  )

  AddBlock(
    "|cffff5555Confirmed root cause|r\n" ..
    "The complete MEMBER readiness publication contains 44 payload values. The common validator counts the opcode as an additional encoded field, producing 45 encoded fields. The MEMBER-specific contract allowed only 44 encoded fields even though its receiver intentionally reads through field 45. Valid readiness publications were therefore rejected before the receiver could process P2, archive leaves, the host Auto-Pass snapshot, or the matrix generation.\n\n" ..
    "The safe-packet fallback could not recover this error because it removes optional value content but preserves the same number of fields. Refresh Readiness recomputed correct state and then sent the same rejected MEMBER shape, which explains why repeated refreshes did not change the member table.\n\n" ..
    "The fix raises only MEMBER to 45 encoded fields. The generic protocol ceiling and every unrelated opcode remain unchanged. Boundary validation now accepts exactly 44 MEMBER payload values and rejects a 45th payload value."
  )

  AddBlock(
    "|cffa87ae8Historical engineering assessment before the MEMBER contract retest|r\n" ..
    "These values are engineering confidence, not measured success rates.\n\n" ..
    "|cff4adf6b99%|r - Profile payload and archive convergence. The clients' actual data matched.\n" ..
    "|cff4adf6b98%|r - P1 and live-roll delivery. Test Raid Items remained reliable.\n" ..
    "|cff4adf6b98%|r - Host first-lobby progressive readiness after the Workflow 11 changes.\n" ..
    "|cff6fb8ff92%|r - Complete MEMBER readiness publication after the field-contract correction; preflight and live validation remain.\n" ..
    "|cff6fb8ff92%|r - Refresh Readiness convergence after the same correction; it now has a deliverable output packet.\n" ..
    "|cffffcc0085%|r - Initial host Auto-Pass publication. The blocked MEMBER snapshot is the likely cause, but a live OFF-state retest is required.\n" ..
    "|cffffcc0065%|r - Reopened-lobby P2, Item History, and Session History stability. The failed MEMBER publication polluted all observations, so a clean retest must determine whether another proof-lifecycle defect remains."
  )

  AddBlock(
    "|cffff5555Historical confirmed defects - MEMBER contract corrected|r\n" ..
    "1. The MEMBER field contract was off by one: 44 payload values plus the opcode were rejected by a 44 encoded-field ceiling.\n" ..
    "2. The receiver and sender agreed on the expanded packet, but the common admission contract rejected it before dispatch.\n" ..
    "3. The safe-packet fallback cannot reduce field count, so this failure remained permanently pending rather than recovering.\n" ..
    "4. Member-side P2, archive leaves, matrix generation, and initial host Auto-Pass could not receive the complete authoritative publication.\n" ..
    "5. Refresh Readiness used the same blocked packet and therefore could not make host and member tables agree."
  )

  AddBlock(
    "|cffffcc00Questions recorded before the successful retest|r\n" ..
    "- Reopened-lobby P2, Item History, and Session History question marks may disappear once MEMBER publication succeeds. If they remain, the next target is proof invalidation across session close and lobby creation.\n" ..
    "- The initial host Auto-Pass mismatch is probably part of the blocked MEMBER snapshot because later compact toggle updates work, but this still needs a clean live confirmation.\n" ..
    "- A historical fallback-pending diagnostic may remain visible after the queue clears. That is diagnostic retention, not evidence that current data is still moving.\n" ..
    "- Host-side reopened-lobby unknown states cannot yet be classified as false presentation or genuine missing proof because the member's authoritative response channel was blocked during these tests."
  )

  AddBlock(
    "|cffa87ae8Implementation checklist|r\n" ..
    "[CODE COMPLETE | PREFLIGHT PASS] Raise only MEMBER from 44 to 45 encoded fields so 44 payload values plus the opcode are admitted.\n" ..
    "[CODE COMPLETE | PREFLIGHT PASS] Update the protocol boundary fixture to accept 44 MEMBER payload values and reject 45.\n" ..
    "[CODE COMPLETE] Keep the global field ceiling and every unrelated opcode unchanged.\n" ..
    "[CODE COMPLETE] Record the correction in the 0.9.15 changelog and protocol documentation.\n" ..
    "[x] Release Preflight passes 1658 / 1658.\n" ..
    "[x] Install the same corrected client on both machines and /reload both clients.\n" ..
    "[x] Run three consecutive two-client lobby phases without broad repair.\n" ..
    "[x] Require host and member P2/Loot List/Items/Sessions/Awards to converge in the normal proof path.\n" ..
    "[ ] Verify that a host with Auto-Pass OFF and ON is displayed correctly on the member without a toggle repair.\n" ..
    "[x] Reopened-lobby regressions were traced to proof identity and canonical leaf differences and corrected.\n" ..
    "[ ] Repeat the 30-target rehearsal and retain the MEMBER rejection counter as a release gate."
  )

  AddBlock(
    "|cffa87ae8Clean validation protocol|r\n" ..
    "[x] 1. Update and /reload both clients, then run Release Preflight.\n" ..
    "[x] 2. Prepare a lobby with one unchanged returning member and wait for the queue to become idle.\n" ..
    "[x] 3. Compare every host and member matrix cell before using any repair tool.\n" ..
    "[ ] 4. After one completed equal-build repair, press Refresh Readiness once and require the member to receive the same completed matrix through compact proof only.\n" ..
    "[x] 5. Start the session and run Test Raid Items.\n" ..
    "[x] 6. Close and reopen the lobby twice without changing profile data.\n" ..
    "[x] 7. The captured regressions were isolated without broadening communication limits."
  )

  section.Layout = function(self)
    local width = math.max(620, (self.frame:GetWidth() or 0) - 36)
    local y = -18
    for _, block in ipairs(self.blocks) do
      block:ClearAllPoints()
      block:SetPoint("TOPLEFT", 18, y)
      block:SetWidth(width)
      y = y - ((block.GetStringHeight and block:GetStringHeight()) or 0) - 18
    end
    local contentHeight = math.max(400, -y + 90)
    self.frame:SetHeight(contentHeight)
    if self.scroll and self.scroll.UpdateScrollChildRect then
      self.scroll:UpdateScrollChildRect()
    end
    return contentHeight
  end

  scroll:Hide()
  return section
end

local function CreateTutorialSyncWorkflow13Section(parent, createSection)
  local scroll, content, header, cards = createSection(parent, nil, {})
  local section = {
    scroll = scroll,
    frame = content,
    header = header,
    cards = cards,
    blocks = {},
  }

  local function AddBlock(text, template)
    local block = content:CreateFontString(nil, "OVERLAY", template or "GameFontHighlightSmall")
    block:SetJustifyH("LEFT")
    block:SetWordWrap(true)
    block:SetText(text)
    section.blocks[#section.blocks + 1] = block
  end

  AddBlock("Sync Workflow 13 - Precise Proof Retry Lifecycle", "GameFontHighlight")

  AddBlock(
    "|cff4adf6bCurrent status - 20 August 2026|r\n" ..
    "The matching-build two-client workflow is stable: first sync, unchanged checksum-only rejoin, canonical empty Items, Test Raid Items close/reopen, finite publication totals, and an idle final queue all passed. The latest user-run Release Preflight passed with warnings at 1800 / 1800 checks; only Communication transport and Profile synchronization exceeded their advisory timing thresholds. A 19-client raid test exposed transport saturation rather than the former proof lifecycle loop, and a later 22-client lobby preparation exposed both a member admission timeout weakness and a long host-frame freeze while simultaneous joins were accepted. The transport, member JOIN lifecycle, and host join-burst corrections below are code-complete and require a same-build raid-scale retest."
  )

  AddBlock(
    "|cffa87ae8Raid-scale transport correction - 19 August 2026|r\n" ..
    "The large-group capture showed finite forward progress, not an indefinitely recreating proof request. P1/P2 and several archive cells completed for some members while the queue fell from four figures to double figures. The dominant failure was host fan-out pressure: thousands of attempted sends, repeated throttle exhaustion, and SendAddonMessage result 11 while several large snapshots were being prepared for different members. Result 11 is WoW's temporary AddOnMessageLockdown state. It correlated with the boss encounter, but it is not the normal protected-action combat lockdown and addon messaging is not categorically forbidden in combat. LidCouncil must query and respect the chat/addon-message restriction state instead of spending retries while the API is unavailable.\n\n" ..
    "The corrected transport classifies ordinary throttle, temporary message lockdown, target-offline, permanent rejection, and generic failure separately. A message lockdown parks the existing queue without consuming any packet retry budget and resumes it when WoW reports that the chat restriction ended. An ordinary throttle opens one shared cooldown for every lane, applies a paced recovery interval after the first successful send, and prevents new profile snapshots from being generated during that cooldown. Only one expanded profile snapshot may be active at a time. Repeated START/JOIN/JACK/IACK readiness controls for the same session, profile, member, distribution, and target replace their older queued copy. Existing priority ordering keeps session admission and P1 controls first, live roll/vote/award traffic second, bootstrap profile data next, and non-blocking archive history last. Receipt timeouts still begin only after PSYNC_END was actually sent, so work parked by WoW cannot expire before delivery was attempted.\n\n" ..
    "On each member, accepted lobby admission now owns one JOIN retry timer. The retry budget advances only when WoW successfully sends a JOIN, not when LidCouncil merely queues it. Successful sends use paced waits of 8, 12, 18, and 25 seconds plus a final 12-second acknowledgement window, giving the host approximately 75 seconds before normal exhaustion. A 90-second hard guard is anchored to the first successful JOIN and pauses during addon-message lockdown or the shared throttle cooldown. Repeated START packets refresh the pending invite without immediately adding another JOIN or another timer. Timed-out requests remain eligible for a valid late JACK for 60 seconds, so an acknowledgement already moving through the host queue is not discarded.\n\n" ..
    "The 22-client screenshot also isolated a host main-thread problem: every accepted JOIN forced two full Session/Sync Management editor refreshes while the measured Session page itself averaged about 242ms and peaked near 684ms. JOIN admission now performs only the durable roster upsert and queues JACK immediately. Profile role insertion, revision commits, comparison, repair planning, snapshots, member publication, and UI refreshes run after a 0.75-second join quiet window. Role and session revisions are committed once per burst, duplicate member follow-ups are coalesced, and one member follow-up is processed every 0.08 seconds. START receipt timeouts now begin only after a successful WoW send, wait eight seconds, and pause while START remains queued or transport is restricted. Sync Management exposes pending, coalesced, processed, failed, and per-member duration totals through the JOIN Worker row and copied performance report.\n\n" ..
    "About > Raid Test Trace now provides an opt-in flight recorder for the next large-client run. Its fixed 2,000-event ring records critical admission and synchronization boundaries, accepted-JOIN burst commits and paced follow-ups, Test Raid Items preparation and full-board receipt lifecycles, measured editor/snapshot work, WoW message-lockdown transitions, changed queue/counter samples with a ten-second idle heartbeat, combat and encounter transitions, and frame gaps of at least 250ms. It deliberately skips individual bulk chunks and never refreshes the UI for each event. Full Live Board snapshots are serialized per session/profile: one active transaction may retain only the newest waiting board, which is released after the active transaction finishes. Test item definitions wait for real WoW item metadata before a board is built, time out safely instead of publishing question-mark items, and coalesce rapid repeated requests. Start the trace on the host and representative members before preparing the lobby, add manual markers before invitations, Test Raid Items, pull, encounter end, and any observed stall, then stop and copy each report from About."
  )

  AddBlock(
    "|cffa87ae8Confirmed failure|r\n" ..
    "After a session was ended and reopened, an exact archive proof could fail to arrive. The fourth retry cleared the pending join-manifest object, which also erased the only retry budget. Automatic prewarm or checkpoint callbacks then created the same request with a new token. Live follow-up isolated the resulting pressure on the member: PSUB_STATE grew to 5,184 queued parts and PSUBPATCH_STATE to 816, filling the 6,000-package queue while the host discarded stale responses. After response coalescing bounded that queue, a second live retest exposed the remaining gate defect: the accepted PSUB request token was retained in request state but dropped when the sync candidate was built, so every valid multipart response was treated as unrelated and the bounded lifecycle ended in precise proof timeout. A third live retest retained bounded queues but sent 821 PSUB_STATE parts before the host timed out: the join-manifest scheduler was consuming one attempt every second while the underlying request was still waiting, even though one approximately 205-part response needs substantially longer at addon-channel throughput. A fourth live retest kept both queues bounded but did not converge: the host reached approximately 94% WoW CPU with severe FPS loss while the member remained stable at 20-25%. The host sent 1,071 messages, including 478 MEMBER packets with 11 throttle events, received 4,592 messages, and ignored 1,530 stale messages while only two queue entries remained visible. The member sent 4,299 messages, including 2,498 PSUB_STATE parts, while 63 response parts remained queued. This is a replenishing request/publication loop rather than one finite backlog."
  )

  AddBlock(
    "|cffa87ae8Lifecycle contract|r\n" ..
    "A proof request is identified by session, member, local profile revision, and normalized requested domains and leaves. The same identity keeps one request token for its initial request and every bounded retry. Exhaustion writes a terminal tombstone before the pending timer is cleared, so automatic callbacks suppress that exact identity. A new session, a changed profile revision, changed scopes, or an explicit Refresh Readiness creates a new request lifecycle. The responder also keeps one multipart response token for that request and the transport owns one queue slot per response part; a replacement request supersedes stale queued parts instead of appending another response set."
  )

  AddBlock(
    "|cffa87ae8Historical fourth live-test assessment - feedback loop now fixed|r\n" ..
    "The original 6,000-package retry-reset failure is no longer reproducing: queue depth is bounded and the active proof wrapper still reuses the join-manifest token for the same automatic scope while rejecting a competing automatic scope. The remaining failure is a host-side feedback loop. Routine PSTATE handling republishes MEMBER even when readiness did not change, force-immediate publication bypasses normal coalescing, and the full payload signature contains volatile synchronization timestamps. Each successful MEMBER publication can also enter checkpoint bookkeeping, while each PSUB_REQ forces another delayed PSTATE even when its stable profile-state signature is unchanged. Timestamp-only recovery/display changes can additionally look like visible changes and rearm prewarm or detailed comparison. The host then rebuilds the visible Session or Sync Management UI at approximately message cadence. Existing refresh merging covers only a 0.05-second burst, so 100-250ms page/editor refreshes can continuously saturate WoW's main thread without producing a Lua error."
  )

  AddBlock(
    "|cffa87ae8Addon-wide cross-check that drove the completed correction|r\n" ..
    "The scan covered every Lua caller that sends MEMBER, PSTATE, PSUB_REQ, PSUB_STATE, patch manifests, join-manifest repair, checkpoint/prewarm work, and Session editor refreshes. No second unbounded archive queue was found. Checkpoint row signatures and repair transport signatures already exclude lastSyncAt and have bounded retention; precise response tokens are checked before PSUB_STATE multipart assembly; the host monitor was on Manual refresh in the reproducing capture; and transport throttle retries are bounded. Relevant additional weaknesses remain: PSUBPATCH_STATE validates its request token only after multipart reassembly and manifest preview, a forced PSUB request always schedules forced PSTATE publication, automatic UI refresh has no minimum interval beyond the 0.05-second merge window, and an execution-time abort can leave a non-empty outbound queue marked running without a guaranteed replacement flush timer. These are safeguards for the same feedback/CPU failure and belong in the next patch."
  )

  AddBlock(
    "|cffa87ae8Design challenge - why is an unchanged rejoin not checksum-only?|r\n" ..
    "The challenge is valid. After the first authoritative transfer, two unchanged replicas should normally exchange only compact identity, revision, and checksum proof. LidCouncil already calculates stable Settings, Permissions, Operational, Loot, and Session domain checksums; a canonical Current Session checksum; four P3 scope checksums for Loot List, Item History, Session History, and Fun Awards; and leaf checksums for individual ledger rows, groups, award events, completed sessions, and Live Board snapshots. An empty Item History also has a real canonical empty-bucket checksum. If the profile identity and the relevant top-level checksums match, detailed PSUB proof, patch manifests, and profile chunks are unnecessary. They are justified only when a checksum is missing, differs, belongs to another session or profile revision, or a bounded repair needs the exact differing leaves.\n\n" ..
    "The hard first sync succeeding while the smaller proof path fails is therefore not evidence that checksums are expensive. The first sync is a finite mostly one-way snapshot. The returning-member path is a bidirectional state machine: PSTATE updates host MEMBER publication, MEMBER and checkpoint callbacks can schedule more validation, PSUB_REQ can force another PSTATE, and each automatic UI update can rebuild an expensive Session page. Volatile timestamps, forced unchanged publication, or a proof token lifecycle error can turn a few correct hashes into a feedback loop. The fourth live test matches that failure shape: very small visible queues, continuously rising MEMBER and PSUB_STATE totals, throttle warnings, and host-only CPU saturation."
  )

  AddBlock(
    "|cffa87ae8Session History authority assessment|r\n" ..
    "The host-authority instinct is correct, but manual-only Session History is not the safest model. Today the host creates the PREPARING record and the joining client stores a mirrored record with the same session ID. The active record is already excluded from the P3 Session History scope and is compared separately as P2 Current Session State, so joining at a different moment should not by itself change the current archive checksum. When STOP changes that record to FINISHED, it enters P3 and its archived checksum intentionally includes host-authored start/end data plus participant status and historical metadata. Both clients use the host's STOP endedAt, but there is no separate sealed-record payload that replaces every canonical historical field at finalization. A locally missing or differently hydrated participant field can therefore become a real Session History mismatch after close.\n\n" ..
    "The stronger contract is to keep PREPARING/ACTIVE state provisional and host-authored, then have the host seal one immutable final history record at STOP. Members must never invent canonical history timestamps from their own clock or merge local presentation/recovery metadata into that record; they replace the authoritative fields by session ID and return a compact checksum acknowledgement. A matching checksum ends the workflow immediately. A mismatch requests only that one session leaf, and a manual push remains the fallback after bounded automatic repair. This preserves complete history for future hosts, participation counts, audit/display, and recovery while preventing join timing from manufacturing permanent differences.\n\n" ..
    "A yellow state is useful only as a delivery distinction: roll-ready and canonical P1/P2 may be green while a newly finalized or older archive is still pending. Green must continue to mean exact canonical checksum alignment; yellow means known background archive work or known stale history; a question mark means proof is missing or inconclusive. Treating a permanent mismatch as irrelevant would hide replica drift. The existing separation between Roll Ready and P3 should carry this meaning rather than making manual history synchronization the normal correctness path."
  )

  AddBlock(
    "|cffa87ae8Approved day-two policy|r\n" ..
    "STOP is the authority boundary because that is when final duration, endedAt, participant state, and audit metadata become true; starting a later raid cannot safely reconstruct those facts. The host now seals that completed record at STOP. During the next preparation, an unchanged returning member compares the compact Session History scope checksum. If it differs, the host sends only the newest sealed ssr:id:<session> leaf once. Starting the new session is the one automatic retry boundary. No automatic broad Session History or full-profile fallback is allowed for this policy. If the member missed multiple raid days, the newest record is caught up but the remaining root difference stays visible as a yellow check until a manual history push. PREPARING/ACTIVE remains P2 provisional state and is excluded from the completed-history checksum."
  )

  AddBlock(
    "|cffa87ae8Implementation checklist|r\n" ..
    "[CODE COMPLETE] Build a stable proof identity from session, member, profile revision, domains, and subdomains.\n" ..
    "[CODE COMPLETE] Let the deferred-proof scheduler own the first PSUB request and reuse its JMR token for every retry and overlapping automatic request.\n" ..
    "[CODE COMPLETE] Store a terminal tombstone before retry state is cleared and suppress automatic recreation of the same identity.\n" ..
    "[CODE COMPLETE] Rearm only when session, revision, or scopes change, or when the host explicitly presses Refresh Readiness.\n" ..
    "[CODE COMPLETE] Add deterministic preflight fixtures for exhaustion, token reuse, changed-identity rearming, and manual rearming.\n" ..
    "[CODE COMPLETE] Reuse PSUB_STATE and PSUBPATCH_STATE multipart response tokens for the same request identity.\n" ..
    "[CODE COMPLETE] Coalesce an identical queued response part and replace the same response slot when a newer request token supersedes it.\n" ..
    "[CODE COMPLETE] Keep automatic patch-manifest requests on their remembered token for the active session instead of rotating it on every forced callback.\n" ..
    "[CODE COMPLETE] Project the accepted PSUB request token into the sync candidate and require token, completion, detail, and requested-domain coverage before accepting proof.\n" ..
    "[CODE COMPLETE] Add regressions for matching, missing, and mismatched proof tokens, incomplete domain coverage, immediate exit from the retry path, and canonical empty Item History.\n" ..
    "[CODE COMPLETE] Poll a waiting precise request without consuming its bounded retry budget; consume one attempt only after the request reports failure or returns unusable completed proof.\n" ..
    "[CODE COMPLETE] Extend the request stall deadline on each unique valid multipart part, revive a just-timed-out request when matching progress arrives, and preserve partial domain receipts when the same token retries.\n" ..
    "[CODE COMPLETE] Count unique multipart parts in O(1) per receipt and throttle stale multipart pruning so large proof responses do not rescan the full expected part range on every packet.\n" ..
    "[CODE COMPLETE] Build a stable MEMBER publication and work-trigger signature from authoritative roster/readiness/checksum/apply state, excluding synchronization and recovery timestamps and diagnostic telemetry.\n" ..
    "[CODE COMPLETE] Separate urgent delivery from duplicate suppression. forceImmediate skips the short delay but an identical automatic MEMBER payload is still suppressed; readiness-only republication never creates a session checkpoint.\n" ..
    "[CODE COMPLETE] Keep exact PSTATE proof republication for the first accepted verdict while stable MEMBER dedupe suppresses every identical repeat.\n" ..
    "[CODE COMPLETE] Stop PSUB_REQ from scheduling PSTATE at all. A checksum request does not mutate profile state; initial admission, completed apply, and explicit readiness actions own PSTATE publication.\n" ..
    "[CODE COMPLETE] Bound Session editor rendering to a minimum 0.75-second cadence while retaining the merged dirty state and final refresh.\n" ..
    "[CODE COMPLETE] Reject stale PSUBPATCH_STATE request tokens before multipart reassembly/preview, and rearm a non-empty outbound queue whose running flag survived without a flush timer.\n" ..
    "[CODE COMPLETE] Add deterministic preflight gates for identical MEMBER suppression, explicit repeat, stale archive aggregation, bounded latest-history attempts, editor cadence, and outbound flush recovery.\n" ..
    "[CODE COMPLETE] Make the unchanged existing-replica path checksum-only. Matching profile identity plus matching P1, P2, and four P3 scope checksums schedule no detailed proof or profile payload.\n" ..
    "[CODE COMPLETE] Keep PREPARING/ACTIVE outside completed-history authority and seal one host-authored completed record at STOP. The existing ssr:id:<session> patch applies that canonical record by session ID as an exact replacement.\n" ..
    "[CODE COMPLETE] On a known Session History mismatch, send only the newest completed session leaf once during preparation and retry it once at Start; forbid automatic broad/full fallback and leave older gaps yellow for manual push.\n" ..
    "[CODE COMPLETE] Render green only for exact canonical alignment, yellow for known stale archive data, and question mark for missing/inconclusive proof.\n" ..
    "[CODE COMPLETE] Add an identical-rejoin/empty Item History budget fixture and deterministic sealed-leaf selection/catch-up budget fixtures.\n" ..
    "[LIVE TEST] Finalize one session with skewed client clocks and different local presentation metadata, then require both clients to produce the host-sealed Session History checksum; drop STOP once and require one bounded session-leaf repair.\n" ..
    "[VERIFIED BASELINE] The last user-run Release Preflight before the raid-scale transport patch passed 1,773/1,773 checks across seven suites.\n" ..
    "[CODE COMPLETE] Classify WoW addon-message lockdown separately from throttle and generic API failure.\n" ..
    "[CODE COMPLETE] Park the outbound queue during chat/addon-message lockdown without consuming retry budgets, then resume after the restriction is inactive.\n" ..
    "[CODE COMPLETE] Apply one shared adaptive cooldown after any throttle result so other members and lanes cannot immediately consume their own retry budgets.\n" ..
    "[CODE COMPLETE] Pace post-throttle recovery and stop generating new expanded snapshots during the shared cooldown or message lockdown.\n" ..
    "[CODE COMPLETE] Limit expanded snapshot admission to one active transfer and preserve ACK timing from the successfully sent PSYNC_END boundary.\n" ..
    "[CODE COMPLETE] Coalesce repeated queued START, JOIN, JACK, and IACK readiness controls by stable member/session/profile identity.\n" ..
    "[CODE COMPLETE] Give every member one serialized JOIN retry lifecycle with paced 8/12/18/25-second backoff and a final 12-second acknowledgement wait.\n" ..
    "[CODE COMPLETE] Count JOIN attempts only after a successful WoW send, pause the timeout during transport restriction, suppress duplicate START timer chains, and accept a matching late JACK for 60 seconds.\n" ..
    "[CODE COMPLETE] Keep host JOIN admission lightweight: queue JACK immediately, coalesce duplicate follow-up work per member, wait for a 0.75-second quiet window, then pace comparison/repair/snapshot work one member at a time.\n" ..
    "[CODE COMPLETE] Batch new-member role and session revision commits once per join wave and replace per-member forced editor repaints with one deferred refresh.\n" ..
    "[CODE COMPLETE] Anchor host START receipt attempts to successful sends, pause queued receipt timers during message restriction, and expose the paced JOIN worker in Sync Management and performance reports.\n" ..
    "[CODE COMPLETE] Add About > Raid Test Trace with a bounded low-overhead timeline, manual markers, queue/counter sampling, critical protocol timings, snapshot/editor timings, transport transitions, and main-thread frame-gap attribution.\n" ..
    "[CODE COMPLETE] Serialize full Live Board snapshots per session/profile, retain only the newest waiting board, and release it only after the active transaction reaches a terminal receipt state.\n" ..
    "[CODE COMPLETE] Make Test Raid Items wait for real item names, links, levels, quality, and icons; retry cold Encounter Journal definitions; stop safely on timeout; and debounce rapid repeated requests.\n" ..
    "[CODE COMPLETE] Correlate Test Raid Items run IDs with board IDs, activation, coalescing, release, receipt, repair, and failure events; double trace retention and suppress unchanged one-second samples.\n" ..
    "[CODE COMPLETE] Report a temporary WoW message pause separately from retry exhaustion in Sync Management.\n" ..
    "[PREFLIGHT ADDED] Cover result classification, retry preservation during lockdown, shared cooldown growth/cap, recovery pacing, snapshot admission gates, readiness-control coalescing, serialized member JOIN timer ownership, retry pacing, restriction-paused deadlines, and late-ACK grace.\n" ..
    "[LIVE RAID RETEST] Install the same corrected build on host and every participating member; saved-variable deletion is not required. Test 16-22 clients first out of combat, then across pull/combat/end transitions.\n" ..
    "[LIVE RAID RETEST] Require P1 and P2 to converge before bulk P3, no immediate red X caused only by local message lockdown, queue depth to trend downward, and throttle/API-terminal totals to stop increasing after restrictions clear.\n" ..
    "[LIVE RAID RETEST] Confirm that late joiners enter the same bounded admission queue, existing ready members keep their state, and live roll/choice/vote/award traffic preempts remaining bootstrap/archive chunks.\n" ..
    "[LIVE RAID RETEST] Start Raid Test Trace on the host and representative members before lobby preparation; mark invitations, all-joined, pull, encounter end, and observed stalls; stop and copy every report before starting another trace.\n" ..
    "[x] Install the same current build on both clients and /reload both clients.\n" ..
    "[ ] Reopen an unchanged returning-member lobby and withhold precise proof long enough to exhaust all retries.\n" ..
    "[ ] Confirm that Detail Compare and Patch Manifest queues stay bounded to one multipart response set while retries run, then stop growing after the terminal attempt.\n" ..
    "[x] In the normal successful path, the member no longer approaches the 6,000-package limit and response/publication totals plateau. Deliberate exhaustion remains above.\n" ..
    "[ ] Press Refresh Readiness once and confirm that exactly one new proof token is armed.\n" ..
    "[ ] Change one relevant profile revision or requested archive scope and confirm that the new identity rearms without a manual refresh.\n" ..
    "[x] Allow proof to arrive without timeout and require host/member P2 and all four P3 archive cells to converge.\n" ..
    "[x] Repeat with no saved Live Board history and confirm Items becomes ready from the canonical empty checksum without requesting a repair.\n" ..
    "[x] The normal multipart response completed without execution-time-limit errors. Deliberate progress-aware retry observation remains covered by Preflight rather than live fault injection.\n" ..
    "[x] During an unchanged returning-member join, MEMBER and proof totals plateau after convergence instead of increasing with elapsed time.\n" ..
    "[PARTIAL] The outbound queue drains to zero and the old feedback loop is gone, but large-profile sync can still spike host CPU/FPS; performance optimization remains Workflow 7/8 work.\n" ..
    "[ ] Finish raid day one, prepare day two, and confirm the returning member receives at most the one newest sealed session leaf; Session History should move yellow to green without broad Session History or full-profile traffic.\n" ..
    "[ ] Start day two before that leaf converges and confirm exactly one retry is allowed at Start, with no further automatic attempts in that lobby.\n" ..
    "[ ] Rejoin with a member missing two completed raid days. Confirm only the newest record is caught up automatically, the older gap remains yellow, roll readiness stays green, and one manual Session History push resolves the remaining gap."
  )

  AddBlock(
    "|cffa87ae8Two-client evidence to retain|r\n" ..
    "Capture the PSUB and PSUBPATCH request tokens, multipart response tokens, requested scopes, first/last part times, retry count, Detail Compare/Patch Manifest queue depths after each retry, terminal diagnostic, and the token created by Refresh Readiness. Also retain the copied Raid Test Trace from the host and representative members, host/member CPU and FPS, MEMBER/PSTATE/PSUB_REQ/PSUB_STATE sent and queued totals at two timestamps at least 30 seconds apart, stale ignored count, editor and Session page last/average/maximum refresh cost, and throttle warnings. The acceptance rule is bounded growth and progress-aware timing: one identity may resend after a genuine stall with one token, active multipart progress must not consume attempts, queued parts must remain at or below one current response set, automatic publication and request totals must plateau after convergence, and no automatic response cycle may restart after exhaustion until an allowed rearm event occurs."
  )

  section.Layout = function(self)
    local width = math.max(620, (self.frame:GetWidth() or 0) - 36)
    local y = -18
    for _, block in ipairs(self.blocks) do
      block:ClearAllPoints()
      block:SetPoint("TOPLEFT", 18, y)
      block:SetWidth(width)
      y = y - ((block.GetStringHeight and block:GetStringHeight()) or 0) - 18
    end
    local contentHeight = math.max(400, -y + 90)
    self.frame:SetHeight(contentHeight)
    if self.scroll and self.scroll.UpdateScrollChildRect then
      self.scroll:UpdateScrollChildRect()
    end
    return contentHeight
  end

  scroll:Hide()
  return section
end

local function CreateTutorialSyncWorkflow14Section(parent, createSection)
  local scroll, content, header, cards = createSection(parent, nil, {})
  local section = {
    scroll = scroll,
    frame = content,
    header = header,
    cards = cards,
    blocks = {},
  }

  local function AddBlock(text, template)
    local block = content:CreateFontString(nil, "OVERLAY", template or "GameFontHighlightSmall")
    block:SetJustifyH("LEFT")
    block:SetWordWrap(true)
    block:SetText(text)
    section.blocks[#section.blocks + 1] = block
  end

  AddBlock("Sync Workflow 14 - Readiness Truth and Archive Indicators", "GameFontHighlight")

  AddBlock(
    "|cff4adf6bCurrent status - 11 August 2026|r\n" ..
    "Indicator and canonicalization implementation: 11 / 11 complete (100%). Release Preflight: 1658 / 1658. The standard two-client presentation gate is 5 / 5 complete (100%): P1/P2 become playable independently from P3, unchanged rejoin uses checksum-only green states, equal-profile P2 reopens green without repair, TESTSIM no longer leaves a false Item History warning, and the indicators/progress labels behaved correctly at the tested resolutions. Remaining backlog: Copy Sync Detail, resizable Sync Detail, one equal-build Refresh Readiness-after-repair check, and a genuine saved-board difference control."
  )

  AddBlock(
    "|cffa87ae8Large-profile live-test assessment|r\n" ..
    "The first join of the large profile behaved like a bounded background synchronization. P1 became usable quickly, P2 followed independently, and Session History continued as a finite 660-part transfer with visible progress at 78/660. The member was roll-ready before the historical archive finished, which is intentional: raid-critical Settings, Permissions, current-session state, and current Loot List participation must not wait for years of retained history. The screenshot did not show the old retry loop; it showed useful foreground readiness plus unfinished background delivery.\n\n" ..
    "At that time the presentation was misleading. The member tooltip correctly said P3 Archive Data was pending and Session History was still sending, while the row badges appeared to be green checks and the summary said Fully Synced. A checkmark was carrying two incompatible meanings: playable now and exact replica alignment. The implemented indicator contract below separates those meanings without changing the successful transfer behavior."
  )

  AddBlock(
    "|cffa87ae8Why the pending badges looked green|r\n" ..
    "The former STALE presentation selected Blizzard's ReadyCheck-Ready texture and applied a yellow vertex tint. That source texture already contains a predominantly green checkmark. Multiplying it by a yellow tint could not add red to pixels whose red channel was absent, so the result remained visually green or green-gold. The new implementation replaces that asset instead of tinting it."
  )

  AddBlock(
    "|cffa87ae8Required semantic contract|r\n" ..
    "Every column must answer one question only: is this exact scope proven aligned? P1 may become green when the raid-ready bootstrap is exact. P2 may become green when Current Session State is exact. Loot List, Items, Sessions, and Awards may become green only when that scope's canonical checksum matches after any received data has been applied. A scope that is merely non-blocking or safe to postpone must not borrow the green aligned indicator. Roll readiness belongs in the separate Roll Ready summary and must not be inferred from four archive badges."
  )

  AddBlock(
    "|cffa87ae8Indicator language|r\n" ..
    "|cff4adf6bGreen check|r - Exact canonical checksum alignment for that column. No apply step remains.\n" ..
    "|cff66b8ffBlue progress symbol|r - Proof is being validated or data is queued, sending, receiving, waiting for receipt, or waiting to apply. Show the matching scope name and part progress, for example Sessions 78/660.\n" ..
    "|cffffc747Yellow warning diamond or exclamation|r - Both sides supplied proof and a known non-blocking archive difference remains, or a bounded automatic repair ended while older history still needs a manual push. Do not use the baked green ready-check texture for this state.\n" ..
    "|cff9ca6b5Gray question mark|r - Proof is missing, incomplete, belongs to another identity, or is otherwise inconclusive. This is unknown, not failure.\n" ..
    "|cffff5a5aRed cross|r - A conclusive failure or user review is required.\n\n" ..
    "Shape and color should both change so color-blind users and tinted Blizzard textures do not collapse distinct states into the same green check."
  )

  AddBlock(
    "|cffa87ae8Summary wording|r\n" ..
    "Roll Ready remains the authoritative answer to Can we start the raid? Fully Synced is replaced whenever any P3 scope is unfinished. Session Sync reports Live Ready - Archive Syncing while blue delivery is active, Live Ready - Archive Differs when yellow stale scopes remain, Live Ready - Validating Archive when proof is incomplete, and Fully Synced only when P1, P2, and every displayed P3 scope are exactly green. This preserves fast raid startup without overstating replica convergence."
  )

  AddBlock(
    "|cffa87ae8Implemented indicator contract|r\n" ..
    "The Current Session table now applies this language directly. Green ReadyCheck is reserved for exact canonical alignment. Active validation or delivery uses a blue refresh symbol. A proven non-blocking difference uses a yellow alert symbol. Missing or inconclusive proof uses a desaturated gray question mark. Review or conclusive failure uses the red not-ready cross. Active repair takes precedence over a stale result, so a scope is blue while it is moving and becomes yellow only if a difference remains afterward.\n\n" ..
    "The Session Sync card is archive-aware without changing Roll Ready. It reports Live Ready - Archive Syncing, Live Ready - Archive Differs, Live Ready - Validating Archive, or Live Ready - Review Needed when P2 is usable but P3 is unfinished. Fully Synced is now reserved for exact proof across every displayed P1, P2, and P3 scope."
  )

  AddBlock(
    "|cffa87ae8Exact Item History diagnostic added|r\n" ..
    "After a complete precise session proof, the host now compares the local and remote slb leaves directly. Hovering the member row while Item History is stale lists up to six exact differing Live Board leaves, whether each checksum differs or the leaf is missing locally/remotely, and the raw slb subdomain identity. Additional leaves are counted. This is diagnostic-only: it does not change retry limits, request tokens, transfers, readiness, or archive checksums. It is intended to confirm whether the remaining yellow state comes from a hidden Test Raid Items snapshot or another specific board before canonicalization changes are considered."
  )

  AddBlock(
    "|cffa87ae8P2 proof diagnostic added|r\n" ..
    "When P2 is not green, hovering the member row now reports the exact current-lobby proof reason instead of stopping at Proof unknown. On the host this distinguishes a missing proof, a proof from the previous lobby, profile identity mismatch, an incomplete proof, a missing host/member current-session checksum, a checksum difference, and matching checksums whose compact status publication is still pending. On a member client it explicitly says that host validation is pending because the immutable proof object is host-only. This is read-only instrumentation and does not request data, rearm retries, alter status matrices, or change readiness."
  )

  AddBlock(
    "|cffa87ae8Exact Loot List diagnostic added|r\n" ..
    "The Loot List archive checksum covers the complete canonical loot family, not only rows that are obvious in the visible editor. It includes shared/list-group leaves plus per-character Loot Ledger rows and compact Member Meta leaves. Two editors can therefore look equal while a hidden member-meta field, a missing character leaf, differently cased character identity, or duplicate identity changes the aggregate checksum. When Loot List is yellow, the host member tooltip now lists the exact differing leaves and classifies checksum mismatch, missing on host/member, identity casing, and duplicate host/member identity. If the aggregate differs but the completed leaf manifests compare equal, the tooltip reports that explicitly instead of inventing a leaf cause. This diagnostic is read-only."
  )

  AddBlock(
    "|cffa87ae8Earlier two-client evidence before the final P2/TESTSIM corrections|r\n" ..
    "A fresh character completed the initial profile and archive synchronization cleanly. On the next preparation without Test Raid Items, Loot List, Item History, Session History, and Fun Awards all reached exact green while the Background Sync Monitor was healthy, idle, and empty. P2 alone remained a gray Proof unknown state. That result separates the remaining P2 proof-publication question from archive convergence; it is not evidence of continuing archive traffic or a retry loop.\n\n" ..
    "After a later session used Test Raid Items, the following lobby showed Item History as the only yellow Archive differs scope while the other archive columns remained green. This strongly supports the working hypothesis that the simulation creates or changes a canonical Live Board history leaf. The warning is therefore reporting a real checksum difference rather than inventing a transport failure. Keep the current bounded archive lifecycle unchanged until the exact slb diagnostic identifies the differing simulated board and a separate policy decision is made about whether test-only history belongs in canonical Item History."
  )

  AddBlock(
    "|cffa87ae8Equal-build precise-preview evidence and root cause|r\n" ..
    "Both clients were updated to the same addon version. The initial synchronization completed cleanly, but the next untouched preparation left P2 at Proof pending. Precise preview isolated one active Session Record. Its full archival leaf contains host-only character presentation fields, but those fields are already excluded from the bootstrap-critical Current Session State checksum. The checksum-relevant difference is rollOptions: the host record contains the configured BiS / Almost BiS choices while the member record contains fallback Need / Greed choices. START retains a legacy roll-option decoder but does not carry a full modern option set; encoding all eight LidCouncil options there would exceed the safe addon-message field and byte budgets. The actual defect was that a returning member with an already synchronized profile still allowed the new session record to fall back to defaults instead of binding it to that local canonical profile. The correction reuses the synchronized local options immediately; a first-time member remains pending until the existing profile snapshot path finishes and refreshes the session cache. This is deterministic state binding, not a retry loop or failed proof lifecycle.\n\n" ..
    "After Test Raid Items, precise preview isolated the saved Live Board plus the following Session Record. The board differences are primarily non-semantic transport representation: icon numbers become strings, host LOCAL and REMOTE selection provenance becomes MIRROR on the receiver, updatedAt differs by one second, and the payload-safe BSYNC item fallback may omit a long item link. These fields currently enter the saved-board checksum too literally, so equal gameplay state can remain yellow. The correction must normalize transport-local provenance, scalar representation, and timing before checksum comparison, while treating an omitted fallback link carefully so a genuinely different item variant is not hidden. Test-only snapshots may instead be excluded from canonical Item History proof, but that policy must remain a separate, narrowly tested decision."
  )

  AddBlock(
    "|cffa87ae8Sync Detail usability backlog|r\n" ..
    "[TODO UX] Add a Copy Sync Detail control that exports the complete visible summary, loaded precise leaf scopes, detailed snapshot differences, and nested preview rows as plain clipboard text. It must work without applying, pulling, or pushing anything and should include enough identity context to replace multi-screenshot bug reports.\n" ..
    "[TODO UX] Make the Sync Detail window resizable with a visible drag handle, sensible 1080p minimum dimensions, screen-clamped maximum dimensions, scroll regions that relayout with the window, and remembered dimensions for the current client. Resizing must be presentation-only and must not refresh proof, request another manifest, or mutate selected preview rows."
  )

  AddBlock(
    "|cffa87ae8Implementation and live-test checklist|r\n" ..
    "[CODE COMPLETE] Record exact differing slb leaves after complete precise proof and expose them in the Current Session member tooltip.\n" ..
    "[CODE COMPLETE] Add deterministic diagnostics for matching leaves, checksum mismatch, host-only leaf, member-only leaf, and missing proof.\n" ..
    "[CODE COMPLETE] Reserve green checks for exact per-column alignment and use independent shapes for progress, known stale data, unknown proof, and failure.\n" ..
    "[CODE COMPLETE] Replace Fully Synced with a live-ready/archive-progress summary whenever any P3 scope is not exact.\n" ..
    "[CODE COMPLETE] Add preflight fixtures for all five indicator meanings, active-repair precedence, and every archive-aware summary state.\n" ..
    "[CODE COMPLETE] Label blue archive progress with the matching Items or Sessions column name instead of the ambiguous History label.\n" ..
    "[CODE COMPLETE] Replace gray P2 Proof unknown ambiguity with read-only current-lobby proof/session/profile/checksum diagnostics in the member tooltip.\n" ..
    "[CODE COMPLETE] Add exact Loot List leaf diagnostics for checksum, missing-side, casing, duplicate-identity, and unexplained aggregate mismatch states.\n" ..
    "[LIVE PASS] Confirm a fresh member can finish the complete initial archive transfer and an unchanged rejoin can leave all four P3 scopes exact with an idle queue.\n" ..
    "[LIVE EVIDENCE] Confirm Test Raid Items can leave Item History as the only known archive difference on the next lobby.\n" ..
    "[LIVE ROOT CAUSE] Equal-build precise preview proved the returning member failed to bind its synchronized profile roll options, leaving fallback Need / Greed and P2 checksums unequal.\n" ..
    "[LIVE ROOT CAUSE] Test Raid Items precise preview proved saved-board differences in icon scalar type, selection provenance, updatedAt, and a payload-fallback-omitted link.\n" ..
    "[CODE COMPLETE] Rebind a returning session to the already synchronized local profile options without expanding START beyond its safe transport budget; retain legacy invite options only while a first profile is still pending.\n" ..
    "[CODE COMPLETE] Canonicalize real saved-board proof across icon scalar type, selection provenance, receiver-local updatedAt, selected UI row, local profile alias, and derived host-vote presentation while retaining gameplay item and choice differences.\n" ..
    "[CODE COMPLETE] Exclude explicitly non-history, simulated, and all-TESTSIM saved boards from canonical Item History proof so transport tests cannot create a persistent archive warning.\n" ..
    "[TODO UX 0 / 2] Add Copy Sync Detail clipboard export and a resizable, relayout-safe Sync Detail window.\n" ..
    "[ ] In an unchanged reopened lobby with an idle queue, capture the P2 Diagnostic, press Refresh Readiness once, and capture it again; confirm whether P2 becomes exact without starting archive delivery.\n" ..
    "[LIVE EVIDENCE] While Loot List was yellow, precise preview isolated hidden Member Meta and empty/default statistics structure rather than visible Loot List rows.\n" ..
    "[LIVE PASS] P1/P2 become playable without claiming that unfinished P3 columns are aligned.\n" ..
    "[LIVE PASS] On an unchanged rejoin, checksum-only scopes become exact green without a heavy transfer.\n" ..
    "[LIVE PASS] Equal-profile reopen reaches P2 exact green without Refresh Readiness or archive delivery.\n" ..
    "[LIVE PASS + CONTROL PENDING] Test Raid Items, close, and reopen creates no TESTSIM Item History warning. A deliberately changed genuine saved board is still needed as the positive warning control.\n" ..
    "[LIVE PASS] Indicator assets, named Items/Sessions progress, and Session Sync wording behaved correctly at the tested 1080p/4K layouts."
  )

  section.Layout = function(self)
    local width = math.max(620, (self.frame:GetWidth() or 0) - 36)
    local y = -18
    for _, block in ipairs(self.blocks) do
      block:ClearAllPoints()
      block:SetPoint("TOPLEFT", 18, y)
      block:SetWidth(width)
      y = y - ((block.GetStringHeight and block:GetStringHeight()) or 0) - 18
    end
    local contentHeight = math.max(400, -y + 90)
    self.frame:SetHeight(contentHeight)
    if self.scroll and self.scroll.UpdateScrollChildRect then
      self.scroll:UpdateScrollChildRect()
    end
    return contentHeight
  end

  scroll:Hide()
  return section
end

local function CreateTutorialSyncWorkflow15Section(parent, createSection)
  local scroll, content, header, cards = createSection(parent, nil, {})
  local section = {
    scroll = scroll,
    frame = content,
    header = header,
    cards = cards,
    blocks = {},
  }

  local function AddBlock(text, template)
    local block = content:CreateFontString(nil, "OVERLAY", template or "GameFontHighlightSmall")
    block:SetJustifyH("LEFT")
    block:SetWordWrap(true)
    block:SetText(text)
    section.blocks[#section.blocks + 1] = block
  end

  AddBlock("Sync Workflow 15 - Deferred Targeted Member Pull", "GameFontHighlight")

  AddBlock(
    "|cff4adf6bCurrent status - 11 August 2026|r\n" ..
    "Decision and safety contract: 100% complete. Existing precise Preview/Pull Member Repair: two-client live pass for member-newer Loot List leaves. Emergency broad Targeted Member Pull implementation: 0% by design. It remains deferred because the normal safe tool succeeded and no recovery gap currently justifies adding a destructive authority path. No automatic join, archive, checksum, readiness, or Targeted Host Override behavior is changed by this page."
  )

  AddBlock(
    "|cffa87ae8Requested operator workflow|r\n" ..
    "The host selects one currently joined member, selects familiar data scopes like the Targeted Host Override picker, reviews which selected scopes are actually newer on that member, and pulls the selected data into the host profile. The intended use is recovery of known newer member data, not routine two-way profile merging and not a transfer that runs automatically when someone joins."
  )

  AddBlock(
    "|cffa87ae8Existing foundation|r\n" ..
    "Most transport and import foundations already exist in Pull Member Repair and Preview Member Repair. The active host can request domain or precise leaf snapshots from one joined member; the member accepts the request only from the active session host; the host can preview nested differences; and a host-side undo snapshot is created before an accepted repair is imported. This path is now live-proven for selecting and pulling member-newer Loot List leaves while preserving unrelated data. A later emergency implementation would need a clearer operator contract over this proven path and must not create a reverse HOST_OVERRIDE authority marker."
  )

  AddBlock(
    "|cffa87ae8Why a literal reverse override is unsafe|r\n" ..
    "Host Override is authoritative because the active host is the source of truth for the shared raid profile. A member can be newer in one leaf while still missing sessions, items, awards, roles, or corrections retained by the host. Replacing an entire host scope from that member could delete valid host-only archive rows. A larger revision number is evidence that something changed, not proof that the member owns every row in the chapter. Current Session State is especially sensitive because replacing it could overwrite the host's canonical lobby/session identity and member state."
  )

  AddBlock(
    "|cffa87ae8Proposed safety contract|r\n" ..
    "1. Only the active host may initiate the pull, and the source must still be joined in the exact active session and group.\n" ..
    "2. Bind every request to session ID, source member, profile lineage, expected source revision/checksums, and selected scopes. Reject the response if that identity changes between comparison and transfer.\n" ..
    "3. Member-newer exact scopes may be selected. Equal scopes remain disabled. Diverged or inconclusive scopes require Preview and explicit row selection; unknown evidence fails closed.\n" ..
    "4. Current Session State is excluded from the normal picker. The host remains authoritative for P2.\n" ..
    "5. Loot List, Item History, Session History, and Fun Awards use precise leaf import or safe merge by default. Exact deletion-and-replacement is a separate destructive action and must not be implied by Pull.\n" ..
    "6. Complete Shared Profile is high risk and requires a fresh preview plus an explicit destructive confirmation.\n" ..
    "7. Create the local host undo snapshot before mutation, apply the selected operation atomically, retain acknowledgement/terminal status, then request fresh exact proof."
  )

  AddBlock(
    "|cffa87ae8Deferred implementation agenda|r\n" ..
    "[DEFERRED] Reuse the Targeted Host Override member/scope presentation but give it a separate Targeted Member Pull contract and descriptions.\n" ..
    "[DEFERRED] Route ordinary pulls through the existing PSYNC_REQ Pull Member Repair path; do not grant members host-authoritative archive replacement.\n" ..
    "[DEFERRED] Add eligibility states for Member newer, Diverged - preview required, Same - disabled, and Proof unknown - blocked.\n" ..
    "[DEFERRED] Bind request identity and reject source revision/checksum races before import.\n" ..
    "[DEFERRED] Preserve active session identity, add explicit archive merge semantics, and keep Current Session State unavailable.\n" ..
    "[DEFERRED] Add preflight coverage for authorization, selected-scope fidelity, changed-source rejection, divergent preview, equal-scope no-op, archive preservation, combat deferral, acknowledgement, post-apply proof, and undo.\n" ..
    "[DEFERRED LIVE TEST] Pull one genuinely member-newer leaf, verify unrelated host-only history survives, verify both clients converge afterward, then undo on the host and verify the original host checksum returns."
  )

  AddBlock(
    "|cffa87ae8Acceptance boundary|r\n" ..
    "The feature is acceptable only if it remains a manual, one-member, one-operation recovery action; sends no data for equal scopes; never changes P2 authority; cannot remove unrelated host archive rows during a normal pull; and finishes with bounded traffic, a visible receipt, a fresh checksum proof, and a usable host undo. Until those guarantees are implemented and tested, use the existing Preview Member Repair and Pull Member Repair tools."
  )

  section.Layout = function(self)
    local width = math.max(620, (self.frame:GetWidth() or 0) - 36)
    local y = -18
    for _, block in ipairs(self.blocks) do
      block:ClearAllPoints()
      block:SetPoint("TOPLEFT", 18, y)
      block:SetWidth(width)
      y = y - ((block.GetStringHeight and block:GetStringHeight()) or 0) - 18
    end
    local contentHeight = math.max(400, -y + 90)
    self.frame:SetHeight(contentHeight)
    if self.scroll and self.scroll.UpdateScrollChildRect then
      self.scroll:UpdateScrollChildRect()
    end
    return contentHeight
  end

  scroll:Hide()
  return section
end

local function CreateTutorialPossibleFeaturesSection(parent, createSection)
  local scroll, content, header, cards = createSection(parent, nil, {})
  local section = {
    scroll = scroll,
    frame = content,
    header = header,
    cards = cards,
    blocks = {},
  }

  local function AddBlock(text, template)
    local block = content:CreateFontString(nil, "OVERLAY", template or "GameFontHighlightSmall")
    block:SetJustifyH("LEFT")
    block:SetWordWrap(true)
    block:SetText(text)
    section.blocks[#section.blocks + 1] = block
  end

  AddBlock("Possible Features", "GameFontHighlight")

  AddBlock(
    "|cffffcc00Emergency Targeted Member Pull - not currently planned|r\n" ..
    "This is the member-to-host counterpart sometimes described as a reverse Targeted Host Override. The host would select one joined member and one or more data scopes, then deliberately import that member's package even when LidCouncil cannot prove that the member owns the newer or more complete copy. It would be an emergency recovery tool, not part of automatic synchronization."
  )

  AddBlock(
    "|cffa87ae8Intended purpose|r\n" ..
    "The feature would cover a rare operator-driven recovery: the normal Leaf Scope Picker or precise Preview Member Repair cannot complete a repair, but the raid team has independently inspected the member's copy through Discord, a stream, or another external comparison and deliberately chooses that copy. It could provide a fast live-session fallback when a known-good member holds data the host has lost."
  )

  AddBlock(
    "|cffa87ae8Why the normal tool remains preferred|r\n" ..
    "The Leaf Scope Picker already identifies exact differences and can pull only the selected member values while preserving unrelated host data. Live testing confirmed that notes and main-character changes can be discovered, previewed, and imported successfully. Profile Transfer is the safer broad fallback because its portable packages are checksummed, schema-checked, applied transactionally, and backed up locally before mutation. Together these paths currently cover the demonstrated recovery needs without adding a second destructive authority mechanism."
  )

  AddBlock(
    "|cffa87ae8Main risks|r\n" ..
    "1. Host Override and member pull are not symmetrical: the host is authoritative, while a member may be newer in one leaf but incomplete elsewhere.\n" ..
    "2. A broad member package could delete valid host-only Session History, Item History, Loot List, Fun Award, roster, or configuration records.\n" ..
    "3. Revision order proves that something changed; it does not prove that the member owns a complete chapter.\n" ..
    "4. Pulling Current Session State could replace the host's canonical P2 lobby/session identity.\n" ..
    "5. A member can change between external review and transfer, making the reviewed package different from the received package.\n" ..
    "6. A convenient emergency button could be mistaken for routine synchronization and bypass the precise evidence that currently protects the host profile."
  )

  AddBlock(
    "|cffa87ae8Why it is deferred|r\n" ..
    "There is currently no reproduced recovery gap that requires this risk. The designed precise pull completed the requested live repair, and Profile Transfer remains available for a deliberately broad or offline restore. Implementing an evidence-bypassing live import now would increase the mutation, authorization, undo, and test surface while the safer tools already solve the observed cases. The feature therefore remains documented but unimplemented."
  )

  AddBlock(
    "|cffa87ae8Reasons to reconsider|r\n" ..
    "Reconsider only after a reproducible incident shows that both precise member repair and Profile Transfer cannot recover a known-good member copy, or repeated real raids establish a practical need for immediate live recovery. Before implementation, require a durable host backup and tested restore, exact binding to session/member/profile/revision/checksums/scopes, rejection when the source changes, no normal access to Current Session State, archive merge by default, a separate explicit confirmation for exact replacement, bounded acknowledged transport, post-import checksum proof, comprehensive preflight coverage, and a two-client destructive-recovery rehearsal."
  )

  AddBlock(
    "|cffffcc00Manual Raid Item Entry - deferred|r\n" ..
    "Add an Add Item action beside Manual Adjustment. The operator would select a character, search the configured raid's Adventure Guide loot, choose an exact item, and either accept the profile's resolved point value or enter points manually. The result would be a real Loot List item entry rather than a note-style manual adjustment."
  )

  AddBlock(
    "|cffa87ae8Why it is feasible but not a release-safe quick change|r\n" ..
    "The addon already has raid and boss loot collectors, an item-aware ledger entry schema, and the normal point resolver. A correctly implemented entry would change only the selected character's Loot List leaf; it would not require a new Session History or Item History protocol. However, the current manual-adjustment request deliberately transports only a comment and points and marks the result as manual. A real item action must also preserve item ID/link/variant, icon, quality, item level, award provenance, point behavior, permissions, and a stable request identity so retries cannot create duplicate awards. WoW item data may also arrive asynchronously. Reusing the note path or directly editing the saved table would risk incorrect item counts, persistent Loot List checksum differences, or duplicate entries."
  )

  AddBlock(
    "|cffa87ae8Safe future implementation boundary|r\n" ..
    "Reuse the existing Adventure Guide loot collection and AddLootLedgerEntry API, but give real item insertion its own host-routed, acknowledged and idempotent operation. Keep the first version host-only, default to manually confirmed points, and offer automatic points only through the existing profile point resolver. Synchronize only the affected Loot List character leaf, refresh exact proof afterward, and add preflight coverage for item-cache delay, exact item metadata, manual and automatic points, permission rejection, retry deduplication, and two-client convergence. Until that work can be tested without release pressure, use Manual Adjustment notes and points."
  )

  AddBlock(
    "|cffffcc00Update Profile Raid Catalog - deferred|r\n" ..
    "Profiles intentionally freeze the Adventure Guide raid list that exists when the profile is created. This protects an old season profile from silently turning into a later season or expansion. The tradeoff is that a raid added later to the same Adventure Guide tier is not automatically available to an existing profile unless Blizzard already exposed that raid when the profile snapshot was captured."
  )

  AddBlock(
    "|cffa87ae8Safe future implementation boundary|r\n" ..
    "Provide an explicit Update Profile Raid Catalog action that previews newly discovered raids and appends only their unique instance IDs to the existing frozen season snapshot. It must never remove or replace stored raids, reinterpret the profile as a different tier, change the selected raid, rewrite point settings, or alter Loot List, Item History, Session History, Fun Awards, or live-session data. Refuse the update when the current Adventure Guide tier differs from the profile tier and direct the operator to create a new profile for the new season instead. A successful append must touch the Settings revision, synchronize the updated snapshot normally, and finish with fresh checksum proof. Add migration diagnostics for legacy profiles that have no valid snapshot, plus preflight coverage for no-op refresh, same-tier append, duplicate prevention, different-tier rejection, sync convergence, and unchanged historical data."
  )

  AddBlock(
    "|cffffcc00Adventure Guide LidTable Item-Level Discovery - deferred|r\n" ..
    "Add an explicit Auto-fill Item Levels action to Profile > Points after a profile and its frozen raid catalog already exist. The action would scan the profile-season raids across Normal, Heroic, and Mythic, collect every distinct displayed item level, sort the result, and preview the proposed LidTable mapping before the operator applies it. This avoids hard-coding every future raid tier while keeping item-level point configuration deliberate rather than silently changing when the Adventure Guide updates."
  )

  AddBlock(
    "|cffa87ae8Design choices and risks|r\n" ..
    "LidTable currently resolves an item only when its item level exactly equals one configured row, so an incomplete scan can incorrectly award zero points. Adventure Guide item data may still be loading, very-rare drops can add extra levels, and scanning all raids and difficulties during Create Profile could cause a visible pause. Do not replace valid rows with placeholders or force profile creation to depend on a successful scan. Keep safe season defaults as the initial configuration, then let the operator run discovery once data is available. Preserve the existing set-piece, off-spec, transmog, and disenchant values while replacing only the item-level mapping."
  )

  AddBlock(
    "|cffa87ae8Preferred future model|r\n" ..
    "Use a variable number of LidTable rows based on the detected distinct item levels instead of manufacturing unused placeholder levels. Keep a defensive maximum such as 20 rows, report and refuse silent truncation when the scan exceeds it, and assign the configured point progression in a clearly previewed order. Do not create separate Custom Point Rules for every discovered level: those rules duplicate LidTable's exact-level job, create two competing sources of truth, and make later editing and synchronization harder. Apply the final table as one Settings mutation, synchronize it normally, and require fresh checksum proof. Preflight must cover incomplete item cache, duplicate levels, normal/heroic/mythic collection, very-rare levels, ordering, overflow, preservation of special point columns, no-op repeat scans, and two-client convergence."
  )

  AddBlock(
    "|cffffcc00Roll-Only Mode (ECO) - possible fallback feature|r\n" ..
    "Provide a deliberately reduced session mode for raids where full shared-profile synchronization is unavailable, too slow, or impractical to validate with a large test group. Members would join without receiving or saving the host's full profile. They would receive only the information required to open loot rolls, submit choices, receive confirmation, and see the result. Loot distribution and the authoritative Loot List would remain on the host."
  )

  AddBlock(
    "|cffa87ae8Minimal session contract|r\n" ..
    "When the lobby opens, the host would send each member a small, temporary, receipt-tracked roll contract containing the session and host identity, protocol/build compatibility, available choice labels and icons, permitted roll actions, and the rules or projected points needed by the roll window. A member becomes Roll-Only Ready after confirming that contract. Normal ITEM_OPEN, choice, acknowledgement, and award messages can then operate without installing the full shared profile. Late joiners receive the same contract plus any currently open items."
  )

  AddBlock(
    "|cffa87ae8Data deliberately excluded|r\n" ..
    "Do not send full profile snapshots, profile checksums, Settings and Permissions synchronization, Item History, Session History, Fun Awards, bulk P3 archive data, detailed checksum comparison, or archive repair. Do not synchronize the full Loot List after every award. The host remains authoritative and sends only a compact award result and, when required by the point model, the affected member's new balance or point delta."
  )

  AddBlock(
    "|cffa87ae8Safe first-version boundary|r\n" ..
    "Choose Roll-Only Mode when creating the lobby and keep it immutable after the session starts. The first version should be host-controlled, disable council voting and host migration, keep member data temporary, avoid persistent member profile/archive writes, and clearly label readiness as Roll-Only Ready rather than Fully Synced. Incompatible clients must fail clearly. Do not silently enter ECO mode when normal synchronization encounters a problem; the operator must select it deliberately. Automatic fallback, mixed full/ECO membership, council distribution, and host transfer should remain separate later designs because each restores synchronization and authority complexity."
  )

  AddBlock(
    "|cffa87ae8Why this should not be a simple sync-off switch|r\n" ..
    "The roll window still depends on profile-defined choices, permissions, point behavior, and session identity. Disabling profile synchronization without replacing those dependencies would create ambiguous rules and incompatible choices. The temporary roll contract is therefore the core safety requirement: it makes the small amount of required state explicit, acknowledged, versioned, and testable while leaving bulk and historical data behind."
  )

  AddBlock(
    "|cffffcc00Before starting this feature, verify wether a Profile sync still makes sense to provide hosting by multiple people|r"
  )

  AddBlock(
    "|cff8bdc9aCurrent decision|r\n" ..
    "Use Leaf Scope Picker / Preview Member Repair first. Use Profile Transfer for a known broad backup or offline restore. Keep Emergency Targeted Member Pull as a future contingency, not as a hidden or automatic sync path. Continue using Manual Adjustment for operator-entered Loot List corrections; defer real raid-item insertion until its item-aware transport and two-client tests can be completed. Keep season snapshots immutable today; consider only the explicit append-only raid-catalog update for a later same-tier raid addition, never an automatic season rollover. For the immediate raid release, verify and update the shipped LidTable item-level defaults manually; defer Adventure Guide discovery until its cache, preview, variable-row, overflow, and convergence safeguards can be implemented and tested. Keep Roll-Only Mode documented as an explicit fallback design, not an automatic response to a normal synchronization delay."
  )

  section.Layout = function(self)
    local width = math.max(620, (self.frame:GetWidth() or 0) - 36)
    local y = -18
    for _, block in ipairs(self.blocks) do
      block:ClearAllPoints()
      block:SetPoint("TOPLEFT", 18, y)
      block:SetWidth(width)
      y = y - ((block.GetStringHeight and block:GetStringHeight()) or 0) - 18
    end
    local contentHeight = math.max(400, -y + 90)
    self.frame:SetHeight(contentHeight)
    if self.scroll and self.scroll.UpdateScrollChildRect then
      self.scroll:UpdateScrollChildRect()
    end
    return contentHeight
  end

  scroll:Hide()
  return section
end

local function CreateTutorialProfileRecoverySection(parent, createSection)
  local scroll, content, header, cards = createSection(parent, nil, {})
  local section = {
    scroll = scroll,
    frame = content,
    header = header,
    cards = cards,
  }

  section.intro = content:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
  section.intro:SetJustifyH("LEFT")
  section.intro:SetText("Profile Recovery")

  section.purpose = content:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  section.purpose:SetJustifyH("LEFT")
  section.purpose:SetWordWrap(true)
  section.purpose:SetText(
    "|cffa87ae8Purpose|r\n" ..
    "LidCouncil provides two independent recovery tools for shared-profile gaps. Profile Transfer creates portable text packages for offline or pre-session exchange. Targeted Host Override replaces selected data on one joined member through the normal acknowledged synchronization transport. Neither tool replaces routine automatic synchronization or the precise-difference workflow."
  )

  section.order = content:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  section.order:SetJustifyH("LEFT")
  section.order:SetWordWrap(true)
  section.order:SetText(
    "|cffa87ae8Safe recovery order|r\n" ..
    "1. Let normal P1, P2, and P3 synchronization finish.\n" ..
    "2. In Sync Management, load precise differences and follow the highlighted safe Push, Preview, or Pull action for the conclusive scopes.\n" ..
    "3. Use Targeted Host Override only when the host intentionally needs to replace known sections on one joined member.\n" ..
    "4. Use Profile Transfer when the recipient is offline, before a large first sync, or when addon-channel delivery is impractical.\n" ..
    "5. Verify the affected page, then use Refresh Readiness or fresh precise differences to confirm the resulting checksum state."
  )

  section.packages = content:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  section.packages:SetJustifyH("LEFT")
  section.packages:SetWordWrap(true)
  section.packages:SetText(
    "|cffa87ae8Profile Transfer packages - manual and offline|r\n" ..
    "Open Profile, select the profile, then open Transfer. Choose one or more content sections and create the export package. Copy every numbered part in order. On the receiving client, paste the parts into the collector, inspect the completed package, select the identity-safe target, and apply it.\n\n" ..
    "Packages are checksummed, schema-checked, multipart-safe, dependency-aware, and applied transactionally. A local recovery backup is created before mutation. Import is blocked during combat or an active session. Complete Shared Profile is the broadest option; smaller packages reduce copy size and overwrite only their declared sections."
  )

  section.override = content:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  section.override:SetJustifyH("LEFT")
  section.override:SetWordWrap(true)
  section.override:SetText(
    "|cffa87ae8Targeted Host Override - live and acknowledged|r\n" ..
    "The active session host opens Session, Sync Management, and Targeted Host Override. Select one currently joined member, select the sections to replace, review dependency warnings, and confirm. The receiver automatically accepts a valid override only from the active session host and applies it after combat when necessary.\n\n" ..
    "The panel reports Queued, Sending, Awaiting receipt, Awaiting automatic apply, Recovery active, Receipt confirmed, or Failed for every selected section. A second override to the same member is blocked while the first operation is active. Completion and terminal failure unlock a deliberate retry."
  )

  section.scope = content:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  section.scope:SetJustifyH("LEFT")
  section.scope:SetWordWrap(true)
  section.scope:SetText(
    "|cffa87ae8Available override sections|r\n" ..
    "- Complete Shared Profile: every shared section.\n" ..
    "- Profile Setup: raid setup, image, roll options, and point configuration.\n" ..
    "- Roles & Rights: members, roles, and rights policy.\n" ..
    "- Loot List and Character Details: characters, links, notes, awards, and adjustments.\n" ..
    "- Item History: Live Board items, assignments, and trade history.\n" ..
    "- Session History: completed and retained session records.\n" ..
    "- Fun Award Events: canonical real roll and council-vote events. Include Loot List when repairing awards unless the exact missing scope is already known."
  )

  section.safety = content:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  section.safety:SetJustifyH("LEFT")
  section.safety:SetWordWrap(true)
  section.safety:SetText(
    "|cffa87ae8Safety rules|r\n" ..
    "- Both tools overwrite the selected destination data; confirm the source profile and target character first.\n" ..
    "- Do not exchange packages between mismatched addon versions unless compatibility has been verified.\n" ..
    "- Do not use an override merely because P3 is still moving; background history can remain incomplete while the raid is already roll-ready.\n" ..
    "- A receipt proves transport and apply completion. It does not replace a final visual or checksum comparison.\n" ..
    "- If an override fails, read its per-section status and Sync Monitor diagnostic before retrying."
  )

  section.Layout = function(self)
    local width = math.max(620, (self.frame:GetWidth() or 0) - 36)
    local y = -18
    for _, fontString in ipairs({
      self.intro,
      self.purpose,
      self.order,
      self.packages,
      self.override,
      self.scope,
      self.safety,
    }) do
      fontString:ClearAllPoints()
      fontString:SetPoint("TOPLEFT", 18, y)
      fontString:SetWidth(width)
      y = y - ((fontString.GetStringHeight and fontString:GetStringHeight()) or 0) - 18
    end
    local contentHeight = math.max(400, -y + 90)
    self.frame:SetHeight(contentHeight)
    if self.scroll and self.scroll.UpdateScrollChildRect then
      self.scroll:UpdateScrollChildRect()
    end
    return contentHeight
  end

  scroll:Hide()
  return section
end

local function CreateTutorialPreflightSection(parent, createSection)
  local scroll, content, header, cards = createSection(parent, nil, {})
  local section = {
    scroll = scroll,
    frame = content,
    header = header,
    cards = cards,
  }

  section.intro = content:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
  section.intro:SetJustifyH("LEFT")
  section.intro:SetText("Preflight")

  section.purpose = content:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  section.purpose:SetJustifyH("LEFT")
  section.purpose:SetWordWrap(true)
  section.purpose:SetText(
    "|cffa87ae8Purpose|r\n" ..
    "Release Preflight is LidCouncil's local automated code-health check before a raid test or release build. It runs inside your WoW client without requiring a party, raid, or second player. " ..
    "The profile-sync report currently uses internal validation revision |cff4adf6bv87|r; this is a test-suite revision, not the LidCouncil addon version. The nearby Synthetic Sync Rehearsal is a separate tool that creates fake raiders and estimates load; a rehearsal result is not a Preflight pass."
  )

  section.safety = content:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  section.safety:SetJustifyH("LEFT")
  section.safety:SetWordWrap(true)
  section.safety:SetText(
    "|cffa87ae8Before running it|r\n" ..
    "1. Leave combat.\n" ..
    "2. Stop or close every prepared or active LidCouncil session.\n" ..
    "3. Wait until the communication queue and active transfers are idle.\n" ..
    "4. Keep the profile you want to validate available locally.\n" ..
    "Preflight blocks itself instead of running when combat, a session, or synchronization traffic could make the result unsafe or misleading."
  )

  section.steps = content:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  section.steps:SetJustifyH("LEFT")
  section.steps:SetWordWrap(true)
  section.steps:SetText(
    "|cffa87ae8How to run Preflight|r\n" ..
    "1. Open the LidCouncil Editor.\n" ..
    "2. Open |cff4adf6bAbout|r, then select |cff4adf6bDeveloper Mode|r.\n" ..
    "3. Enable |cff4adf6bActivate Developer Mode|r.\n" ..
    "4. Scroll to the |cff4adf6bSync Harness|r section.\n" ..
    "5. Under |cff4adf6bRelease Preflight|r, click |cff4adf6bRun Preflight|r.\n" ..
    "6. Read the result card and detailed report below the controls. Use |cff4adf6bCopy Report|r to open a selectable report, then press |cff4adf6bCtrl+C|r. |cff4adf6bClear Last Result|r removes only the Preflight report; |cff4adf6bClear Rehearsal|r separately removes synthetic harness state."
  )

  section.coverage = content:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  section.coverage:SetJustifyH("LEFT")
  section.coverage:SetWordWrap(true)
  section.coverage:SetText(
    "|cffa87ae8What Preflight validates|r\n" ..
    "- Startup health and required API/helper registration.\n" ..
    "- Communication protocol field contracts and safe payload boundaries.\n" ..
    "- Outbound transport ACK loss, late ACK recovery, retry budgets and terminal failures.\n" ..
    "- Profile synchronization authority, revisions, checksums, precise repair and convergence rules.\n" ..
    "- Deterministic 20-member and 30-member five-item live-loot bursts: payload-safe ITEM_REPORT/ITEM_OPEN/CHOICE packets, per-member item receipts, idempotent member-choice replay, and council mirror receipts with bounded targeted repair.\n" ..
    "- Authoritative replacement and checksum convergence for a generated 70-session archive.\n" ..
    "- Real Simulation vote exclusion, asymmetric combat staging, and release-preflight aggregation."
  )

  section.dataSafety = content:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  section.dataSafety:SetJustifyH("LEFT")
  section.dataSafety:SetWordWrap(true)
  section.dataSafety:SetText(
    "|cffa87ae8Data safety|r\n" ..
    "Validation uses deterministic in-memory fixtures and hidden temporary profile copies. The 70-session archive test records the current profile's session count, performs the authoritative replacement on scratch copies, verifies convergence, and removes both copies afterward. " ..
    "A failed cleanup or any change to the active profile is itself reported as a failed check."
  )

  section.results = content:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  section.results:SetJustifyH("LEFT")
  section.results:SetWordWrap(true)
  section.results:SetText(
    "|cffa87ae8How to read the result|r\n" ..
    "|cff4adf6bPASS|r - Every required suite completed successfully.\n" ..
    "|cffffd34ePASS WITH WARNINGS|r - Required checks passed, but one or more suites were slow or reported a non-blocking warning.\n" ..
    "|cffff6b5cFAIL|r - At least one invariant failed. Do not use that result as release evidence; record the failed suite/check.\n" ..
    "|cffb8b8c8BLOCKED|r - Preflight did not run because combat, an active session, pending communication, cooldown, or another safety gate was active. Resolve the stated reason and retry."
  )

  section.limits = content:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  section.limits:SetJustifyH("LEFT")
  section.limits:SetWordWrap(true)
  section.limits:SetText(
    "|cffa87ae8What Preflight cannot prove|r\n" ..
    "Preflight proves deterministic code paths and data convergence inside one client. It does not reproduce Blizzard addon-channel scheduling between real computers, raid-wide throttling, latency, disconnects, frame-rate differences, or mixed client versions. " ..
    "A clean result is the gate before live testing, not a replacement for the planned 2-player, 10-player, and 20-player sessions."
  )

  section.Layout = function(self)
    local width = math.max(620, (self.frame:GetWidth() or 0) - 36)
    local y = -18
    for _, fontString in ipairs({
      self.intro,
      self.purpose,
      self.safety,
      self.steps,
      self.coverage,
      self.dataSafety,
      self.results,
      self.limits,
    }) do
      fontString:ClearAllPoints()
      fontString:SetPoint("TOPLEFT", 18, y)
      fontString:SetWidth(width)
      y = y - ((fontString.GetStringHeight and fontString:GetStringHeight()) or 0) - 18
    end
    local contentHeight = math.max(400, -y + 90)
    self.frame:SetHeight(contentHeight)
    if self.scroll and self.scroll.UpdateScrollChildRect then
      self.scroll:UpdateScrollChildRect()
    end
    return contentHeight
  end

  scroll:Hide()
  return section
end

local function BuildTutorialPage(editor, tutorialPage)
  if not editor or not tutorialPage then return end

  local tutorialNavBg = CreateFrame("Frame", nil, tutorialPage, "BackdropTemplate")
  tutorialNavBg:SetPoint("TOPLEFT", 0, 0)
  tutorialNavBg:SetPoint("BOTTOMLEFT", 0, 0)
  tutorialNavBg:SetWidth(240)
  ApplyFrameSkin(tutorialNavBg)
  editor.tutorialNavBg = tutorialNavBg

  local tutNavBeginnerBtn = CreateFrame("Button", nil, tutorialNavBg, "UIPanelButtonTemplate")
  tutNavBeginnerBtn:SetSize(200, 20)
  tutNavBeginnerBtn:SetPoint("TOPLEFT", 6, -18)
  tutNavBeginnerBtn:SetText("Beginner Guide")
  tutNavBeginnerBtn:SetScript("OnClick", function()
    if editor and editor.ShowTutorialSection then editor:ShowTutorialSection("Beginner") end
  end)
  editor.tutorialNavBeginnerBtn = tutNavBeginnerBtn

  local tutNavProfilesBtn = CreateFrame("Button", nil, tutorialNavBg, "UIPanelButtonTemplate")
  tutNavProfilesBtn:SetSize(200, 20)
  tutNavProfilesBtn:SetPoint("TOPLEFT", 6, -44)
  tutNavProfilesBtn:SetText("Profiles")
  tutNavProfilesBtn:SetScript("OnClick", function()
    if editor and editor.ShowTutorialSection then editor:ShowTutorialSection("Profiles") end
  end)
  editor.tutorialNavProfilesBtn = tutNavProfilesBtn

  local tutNavPanelsBtn = CreateFrame("Button", nil, tutorialNavBg, "UIPanelButtonTemplate")
  tutNavPanelsBtn:SetSize(200, 20)
  tutNavPanelsBtn:SetPoint("TOPLEFT", 6, -70)
  tutNavPanelsBtn:SetText("Session")
  tutNavPanelsBtn:SetScript("OnClick", function()
    if editor and editor.ShowTutorialSection then editor:ShowTutorialSection("Panels") end
  end)
  editor.tutorialNavPanelsBtn = tutNavPanelsBtn

  local tutNavCustomBtn = CreateFrame("Button", nil, tutorialNavBg, "UIPanelButtonTemplate")
  tutNavCustomBtn:SetSize(200, 20)
  tutNavCustomBtn:SetPoint("TOPLEFT", 6, -96)
  tutNavCustomBtn:SetText("Live Board")
  tutNavCustomBtn:SetScript("OnClick", function()
    if editor and editor.ShowTutorialSection then editor:ShowTutorialSection("Custom") end
  end)
  editor.tutorialNavCustomBtn = tutNavCustomBtn

  local tutNavSimulationBtn = CreateFrame("Button", nil, tutorialNavBg, "UIPanelButtonTemplate")
  tutNavSimulationBtn:SetSize(200, 20)
  tutNavSimulationBtn:SetPoint("TOPLEFT", 6, -122)
  tutNavSimulationBtn:SetText("Run Simulation")
  tutNavSimulationBtn:SetScript("OnClick", function()
    if editor and editor.ShowTutorialSection then editor:ShowTutorialSection("RunSimulation") end
  end)
  editor.tutorialNavSimulationBtn = tutNavSimulationBtn

  local tutNavAddFolderBtn = CreateFrame("Button", nil, tutorialNavBg, "UIPanelButtonTemplate")
  tutNavAddFolderBtn:SetSize(200, 20)
  tutNavAddFolderBtn:SetPoint("TOPLEFT", 6, -148)
  tutNavAddFolderBtn:SetText("Loot List")
  tutNavAddFolderBtn:SetScript("OnClick", function()
    if editor and editor.ShowTutorialSection then editor:ShowTutorialSection("AddFolder") end
  end)
  editor.tutorialNavAddFolderBtn = tutNavAddFolderBtn

  editor.tutorialNavGroupsBtn = nil

  local tutNavWorkflowBtn = CreateFrame("Button", nil, tutorialNavBg, "UIPanelButtonTemplate")
  tutNavWorkflowBtn:SetSize(200, 20)
  tutNavWorkflowBtn:SetPoint("TOPLEFT", 6, -174)
  tutNavWorkflowBtn:SetText("LC Workflow")
  tutNavWorkflowBtn:SetScript("OnClick", function()
    if editor and editor.ShowTutorialSection then editor:ShowTutorialSection("Workflow") end
  end)
  editor.tutorialNavWorkflowBtn = tutNavWorkflowBtn

  local tutNavAutoPassBtn = CreateFrame("Button", nil, tutorialNavBg, "UIPanelButtonTemplate")
  tutNavAutoPassBtn:SetSize(200, 20)
  tutNavAutoPassBtn:SetPoint("TOPLEFT", 6, -200)
  tutNavAutoPassBtn:SetText("Auto Pass")
  tutNavAutoPassBtn:SetScript("OnClick", function()
    if editor and editor.ShowTutorialSection then editor:ShowTutorialSection("AutoPass") end
  end)
  editor.tutorialNavAutoPassBtn = tutNavAutoPassBtn

  local tutNavCombatBtn = CreateFrame("Button", nil, tutorialNavBg, "UIPanelButtonTemplate")
  tutNavCombatBtn:SetSize(200, 20)
  tutNavCombatBtn:SetPoint("TOPLEFT", 6, -226)
  tutNavCombatBtn:SetText("Combat Restrictions")
  tutNavCombatBtn:SetScript("OnClick", function()
    if editor and editor.ShowTutorialSection then editor:ShowTutorialSection("CombatRestrictions") end
  end)
  editor.tutorialNavCombatBtn = tutNavCombatBtn

  local tutNavSyncBtn = CreateFrame("Button", nil, tutorialNavBg, "UIPanelButtonTemplate")
  tutNavSyncBtn:SetSize(200, 20)
  tutNavSyncBtn:SetPoint("TOPLEFT", 6, -252)
  tutNavSyncBtn:SetText("Sync Workflow")
  tutNavSyncBtn:SetScript("OnClick", function()
    if editor and editor.ShowTutorialSection then editor:ShowTutorialSection("SyncWorkflow") end
  end)
  editor.tutorialNavSyncBtn = tutNavSyncBtn

  local tutNavSyncPriorityBtn = CreateFrame("Button", nil, tutorialNavBg, "UIPanelButtonTemplate")
  tutNavSyncPriorityBtn:SetSize(200, 20)
  tutNavSyncPriorityBtn:SetPoint("TOPLEFT", 6, -278)
  tutNavSyncPriorityBtn:SetText("Sync Priority")
  tutNavSyncPriorityBtn:SetScript("OnClick", function()
    if editor and editor.ShowTutorialSection then editor:ShowTutorialSection("SyncPriority") end
  end)
  editor.tutorialNavSyncPriorityBtn = tutNavSyncPriorityBtn

  editor.tutorialNavSyncAuditBtn = CreateFrame("Button", nil, tutorialNavBg, "UIPanelButtonTemplate")
  editor.tutorialNavSyncAuditBtn:SetSize(200, 20)
  editor.tutorialNavSyncAuditBtn:SetPoint("TOPLEFT", 6, -304)
  editor.tutorialNavSyncAuditBtn:SetText("Sync Workflow 2")
  editor.tutorialNavSyncAuditBtn:SetScript("OnClick", function()
    if editor and editor.ShowTutorialSection then editor:ShowTutorialSection("SyncWorkflow2") end
  end)

  editor.tutorialNavSyncProgressBtn = CreateFrame("Button", nil, tutorialNavBg, "UIPanelButtonTemplate")
  editor.tutorialNavSyncProgressBtn:SetSize(200, 20)
  editor.tutorialNavSyncProgressBtn:SetPoint("TOPLEFT", 6, -330)
  editor.tutorialNavSyncProgressBtn:SetText("Sync Workflow 3")
  editor.tutorialNavSyncProgressBtn:SetScript("OnClick", function()
    if editor and editor.ShowTutorialSection then editor:ShowTutorialSection("SyncWorkflow3") end
  end)

  editor.tutorialNavSyncWorkflow4Btn = CreateFrame("Button", nil, tutorialNavBg, "UIPanelButtonTemplate")
  editor.tutorialNavSyncWorkflow4Btn:SetSize(200, 20)
  editor.tutorialNavSyncWorkflow4Btn:SetPoint("TOPLEFT", 6, -356)
  editor.tutorialNavSyncWorkflow4Btn:SetText("Sync Workflow 4")
  editor.tutorialNavSyncWorkflow4Btn:SetScript("OnClick", function()
    if editor and editor.ShowTutorialSection then editor:ShowTutorialSection("SyncWorkflow4") end
  end)

  editor.tutorialNavSyncWorkflow5Btn = CreateFrame("Button", nil, tutorialNavBg, "UIPanelButtonTemplate")
  editor.tutorialNavSyncWorkflow5Btn:SetSize(200, 20)
  editor.tutorialNavSyncWorkflow5Btn:SetPoint("TOPLEFT", 6, -382)
  editor.tutorialNavSyncWorkflow5Btn:SetText("Sync Workflow 5")
  editor.tutorialNavSyncWorkflow5Btn:SetScript("OnClick", function()
    if editor and editor.ShowTutorialSection then editor:ShowTutorialSection("SyncWorkflow5") end
  end)

  editor.tutorialNavSyncWorkflow6Btn = CreateFrame("Button", nil, tutorialNavBg, "UIPanelButtonTemplate")
  editor.tutorialNavSyncWorkflow6Btn:SetSize(200, 20)
  editor.tutorialNavSyncWorkflow6Btn:SetPoint("TOPLEFT", 6, -408)
  editor.tutorialNavSyncWorkflow6Btn:SetText("Sync Workflow 6")
  editor.tutorialNavSyncWorkflow6Btn:SetScript("OnClick", function()
    if editor and editor.ShowTutorialSection then editor:ShowTutorialSection("SyncWorkflow6") end
  end)

  editor.tutorialNavSyncWorkflow7Btn = CreateFrame("Button", nil, tutorialNavBg, "UIPanelButtonTemplate")
  editor.tutorialNavSyncWorkflow7Btn:SetSize(200, 20)
  editor.tutorialNavSyncWorkflow7Btn:SetPoint("TOPLEFT", 6, -434)
  editor.tutorialNavSyncWorkflow7Btn:SetText("Sync Workflow 7")
  editor.tutorialNavSyncWorkflow7Btn:SetScript("OnClick", function()
    if editor and editor.ShowTutorialSection then editor:ShowTutorialSection("SyncWorkflow7") end
  end)

  editor.tutorialNavSyncWorkflow8Btn = CreateFrame("Button", nil, tutorialNavBg, "UIPanelButtonTemplate")
  editor.tutorialNavSyncWorkflow8Btn:SetSize(200, 20)
  editor.tutorialNavSyncWorkflow8Btn:SetPoint("TOPLEFT", 6, -460)
  editor.tutorialNavSyncWorkflow8Btn:SetText("Sync Workflow 8")
  editor.tutorialNavSyncWorkflow8Btn:SetScript("OnClick", function()
    if editor and editor.ShowTutorialSection then editor:ShowTutorialSection("SyncWorkflow8") end
  end)

  editor.tutorialNavSyncWorkflow10Btn = CreateFrame("Button", nil, tutorialNavBg, "UIPanelButtonTemplate")
  editor.tutorialNavSyncWorkflow10Btn:SetSize(200, 20)
  editor.tutorialNavSyncWorkflow10Btn:SetPoint("TOPLEFT", 6, -486)
  editor.tutorialNavSyncWorkflow10Btn:SetText("Sync Workflow 10")
  editor.tutorialNavSyncWorkflow10Btn:SetScript("OnClick", function()
    if editor and editor.ShowTutorialSection then editor:ShowTutorialSection("SyncWorkflow10") end
  end)

  editor.tutorialNavSyncWorkflow11Btn = CreateFrame("Button", nil, tutorialNavBg, "UIPanelButtonTemplate")
  editor.tutorialNavSyncWorkflow11Btn:SetSize(200, 20)
  editor.tutorialNavSyncWorkflow11Btn:SetPoint("TOPLEFT", 6, -512)
  editor.tutorialNavSyncWorkflow11Btn:SetText("Sync Workflow 11")
  editor.tutorialNavSyncWorkflow11Btn:SetScript("OnClick", function()
    if editor and editor.ShowTutorialSection then editor:ShowTutorialSection("SyncWorkflow11") end
  end)

  editor.tutorialNavSyncWorkflow12Btn = CreateFrame("Button", nil, tutorialNavBg, "UIPanelButtonTemplate")
  editor.tutorialNavSyncWorkflow12Btn:SetSize(200, 20)
  editor.tutorialNavSyncWorkflow12Btn:SetPoint("TOPLEFT", 6, -538)
  editor.tutorialNavSyncWorkflow12Btn:SetText("Sync Workflow 12")
  editor.tutorialNavSyncWorkflow12Btn:SetScript("OnClick", function()
    if editor and editor.ShowTutorialSection then editor:ShowTutorialSection("SyncWorkflow12") end
  end)

  editor.tutorialNavSyncWorkflow13Btn = CreateFrame("Button", nil, tutorialNavBg, "UIPanelButtonTemplate")
  editor.tutorialNavSyncWorkflow13Btn:SetSize(200, 20)
  editor.tutorialNavSyncWorkflow13Btn:SetPoint("TOPLEFT", 6, -564)
  editor.tutorialNavSyncWorkflow13Btn:SetText("Sync Workflow 13")
  editor.tutorialNavSyncWorkflow13Btn:SetScript("OnClick", function()
    if editor and editor.ShowTutorialSection then editor:ShowTutorialSection("SyncWorkflow13") end
  end)

  editor.tutorialNavSyncWorkflow14Btn = CreateFrame("Button", nil, tutorialNavBg, "UIPanelButtonTemplate")
  editor.tutorialNavSyncWorkflow14Btn:SetSize(200, 20)
  editor.tutorialNavSyncWorkflow14Btn:SetPoint("TOPLEFT", 6, -590)
  editor.tutorialNavSyncWorkflow14Btn:SetText("Sync Workflow 14")
  editor.tutorialNavSyncWorkflow14Btn:SetScript("OnClick", function()
    if editor and editor.ShowTutorialSection then editor:ShowTutorialSection("SyncWorkflow14") end
  end)

  editor.tutorialNavSyncWorkflow15Btn = CreateFrame("Button", nil, tutorialNavBg, "UIPanelButtonTemplate")
  editor.tutorialNavSyncWorkflow15Btn:SetSize(200, 20)
  editor.tutorialNavSyncWorkflow15Btn:SetPoint("TOPLEFT", 6, -616)
  editor.tutorialNavSyncWorkflow15Btn:SetText("Sync Workflow 15")
  editor.tutorialNavSyncWorkflow15Btn:SetScript("OnClick", function()
    if editor and editor.ShowTutorialSection then editor:ShowTutorialSection("SyncWorkflow15") end
  end)

  editor.tutorialNavPossibleFeaturesBtn = CreateFrame("Button", nil, tutorialNavBg, "UIPanelButtonTemplate")
  editor.tutorialNavPossibleFeaturesBtn:SetSize(200, 20)
  editor.tutorialNavPossibleFeaturesBtn:SetPoint("TOPLEFT", 6, -642)
  editor.tutorialNavPossibleFeaturesBtn:SetText("Possible Features")
  editor.tutorialNavPossibleFeaturesBtn:SetScript("OnClick", function()
    if editor and editor.ShowTutorialSection then editor:ShowTutorialSection("PossibleFeatures") end
  end)

  editor.tutorialNavPreflightBtn = CreateFrame("Button", nil, tutorialNavBg, "UIPanelButtonTemplate")
  editor.tutorialNavPreflightBtn:SetSize(200, 20)
  editor.tutorialNavPreflightBtn:SetPoint("TOPLEFT", 6, -668)
  editor.tutorialNavPreflightBtn:SetText("Preflight")
  editor.tutorialNavPreflightBtn:SetScript("OnClick", function()
    if editor and editor.ShowTutorialSection then editor:ShowTutorialSection("Preflight") end
  end)

  editor.tutorialNavProfileRecoveryBtn = CreateFrame("Button", nil, tutorialNavBg, "UIPanelButtonTemplate")
  editor.tutorialNavProfileRecoveryBtn:SetSize(200, 20)
  editor.tutorialNavProfileRecoveryBtn:SetPoint("TOPLEFT", 6, -694)
  editor.tutorialNavProfileRecoveryBtn:SetText("Profile Recovery")
  editor.tutorialNavProfileRecoveryBtn:SetScript("OnClick", function()
    if editor and editor.ShowTutorialSection then editor:ShowTutorialSection("ProfileRecovery") end
  end)

  local tutNavRightsBtn = CreateFrame("Button", nil, tutorialNavBg, "UIPanelButtonTemplate")
  tutNavRightsBtn:SetSize(200, 20)
  tutNavRightsBtn:SetPoint("TOPLEFT", 6, -746)
  tutNavRightsBtn:SetText("Rights Tutorial")
  tutNavRightsBtn:SetScript("OnClick", function()
    if editor and editor.ShowTutorialSection then editor:ShowTutorialSection("Rights") end
  end)
  editor.tutorialNavRightsBtn = tutNavRightsBtn

  local tutNavRolesBtn = CreateFrame("Button", nil, tutorialNavBg, "UIPanelButtonTemplate")
  tutNavRolesBtn:SetSize(200, 20)
  tutNavRolesBtn:SetPoint("TOPLEFT", 6, -720)
  tutNavRolesBtn:SetText("Roles")
  tutNavRolesBtn:SetScript("OnClick", function()
    if editor and editor.ShowTutorialSection then editor:ShowTutorialSection("Roles") end
  end)
  editor.tutorialNavRolesBtn = tutNavRolesBtn

  local tutorialNavigation = {
    { button = tutNavBeginnerBtn },
    { button = tutNavProfilesBtn },
    { button = tutNavPanelsBtn },
    { button = tutNavCustomBtn },
    { button = tutNavSimulationBtn },
    { button = tutNavAddFolderBtn },
    { button = tutNavWorkflowBtn },
    { button = tutNavAutoPassBtn },
    { button = tutNavCombatBtn },
    { button = tutNavSyncBtn },
    { button = tutNavSyncPriorityBtn },
    { button = editor.tutorialNavSyncAuditBtn, developer = true },
    { button = editor.tutorialNavSyncProgressBtn, developer = true },
    { button = editor.tutorialNavSyncWorkflow4Btn, developer = true },
    { button = editor.tutorialNavSyncWorkflow5Btn, developer = true },
    { button = editor.tutorialNavSyncWorkflow6Btn, developer = true },
    { button = editor.tutorialNavSyncWorkflow7Btn, developer = true },
    { button = editor.tutorialNavSyncWorkflow8Btn, developer = true },
    { button = editor.tutorialNavSyncWorkflow10Btn, developer = true },
    { button = editor.tutorialNavSyncWorkflow11Btn, developer = true },
    { button = editor.tutorialNavSyncWorkflow12Btn, developer = true },
    { button = editor.tutorialNavSyncWorkflow13Btn, developer = true },
    { button = editor.tutorialNavSyncWorkflow14Btn, developer = true },
    { button = editor.tutorialNavSyncWorkflow15Btn, developer = true },
    { button = editor.tutorialNavPossibleFeaturesBtn, developer = true },
    { button = editor.tutorialNavPreflightBtn, developer = true },
    { button = editor.tutorialNavProfileRecoveryBtn },
    { button = tutNavRolesBtn },
    { button = tutNavRightsBtn },
  }

  local function MarkChecklistDeveloperButton(button)
    if not button then return end
    button.__lid_customBg = { 0.16, 0.035, 0.045, 0.96 }
    button.__lid_customHover = { 0.95, 0.20, 0.24, 0.24 }
    button.__lid_customPushed = { 0.40, 0.055, 0.075, 0.98 }
    button.__lid_customBorder = { 0.82, 0.20, 0.24, 0.92 }
    button.__lid_customText = { 1.00, 0.88, 0.88, 1.00 }
  end

  for _, entry in ipairs(tutorialNavigation) do
    if entry.developer then
      MarkChecklistDeveloperButton(entry.button)
    end
  end

  function editor:RefreshTutorialChecklistNavigation()
    local showDeveloperPages = ChecklistDeveloperPagesVisible()
    local visibleIndex = 0
    for _, entry in ipairs(tutorialNavigation) do
      local button = entry.button
      local shown = button and (not entry.developer or showDeveloperPages)
      if button then
        button:SetShown(shown and true or false)
        if shown then
          button:ClearAllPoints()
          button:SetPoint("TOPLEFT", tutorialNavBg, "TOPLEFT", 6, -18 - (visibleIndex * 26))
          visibleIndex = visibleIndex + 1
        end
      end
    end

    if not showDeveloperPages
      and CHECKLIST_DEVELOPER_SECTIONS[self.tutorialSection]
      and type(self.ShowTutorialSection) == "function" then
      self:ShowTutorialSection("Beginner")
    end
  end

  local tutorialRightBg = CreateFrame("Frame", nil, tutorialPage, "BackdropTemplate")
  tutorialRightBg:SetPoint("TOPLEFT", tutorialNavBg, "TOPRIGHT", 12, 0)
  tutorialRightBg:SetPoint("BOTTOMRIGHT", 0, 0)
  ApplyFrameSkin(tutorialRightBg)
  tutorialRightBg:SetClipsChildren(true)
  editor.tutorialRightBg = tutorialRightBg
  editor.RefreshTutorialAccentMarkup = function()
    RefreshDeveloperAccentMarkup(tutorialPage)
  end

  local function CreateTutorialSection(parent, headerTex, steps)
    local scroll = CreateFrame("ScrollFrame", nil, parent, "UIPanelScrollFrameTemplate")
    scroll:SetPoint("TOPLEFT", 6, -6)
    scroll:SetPoint("BOTTOMRIGHT", -28, 6)
    BindMouseWheelScroll(scroll)

    local frame = CreateFrame("Frame", nil, scroll)
    frame:SetPoint("TOPLEFT", 0, 0)
    frame:SetSize(1, 1)
    frame:SetClipsChildren(true)
    scroll:SetScrollChild(frame)

    scroll:SetScript("OnSizeChanged", function(self)
      local w = self:GetWidth() - 28
      if w < 1 then w = 1 end
      frame:SetWidth(w)
      if editor and editor.LayoutTutorial then editor:LayoutTutorial() end
      if self.UpdateScrollChildRect then self:UpdateScrollChildRect() end
    end)

    local header = nil
    if headerTex and headerTex ~= "" then
      header = frame:CreateTexture(nil, "ARTWORK")
      header:SetPoint("TOPLEFT", 18, -18)
      RegisterTutorialTexture(editor, header, headerTex)
      header:SetTexCoord(0, 1, 0, 1)
    end

    local cards = {}
    for _, tex in ipairs(steps) do
      local card = CreateFrame("Frame", nil, frame)
      card:SetPoint("TOPLEFT", 0, 0)
      card:SetSize(CARD_W, CARD_H)
      local texObj = card:CreateTexture(nil, "ARTWORK")
      texObj:SetPoint("TOPLEFT", 0, 0)
      texObj:SetPoint("BOTTOMRIGHT", 0, 0)
      RegisterTutorialTexture(editor, texObj, tex)
      texObj:SetTexCoord(0, 1, 0, 1)
      card.texObj = texObj
      cards[#cards + 1] = card
    end

    return scroll, frame, header, cards
  end

  local function SetTutorialSyncCellState(cell, enabled)
    if not cell or not cell.label then
      return
    end

    local ok = enabled and true or false
    cell.label:SetText(ok and "OK" or "X")
    local bg = ok and { 0.08, 0.18, 0.12, 0.98 } or { 0.22, 0.09, 0.10, 0.98 }
    local text = ok and { 0.58, 0.96, 0.70, 1.00 } or { 1.00, 0.58, 0.58, 1.00 }
    if cell.SetBackdropColor then
      cell:SetBackdropColor(bg[1], bg[2], bg[3], bg[4])
    end
    cell.label:SetTextColor(text[1], text[2], text[3], text[4])
  end

  local function CreateTutorialSyncCell(parent, enabled)
    local cell = CreateFrame("Frame", nil, parent, "BackdropTemplate")
    ApplyFrameSkin(cell)
    cell:SetSize(68, 20)
    cell.label = cell:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    cell.label:SetPoint("CENTER", 0, 0)
    SetTutorialSyncCellState(cell, enabled)
    return cell
  end

  local function CreateTutorialSyncTable(parent, titleText, rowDefs, noteText)
    local frame = CreateFrame("Frame", nil, parent)
    frame.__rowDefs = rowDefs or {}
    frame.__rows = {}

    frame.title = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    frame.title:SetJustifyH("LEFT")
    frame.title:SetText(titleText or "")

    frame.header = CreateFrame("Frame", nil, frame, "BackdropTemplate")
    ApplyFrameSkin(frame.header)
    if frame.header.SetBackdropColor then
      frame.header:SetBackdropColor(0.07, 0.07, 0.10, 0.98)
    end
    frame.header.action = frame.header:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    frame.header.action:SetJustifyH("LEFT")
    frame.header.action:SetText("|cffa87ae8Action|r")
    frame.header.live = frame.header:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    frame.header.live:SetJustifyH("CENTER")
    frame.header.live:SetText("|cffa87ae8Live Sync|r")
    frame.header.localOnly = frame.header:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
    frame.header.localOnly:SetJustifyH("CENTER")
    frame.header.localOnly:SetText("|cffa87ae8Local|r")

    for _, rowDef in ipairs(frame.__rowDefs) do
      local row = {}
      if rowDef.section then
        row.section = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
        row.section:SetJustifyH("LEFT")
        row.section:SetText("|cffa87ae8" .. tostring(rowDef.section) .. "|r")
      else
        row.frame = CreateFrame("Frame", nil, frame, "BackdropTemplate")
        ApplyFrameSkin(row.frame)
        if row.frame.SetBackdropColor then
          row.frame:SetBackdropColor(0.05, 0.05, 0.07, 0.98)
        end
        row.label = row.frame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
        row.label:SetJustifyH("LEFT")
        row.label:SetText(tostring(rowDef.label or ""))
        row.live = CreateTutorialSyncCell(row.frame, rowDef.live)
        row.localOnly = CreateTutorialSyncCell(row.frame, rowDef.localOnly)
      end
      frame.__rows[#frame.__rows + 1] = row
    end

    if noteText and noteText ~= "" then
      frame.note = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
      frame.note:SetJustifyH("LEFT")
      frame.note:SetText(noteText)
    end

    frame.LayoutForWidth = function(self, width)
      width = math.max(360, math.floor(tonumber(width) or 360))
      self:SetWidth(width)

      local y = 0
      self.title:ClearAllPoints()
      self.title:SetPoint("TOPLEFT", 0, y)
      self.title:SetWidth(width)
      y = y - ((self.title.GetStringHeight and self.title:GetStringHeight()) or 16) - 8

      local liveW = 68
      local localW = 68
      local gap = 10
      local labelW = math.max(180, width - liveW - localW - (gap * 3))

      self.header:ClearAllPoints()
      self.header:SetPoint("TOPLEFT", 0, y)
      self.header:SetSize(width, 24)
      self.header.action:ClearAllPoints()
      self.header.action:SetPoint("LEFT", 10, 0)
      self.header.action:SetWidth(labelW - 10)
      self.header.live:ClearAllPoints()
      self.header.live:SetPoint("LEFT", labelW + gap, 0)
      self.header.live:SetWidth(liveW)
      self.header.localOnly:ClearAllPoints()
      self.header.localOnly:SetPoint("LEFT", labelW + liveW + (gap * 2), 0)
      self.header.localOnly:SetWidth(localW)
      y = y - 28

      for index, rowDef in ipairs(self.__rowDefs) do
        local row = self.__rows[index]
        if rowDef.section and row and row.section then
          row.section:ClearAllPoints()
          row.section:SetPoint("TOPLEFT", 0, y)
          row.section:SetWidth(width)
          y = y - ((row.section.GetStringHeight and row.section:GetStringHeight()) or 14) - 4
        elseif row and row.frame then
          row.frame:ClearAllPoints()
          row.frame:SetPoint("TOPLEFT", 0, y)
          row.frame:SetSize(width, 24)
          row.label:ClearAllPoints()
          row.label:SetPoint("LEFT", 10, 0)
          row.label:SetWidth(labelW - 10)
          row.live:ClearAllPoints()
          row.live:SetPoint("LEFT", labelW + gap, 0)
          row.live:SetSize(liveW, 20)
          row.localOnly:ClearAllPoints()
          row.localOnly:SetPoint("LEFT", labelW + liveW + (gap * 2), 0)
          row.localOnly:SetSize(localW, 20)
          y = y - 26
        end
      end

      if self.note then
        y = y - 6
        self.note:ClearAllPoints()
        self.note:SetPoint("TOPLEFT", 0, y)
        self.note:SetWidth(width)
        y = y - ((self.note.GetStringHeight and self.note:GetStringHeight()) or 0) - 4
      end

      self:SetHeight(math.max(1, -y))
    end

    return frame
  end

  local panelsSteps = {}
  local profilesSteps = {}
  local customSteps = {}

  local tBeginnerScroll, tBeginnerFrame, tBeginnerHeader, tBeginnerCards =
    CreateTutorialSection(tutorialRightBg, nil, {})
  local tPanelsScroll, tPanelsFrame, tPanelsHeader, tPanelsCards =
    CreateTutorialSection(tutorialRightBg, nil, panelsSteps)
  local tProfilesScroll, tProfilesFrame, tProfilesHeader, tProfilesCards =
    CreateTutorialSection(tutorialRightBg, nil, profilesSteps)
  local tCustomScroll, tCustomFrame, tCustomHeader, tCustomCards =
    CreateTutorialSection(tutorialRightBg, nil, customSteps)
  local tSimulationScroll, tSimulationFrame, tSimulationHeader, tSimulationCards =
    CreateTutorialSection(tutorialRightBg, nil, {})
  local tAddFolderScroll, tAddFolderFrame, tAddFolderHeader, tAddFolderCards =
    CreateTutorialSection(tutorialRightBg, nil, {})
  local tGroupsScroll, tGroupsFrame, tGroupsHeader, tGroupsCards =
    CreateTutorialSection(tutorialRightBg, nil, {})
  local tWorkflowScroll, tWorkflowFrame, tWorkflowHeader, tWorkflowCards =
    CreateTutorialSection(tutorialRightBg, nil, {})
  local tAutoPassScroll, tAutoPassFrame, tAutoPassHeader, tAutoPassCards =
    CreateTutorialSection(tutorialRightBg, nil, {})
  local tCombatScroll, tCombatFrame, tCombatHeader, tCombatCards =
    CreateTutorialSection(tutorialRightBg, nil, {})
  local tSyncScroll, tSyncFrame, tSyncHeader, tSyncCards =
    CreateTutorialSection(tutorialRightBg, nil, {})
  local tSyncPriorityScroll, tSyncPriorityFrame, tSyncPriorityHeader, tSyncPriorityCards =
    CreateTutorialSection(tutorialRightBg, nil, {})
  editor.tutorialSyncAudit = CreateTutorialSyncAuditSection(tutorialRightBg, CreateTutorialSection)
  editor.tutorialSyncProgress = CreateTutorialSyncProgressSection(tutorialRightBg, CreateTutorialSection)
  editor.tutorialSyncWorkflow4 = CreateTutorialSyncWorkflow4Section(tutorialRightBg, CreateTutorialSection)
  editor.tutorialSyncWorkflow5 = CreateTutorialSyncWorkflow5Section(tutorialRightBg, CreateTutorialSection)
  editor.tutorialSyncWorkflow6 = CreateTutorialSyncWorkflow6Section(tutorialRightBg, CreateTutorialSection)
  editor.tutorialSyncWorkflow7 = CreateTutorialSyncWorkflow7Section(tutorialRightBg, CreateTutorialSection)
  editor.tutorialSyncWorkflow8 = CreateTutorialSyncWorkflow8Section(tutorialRightBg, CreateTutorialSection)
  editor.tutorialSyncWorkflow10 = CreateTutorialSyncWorkflow10Section(tutorialRightBg, CreateTutorialSection)
  editor.tutorialSyncWorkflow11 = CreateTutorialSyncWorkflow11Section(tutorialRightBg, CreateTutorialSection)
  editor.tutorialSyncWorkflow12 = CreateTutorialSyncWorkflow12Section(tutorialRightBg, CreateTutorialSection)
  editor.tutorialSyncWorkflow13 = CreateTutorialSyncWorkflow13Section(tutorialRightBg, CreateTutorialSection)
  editor.tutorialSyncWorkflow14 = CreateTutorialSyncWorkflow14Section(tutorialRightBg, CreateTutorialSection)
  editor.tutorialSyncWorkflow15 = CreateTutorialSyncWorkflow15Section(tutorialRightBg, CreateTutorialSection)
  editor.tutorialPossibleFeatures = CreateTutorialPossibleFeaturesSection(tutorialRightBg, CreateTutorialSection)
  editor.tutorialPreflight = CreateTutorialPreflightSection(tutorialRightBg, CreateTutorialSection)
  editor.tutorialProfileRecovery = CreateTutorialProfileRecoverySection(tutorialRightBg, CreateTutorialSection)
  local tRolesScroll, tRolesFrame, tRolesHeader, tRolesCards =
    CreateTutorialSection(tutorialRightBg, nil, {})
  local tRightsScroll, tRightsFrame, tRightsHeader, tRightsCards =
    CreateTutorialSection(tutorialRightBg, nil, {})

  local tBeginnerIntro = tBeginnerFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
  tBeginnerIntro:SetJustifyH("LEFT")
  tBeginnerIntro:SetText("Beginner Guide")

  local tBeginnerNote = tBeginnerFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tBeginnerNote:SetJustifyH("LEFT")
  tBeginnerNote:SetText(
    "|cffa87ae81. First raid setup|r\n" ..
    "- Everyone in the raid needs to download and enable LidCouncil.\n" ..
    "- The intended Profile Master or raid leader creates one new profile for the current season, for example |cff4adf6bMidnight Season 1|r.\n" ..
    "- Invite everyone into the WoW raid party first.\n" ..
    "- Open |cff4adf6bSession|r and press |cff4adf6bPrepare Session|r.\n" ..
    "- Everyone with LidCouncil receives a popup and can join the session."
  )

  local tBeginnerAfter = tBeginnerFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tBeginnerAfter:SetJustifyH("LEFT")
  tBeginnerAfter:SetText(
    "|cffa87ae82. Wait for sync|r\n" ..
    "- After players accept, LidCouncil sends every member the roll-ready data first, before historical background data.\n" ..
    "- This can take a bit of time in a larger raid because the addon shares the queue fairly and avoids sending everything at once.\n" ..
    "- Wait until every required member is |cff4adf6bP1 Ready|r. P2 current-session proof and P3 archive history may continue after the raid becomes roll-ready.\n" ..
    "- When the raid is ready, press |cff4adf6bStart Session|r."
  )

  local tBeginnerPost = tBeginnerFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tBeginnerPost:SetJustifyH("LEFT")
  tBeginnerPost:SetText(
    "|cffa87ae83. Simple raid workflow|r\n" ..
    "- Raid normally, kill bosses, collect loot, and distribute it through the Live Board.\n" ..
    "- When the raid is done, return to |cff4adf6bSession|r and press |cff4adf6bStop Session|r.\n" ..
    "- On later raids, unchanged P1 data can be confirmed quickly with compact checksum proof while any P3 archive work continues in the background:\n" ..
    "  1. Prepare Session\n" ..
    "  2. Start Session\n" ..
    "  3. Raid\n" ..
    "  4. Stop Session\n" ..
    "- This is the most simple way to use LidCouncil."
  )

  local tPanelsIntro = tPanelsFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tPanelsIntro:SetJustifyH("LEFT")
  tPanelsIntro:SetText("The Session page is the preparation and live-session control room for the active LidCouncil profile.")

  local tPanelsTitle = tPanelsFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
  tPanelsTitle:SetJustifyH("LEFT")
  tPanelsTitle:SetText("Session")

  local tPanelsNote = tPanelsFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tPanelsNote:SetJustifyH("LEFT")
  tPanelsNote:SetText(
    "|cffa87ae81. What this page is about|r\n" ..
    "- This page shows whether the selected profile is idle, preparing, or running an active loot session.\n" ..
    "- It is where LidCouncil tracks the host, joined members, invite flow, roll-ready state, Auto Pass state, addon version, and sync state.\n" ..
    "- Session work is now split into two stages: |cff4adf6bPrepare Session|r first, then |cff4adf6bStart Session|r when the raid is ready."
  )

  local tPanelsAfter = tPanelsFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tPanelsAfter:SetJustifyH("LEFT")
  tPanelsAfter:SetText(
    "|cffa87ae82. What you can do here|r\n" ..
    "- Use |cff4adf6bPrepare Session|r to open the preparation lobby and invite the current party or raid.\n" ..
    "- Review who joined and who is still missing. |cff4adf6bP1 Ready|r is the roll-ready gate, |cff4adf6bP2 State|r is current-session proof, and Loot List, Items, Sessions, and Awards show non-blocking P3 archive evidence.\n" ..
    "- In the P2 and P3 evidence columns, a green check means exact canonical proof, blue text means transfer or validation is progressing, an amber warning means archive proof differs, and a gray question mark means proof is still unknown.\n" ..
    "- Use |cff4adf6bStart Session|r to make the host profile authoritative and activate the real loot workflow.\n" ..
    "- Use |cff4adf6bSend Invite|r for late raiders. If displayed states remain stale after traffic becomes idle, |cff4adf6bRefresh Readiness|r requests fresh compact proofs without resending archive payloads.\n" ..
    "- Joined non-host members can use |cff4adf6bLeave Session|r to leave locally without stopping the host's running session."
  )

  local tPanelsPost = tPanelsFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tPanelsPost:SetJustifyH("LEFT")
  tPanelsPost:SetText(
    "|cffa87ae83. Typical use in LidCouncil|r\n" ..
    "- Select and activate the correct profile first.\n" ..
    "- Press |cff4adf6bPrepare Session|r before the boss pull or before loot is handled so members can join and sync can be checked.\n" ..
    "- Wait until required members are P1 Ready. Blue archive progress can continue after Start Session; an amber Archive Differs state is historical and does not by itself block the raid.\n" ..
    "- Press |cff4adf6bStart Session|r only when the host copy should become the source of truth for the active raid session.\n" ..
    "- Return here whenever you need late invites, sync management, Auto Pass overview, or a quick session status check."
  )

  local tProfilesIntro = tProfilesFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tProfilesIntro:SetJustifyH("LEFT")
  tProfilesIntro:SetText("Profiles define the full LidCouncil ruleset and data context you want to use for one raid environment.")

  local tAddFolderNote = tAddFolderFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tAddFolderNote:SetJustifyH("LEFT")
  tAddFolderNote:SetText(
    "|cffa87ae81. What this page is about|r\n" ..
    "- The Loot List page is the long-term data layer behind your loot decisions.\n" ..
    "- It keeps the raider list, manual point entries, notes, and related profile data in one place.\n" ..
    "- Session awards and profile-specific loot data flow back into this page over time."
  )

  local tAddFolderImage = tAddFolderFrame:CreateTexture(nil, "ARTWORK")
  RegisterTutorialTexture(editor, tAddFolderImage, "Interface\\AddOns\\LidCouncil\\Media\\LidCouncil\\Tutorials\\CBG_6_Folder.tga")

  local tAddFolderAfter = tAddFolderFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tAddFolderAfter:SetJustifyH("LEFT")
  tAddFolderAfter:SetText(
    "|cffa87ae82. What you can do here|r\n" ..
    "- Review member-related profile data before and after raid nights.\n" ..
    "- Open details for a raider, maintain notes, and work with manual point entries when your rights allow it.\n" ..
    "- Keep the profile transparent and up to date so Live Board decisions are backed by clean data."
  )

  local tAddFolderPost = tAddFolderFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tAddFolderPost:SetJustifyH("LEFT")
  tAddFolderPost:SetText(
    "|cffa87ae83. Typical use in LidCouncil|r\n" ..
    "- Prepare the Loot List between raids so the next session starts from reliable information.\n" ..
    "- Let session results write back into the profile instead of tracking everything manually outside the addon.\n" ..
    "- Use this page as the long-term memory of the profile while the Live Board handles the real-time decisions."
  )

  local tAddFolderPreviewNote = tAddFolderFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tAddFolderPreviewNote:SetJustifyH("LEFT")
  tAddFolderPreviewNote:SetText("")

  local tAddFolderIntro = tAddFolderFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tAddFolderIntro:SetJustifyH("LEFT")
  tAddFolderIntro:SetText("The Loot List page stores the profile history and planning data that support fair distribution across multiple raids.")

  local tAddFolderStepTitle = tAddFolderFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
  tAddFolderStepTitle:SetJustifyH("LEFT")
  tAddFolderStepTitle:SetText("Loot List")

  local tAddFolderPreviewImage = tAddFolderFrame:CreateTexture(nil, "ARTWORK")
  RegisterTutorialTexture(editor, tAddFolderPreviewImage, "Interface\\AddOns\\LidCouncil\\Media\\LidCouncil\\Tutorials\\CBG_7_preview.tga")

  local tAddFolderPostImage = tAddFolderFrame:CreateTexture(nil, "ARTWORK")
  RegisterTutorialTexture(editor, tAddFolderPostImage, "Interface\\AddOns\\LidCouncil\\Media\\LidCouncil\\Tutorials\\CBG_8_Select_Background.tga")

  local tGroupsIntro = tGroupsFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
  tGroupsIntro:SetJustifyH("LEFT")
  tGroupsIntro:SetText("Group Tutorial")

  local tGroupsNote = tGroupsFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tGroupsNote:SetJustifyH("LEFT")
  tGroupsNote:SetText(
    "|cffa87ae81. Why use groups?|r\n" ..
    "- Groups help you keep long lists clean and organized.\n" ..
    "- Profile groups are managed locally on the |cff4adf6bProfiles|r page.\n" ..
    "- Groups are only for organization. They do not change or delete the content inside them."
  )

  local tGroupsAfter = tGroupsFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tGroupsAfter:SetJustifyH("LEFT")
  tGroupsAfter:SetText(
    "|cffa87ae82. How to create and fill a group|r\n" ..
    "- Open |cff4adf6bProfiles|r and click |cff4adf6bCreate Group|r.\n" ..
    "- Enter a group name.\n" ..
    "- Left-click and drag a profile or category.\n" ..
    "- Drop it onto the target group row.\n" ..
    "- While dragging, the target group highlights so you can clearly see where it will land."
  )

  local tGroupsPost = tGroupsFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tGroupsPost:SetJustifyH("LEFT")
  tGroupsPost:SetText(
    "|cffa87ae83. Managing groups|r\n" ..
    "- Click the |cff4adf6bsmall arrow on the far left|r to collapse or expand a group.\n" ..
    "- Click the group row itself to open its settings on the right side.\n" ..
    "- Use |cff4adf6bRename Group|r to rename it.\n" ..
    "- Use |cff4adf6bDelete Group|r to remove the group only. The profiles or categories inside become |cff4adf6bgroupless|r and stay intact.\n" ..
    "- To remove a single item from a group, drag it and release it back inside the list instead of dropping it on another group."
  )

  local tProfilesGroupsTitle = tProfilesFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
  tProfilesGroupsTitle:SetJustifyH("LEFT")
  tProfilesGroupsTitle:SetText("Profiles")

  local tProfilesGroupsNote = tProfilesFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tProfilesGroupsNote:SetJustifyH("LEFT")
  tProfilesGroupsNote:SetText(
    "|cffa87ae81. What this page is about|r\n" ..
    "- A profile bundles your roll settings, point settings, raid setup, council rights, and related loot data.\n" ..
    "- Use different profiles when your guild needs different rulesets, raid formats, or test environments.\n" ..
    "- Only one profile is active at a time, so this page defines the foundation for the rest of LidCouncil."
  )

  local tProfilesGroupsAfter = tProfilesFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tProfilesGroupsAfter:SetJustifyH("LEFT")
  tProfilesGroupsAfter:SetText(
    "|cffa87ae82. What you can do here|r\n" ..
    "- Create, copy, activate, deactivate, or delete local profile copies. Import or export Roll Settings and Point Settings, and use Transfer for portable scoped profile packages.\n" ..
    "- Open the selected profile settings to define roll options, point logic, raid data, and council permissions.\n" ..
    "- Save, load, or delete local recovery backups for Loot List and historical profile data when you need a safe restore point.\n" ..
    "- Decide which profile should be the active one before you move into session work."
  )

  local tProfilesGroupsPost = tProfilesFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tProfilesGroupsPost:SetJustifyH("LEFT")
  tProfilesGroupsPost:SetText(
    "|cffa87ae83. Typical use in LidCouncil|r\n" ..
    "- Start by selecting the raid profile you want to maintain or use tonight.\n" ..
    "- Review the profile configuration before the first loot drops instead of adjusting rules mid-session.\n" ..
    "- Treat this page as the setup layer; once the profile is ready, the Session page takes over the live workflow."
  )

  local tCustomIntro = tCustomFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tCustomIntro:SetJustifyH("LEFT")
  tCustomIntro:SetText("The Live Board is the real-time decision page once loot starts dropping during a LidCouncil session.")

  local tCustomTitle = tCustomFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
  tCustomTitle:SetJustifyH("LEFT")
  tCustomTitle:SetText("Live Board")

  local tCustomNote = tCustomFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tCustomNote:SetJustifyH("LEFT")
  tCustomNote:SetText(
    "|cffa87ae81. What this page is about|r\n" ..
    "- The Live Board collects dropped items, player choices, votes, and assignment state in one place.\n" ..
    "- It is the central raid-time page for hosts and council members while loot is actively being processed.\n" ..
    "- Instead of guessing, you can compare the current item context directly inside the board."
  )

  local tCustomAfter = tCustomFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tCustomAfter:SetJustifyH("LEFT")
  tCustomAfter:SetText(
    "|cffa87ae82. What you can do here|r\n" ..
    "- Review each dropped item and see the submitted roll choices from joined members.\n" ..
    "- Inspect the item context, compare candidates, and place or remove council votes.\n" ..
    "- Assign or unassign loot when your current role and session rights allow it."
  )

  local tCustomPost = tCustomFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tCustomPost:SetJustifyH("LEFT")
  tCustomPost:SetText(
    "|cffa87ae83. Typical use in LidCouncil|r\n" ..
    "- Keep this page open during raid once the session is running and loot begins to drop.\n" ..
    "- Use it item by item while raiders send their choices and council members review the outcome.\n" ..
    "- Final assignments from the Live Board flow back into the session state and the Loot List data."
  )

  local tSimulationIntro = tSimulationFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
  tSimulationIntro:SetJustifyH("LEFT")
  tSimulationIntro:SetText("Run Simulation")

  local tSimulationNote = tSimulationFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tSimulationNote:SetJustifyH("LEFT")
  tSimulationNote:SetText(
    "|cffa87ae81. Preview Simulation|r\n" ..
    "- This is a local sandbox test for the Live Board.\n" ..
    "- It does not require a prepared or active session.\n" ..
    "- It does not invite, sync, or update other players.\n" ..
    "- It uses local preview members and the Simulation profile to test roll choices, votes, assignments, and board layout.\n" ..
    "- It is useful when you want to check the UI or test profile settings alone."
  )

  local tSimulationAfter = tSimulationFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tSimulationAfter:SetJustifyH("LEFT")
  tSimulationAfter:SetText(
    "|cffa87ae82. Test Raid Items|r\n" ..
    "- This is an active-session dry run.\n" ..
    "- It requires a started session and can only be triggered by the session host.\n" ..
    "- It uses the actual joined session members and the active session profile.\n" ..
    "- LidCouncil sends simulated raid items to the real Live Board and opens the normal LidCouncil roll popup for joined members.\n" ..
    "- Board updates are synced like a real session, but no Loot List points are written."
  )

  local tSimulationPost = tSimulationFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tSimulationPost:SetJustifyH("LEFT")
  tSimulationPost:SetText(
    "|cffa87ae83. Which test should I use?|r\n" ..
    "- Use |cff4adf6bPreview Simulation|r when you are alone or only want to test local Live Board behavior.\n" ..
    "- Use |cff4adf6bTest Raid Items|r when you want to test the multiplayer roll flow with joined session members.\n" ..
    "- Use |cff4adf6bTest Live Delivery|r when you need the narrower one-item ITEM_OPEN transport and receiver-gate diagnostic.\n" ..
    "- Use the |cff4adf6bDeveloper Mode Sync Harness|r when you want synthetic joined members for local Sync Management rehearsal without real players.\n" ..
    "- Test Raid Items is the better raid-readiness test because it uses the same host-to-member workflow as real loot handling.\n" ..
    "- Preview Simulation is safer for quick UI checks because nothing leaves your client."
  )

  local tWorkflowIntro = tWorkflowFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
  tWorkflowIntro:SetJustifyH("LEFT")
  tWorkflowIntro:SetText("LC Workflow")

  local tWorkflowNote = tWorkflowFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tWorkflowNote:SetJustifyH("LEFT")
  tWorkflowNote:SetText(
    "|cffa87ae81. Prepare the session|r\n" ..
    "- Select and activate the loot profile you want to use, then click |cff4adf6bPrepare Session|r.\n" ..
    "- LidCouncil creates a preparation lobby, makes you the host, and sends invites to the current party or raid.\n" ..
    "- Preparation is not live loot processing yet. It exists so members can join and profile sync can be checked before the raid commits to the host copy.\n" ..
    "- If you are not in a party or raid yet, preparation exists only on your client until a group channel exists."
  )

  local tWorkflowAfter = tWorkflowFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tWorkflowAfter:SetJustifyH("LEFT")
  tWorkflowAfter:SetText(
    "|cffa87ae82. How raiders join|r\n" ..
    "- Raiders who are already in your current party or raid and also use LidCouncil receive a join popup.\n" ..
    "- The popup asks whether they want to join the preparation lobby and offers |cff4adf6bYes|r or |cffff4a4aNo|r.\n" ..
    "- If they accept, their client joins the same prepared session and sends a join reply back to the host.\n" ..
    "- The host then updates the joined-member table, syncs member data, and sends the current session snapshot.\n" ..
    "- If the joiner has no local copy of that shared profile yet, Priority 1 sends the roll-ready Settings, Permissions, and current live loot data first.\n" ..
    "- Priority 2 current-session proof and the heavier Priority 3 Loot List, Item History, Session History, and Fun Awards archives can continue after P1 becomes ready.\n" ..
    "- If the joiner already has the same shared profile lineage, LidCouncil compares revisions before replacing anything.\n" ..
    "- They appear in |cff4adf6bCurrent Session|r with sync state, addon version, class/spec, and join time.\n" ..
    "- Players without LidCouncil do not receive the popup or the synced session workflow."
  )

  local tWorkflowLive = tWorkflowFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tWorkflowLive:SetJustifyH("LEFT")
  tWorkflowLive:SetText(
    "|cffa87ae83. Start the live session|r\n" ..
    "- When every required member is |cff4adf6bP1 Ready|r, the host can click |cff4adf6bStart Session|r. P2 and P3 evidence may continue without blocking roll-ready members.\n" ..
    "- If some joined members are still syncing, the warning popup now shows how many are already |cff4adf6broll-ready|r versus still waiting on bootstrap before the host decides whether to force start.\n" ..
    "- LidCouncil broadcasts the start signal, switches the prepared session to active, and treats the host profile as authoritative.\n" ..
    "- When loot drops, the host gets the |cff4adf6bLive Board|r.\n" ..
    "- Joined members get the LidCouncil roll popup and can send their choice back to the host.\n" ..
    "- LidCouncil keeps member, choice, and award data updated during the session.\n" ..
    "- |cff4adf6bAuto Pass|r is personal now: each joined player controls only their own setting.\n" ..
    "- If your personal Auto Pass is ON, LidCouncil auto-passes Blizzard loot rolls on your client for council-managed items. If it is OFF, Blizzard's normal loot roll window still appears.\n" ..
    "- |cffffd100Bonus Rolls|r are not part of LidCouncil Auto Pass."
  )

  local tWorkflowPost = tWorkflowFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tWorkflowPost:SetJustifyH("LEFT")
  tWorkflowPost:SetText(
    "|cffa87ae84. Loot List and late joiners|r\n" ..
    "- Session members are added or updated in the |cff4adf6bLoot List|r layer as they join the prepared or active session.\n" ..
    "- When the host assigns an item on the Live Board, the award is written into the Loot List / loot ledger.\n" ..
    "- Raiders invited after preparation or session start do not join automatically.\n" ..
    "- Use |cff4adf6bSend Invite|r in |cff4adf6bCurrent Session|r to invite only late raiders who are not already part of the session.\n" ..
    "- Existing session members do not get the popup again.\n" ..
    "- Shared snapshot copies can later be removed locally with |cff4adf6bDelete Profile|r once no prepared or active session is running for that profile.\n" ..
    "- A later host session can repopulate that shared copy again if you accept it.\n" ..
    "- Same visible profile names are safe because shared profiles use an internal immutable |cff4adf6bprofileId|r, not only the display name.\n" ..
    "- Only one active profile and one open LidCouncil session can exist at a time."
  )

  local tAutoPassIntro = tAutoPassFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
  tAutoPassIntro:SetJustifyH("LEFT")
  tAutoPassIntro:SetText("Auto Pass")

  local tAutoPassBasics = tAutoPassFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tAutoPassBasics:SetJustifyH("LEFT")
  tAutoPassBasics:SetText(
    "|cffa87ae81. What Auto Pass means now|r\n" ..
    "- Auto Pass is a |cff4adf6bpersonal setting|r. There is no separate session-wide Auto Pass switch anymore.\n" ..
    "- If your personal Auto Pass is |cff4adf6bON|r, LidCouncil calls Blizzard's pass roll on your client for council-managed session loot.\n" ..
    "- Collectibles, toys, and Housing Decor stay visible on the Live Board as |cffffd100Non-Council Items|r and are not auto-passed by LidCouncil.\n" ..
    "- |cffffd100Bonus Rolls|r are not affected by LidCouncil Auto Pass.\n" ..
    "- If your personal Auto Pass is |cffff5a5aOFF|r, you keep Blizzard's normal loot roll window on your client."
  )

  local tAutoPassDefaults = tAutoPassFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tAutoPassDefaults:SetJustifyH("LEFT")
  tAutoPassDefaults:SetText(
    "|cffa87ae82. Default behavior when a session starts or when you join|r\n" ..
    "- When you |cff4adf6bstart|r a session as the host, your personal Auto Pass defaults to |cffff5a5aOFF|r.\n" ..
    "- This keeps manual Blizzard loot handling available for the host by default.\n" ..
    "- When you |cff4adf6bjoin|r someone else's session, your personal Auto Pass defaults to |cff4adf6bON|r.\n" ..
    "- Joined members therefore start in pass mode automatically unless they choose to opt out.\n" ..
    "- Those defaults are applied each time a session is started or accepted."
  )

  local tAutoPassControl = tAutoPassFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tAutoPassControl:SetJustifyH("LEFT")
  tAutoPassControl:SetText(
    "|cffa87ae83. Who can change it|r\n" ..
    "- Every joined player can change |cff4adf6btheir own|r Auto Pass setting any time in |cff4adf6bSession -> Auto Pass Setting|r.\n" ..
    "- The host cannot force another joined client to auto-pass through LidCouncil anymore.\n" ..
    "- Current Session shows each joined member's personal Auto Pass state as |cff4adf6bON|r or |cffff5a5aOFF|r."
  )

  local tAutoPassExamples = tAutoPassFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tAutoPassExamples:SetJustifyH("LEFT")
  tAutoPassExamples:SetText(
    "|cffa87ae84. Practical examples|r\n" ..
    "- Host OFF, Raider ON: the host keeps Blizzard's roll window, the raider auto-passes.\n" ..
    "- Host OFF, Raider OFF: both keep Blizzard's roll window.\n" ..
    "- Host ON, Raider ON: both auto-pass.\n" ..
    "- This is useful for testing because each client can opt in or out without changing the whole session."
  )

  local tSyncIntro = tSyncFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
  tSyncIntro:SetJustifyH("LEFT")
  tSyncIntro:SetText("Sync Workflow")

  local tCombatIntro = tCombatFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
  tCombatIntro:SetJustifyH("LEFT")
  tCombatIntro:SetText("Combat Restrictions")

  local tCombatAllowed = tCombatFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tCombatAllowed:SetJustifyH("LEFT")
  tCombatAllowed:SetText(
    "|cffa87ae81. Allowed in combat|r\n" ..
    "- Keep the editor open and inspect information.\n" ..
    "- Navigate between pages and review Current Session, Sync Management, Live Board, and Loot List state.\n" ..
    "- The active host can use |cff4adf6bRefresh Readiness|r to exchange compact proof and republish the current matrix.\n" ..
    "- Receive incoming session, profile, Live Board, and Loot List updates.\n" ..
    "- Use the raider roll option window and make roll choices on dropped items.\n" ..
    "- Wait for deferred UI refreshes to appear automatically after combat."
  )

  local tCombatBlocked = tCombatFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tCombatBlocked:SetJustifyH("LEFT")
  tCombatBlocked:SetText(
    "|cffa87ae82. Blocked during combat|r\n" ..
    "- Prepare Session, Start Session, Stop Session, Send Invite, Leave Session, and Remove Member.\n" ..
    "- Check Differences, Push Repair, Pull Repair, Preview Repair, and Send Board Sync.\n" ..
    "- Personal Auto Pass toggles.\n" ..
    "- Manual Loot List edits: manual points, character notes, Main / Alt links, character deletion, and Seed Test Raiders.\n" ..
    "- Profile edits: rights, role changes, raid selection, roll settings, point settings, imports, backups, and related profile mutation actions.\n" ..
    "- Live Board host admin actions: assign, unassign, change roll, trade helper, and mark traded.\n" ..
    "- Editor layout controls: dragging, resizing, and panel keyboard nudging."
  )

  local tCombatBehavior = tCombatFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tCombatBehavior:SetJustifyH("LEFT")
  tCombatBehavior:SetText(
    "|cffa87ae83. How LidCouncil behaves now|r\n" ..
    "- Phase 1: incoming data is still received.\n" ..
    "- Phase 2: risky manual mutations are blocked, and selected incoming Loot List operations are queued until combat ends.\n" ..
    "- If a council member edits the Loot List while out of combat, but the session host is in combat, the host waits and applies that change after combat before mirroring it to others.\n" ..
    "- Joined members then apply the mirrored result immediately if they are out of combat, or after combat if they are still fighting.\n" ..
    "- Combat-time editor and runtime UI refreshes are merged and flushed automatically on |cff4adf6bPLAYER_REGEN_ENABLED|r.\n" ..
    "- The goal is to keep the raid running while avoiding Blizzard combat restriction errors."
  )

  local tSyncTriggers = tSyncFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tSyncTriggers:SetJustifyH("LEFT")
  tSyncTriggers:SetText(
    "|cffa87ae81. Current sync model and overload protection|r\n" ..
    "- |cff4adf6bPrepare Session|r invites party/raid members and builds the joined-member table before loot starts.\n" ..
    "- First join, recovery, or missing-profile cases can still require host-side snapshot repair, but missing-profile joins now try a smaller roll-ready bootstrap before the heavier background sync.\n" ..
    "- When a member rejoins later with the same revision + checksum summary, LidCouncil can now reuse persisted checksum / patch baselines from your earlier session view instead of rebuilding every precise scope from scratch.\n" ..
    "- Full snapshots and profile/member updates are sent through an outbound queue instead of being burst to the raid all at once.\n" ..
    "- Normal raid-time actions prefer smaller messages: one choice, one vote, one note, one Main / Alt update, one manual point entry, one award, or one delete action.\n" ..
    "- Profile settings and permissions prefer smaller per-action deltas where supported. Full snapshots stay as recovery and first-join tools.\n" ..
    "- Color changes are synced after the color picker is confirmed, not while the mouse is dragged.\n" ..
    "- Urgent loot flow messages keep priority over heavy profile sync traffic.\n" ..
    "- Settings, permissions, and loot/session data now carry per-domain checksums in the sync state.\n" ..
    "- Profile deltas are acknowledged by joined clients. If a client detects a missed sequence or checksum mismatch, it requests a full host refresh instead of guessing.\n" ..
    "- High-frequency roll/vote stat updates and Live Board history now coalesce into short delayed host checkpoints. Once a member has the needed leaf patch baseline, those checkpoints prefer smaller patch transfers, including direct vote rows, per-member selection rows, and narrow award/trade field rows, instead of resending the whole affected scope.\n" ..
    "- Preview-based member repair is now fenced more strictly: if the host copy or member state changes, or the preview sits too long, LidCouncil drops that cached preview and asks for a fresh one instead of applying stale nested rows."
  )

  local tSyncPaths = tSyncFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tSyncPaths:SetJustifyH("LEFT")
  tSyncPaths:SetText(
    "|cffa87ae82. Who sends what to whom|r\n" ..
    "- Joiners answer only to the host. The host updates joined members and broadcasts member snapshots.\n" ..
    "- Once |cff4adf6bStart Session|r is pressed, the host profile becomes authoritative for that running session.\n" ..
    "- Roll choices go to the host first. The host validates and mirrors the accepted result back to joined boards.\n" ..
    "- Votes, awards, unassigns, and board sync keep the Live Board aligned across host, council, and joined clients.\n" ..
    "- Loot List deltas such as manual points, Main / Alt, notes, and Delete Character go to the host first when the editor is not the host. The host now prefers acknowledged profile deltas for these low-frequency changes and falls back to the older live message path only if needed.\n" ..
    "- |cff4adf6bCheck Differences|r loads detailed leaf checksum scopes for mismatched joined members. LidCouncil also refreshes these in the background when a joined member reports mismatched state. The Sync Management Leaf Scope Picker can then narrow |cff4adf6bPush Repair|r, |cff4adf6bPreview Repair|r, and |cff4adf6bPull Repair|r down to only the selected differing leaves for that member.\n" ..
    "- The safest current action is now highlighted directly on the relevant Sync Management buttons instead of using a separate recommended-repair button.\n" ..
    "- |cff4adf6bPreview Repair|r is inspect-only. It loads exact diff details for the selected leaf scopes into the inline preview area and the Sync Detail popup without applying changes.\n" ..
    "- After a preview arrives for a patch-capable leaf, the Sync Detail popup and inline preview can select individual nested preview rows to keep the host value or take only that member value. Applying that selection updates the host copy and immediately queues direct host repair for the affected leaf scopes. If nothing is selected, the broader member/domain behavior stays unchanged.\n" ..
    "- Sync Management also keeps a short |cff4adf6bRepair History and Undo|r panel for recent host-side repairs so the host can inspect what was applied and revert the last local repair snapshot if needed.\n" ..
    "- |cff4adf6bSend Board Sync|r is the recovery tool for rebuilding a lost Live Board from the host's current board snapshot."
  )

  local tSyncGaps = CreateTutorialSyncTable(tSyncFrame, "|cffa87ae83. Sync List - Profile and Session|r", {
    { section = "Profile" },
    { label = "Create Profile", live = false, localOnly = true },
    { label = "Activate / Deactivate Profile", live = false, localOnly = true },
    { label = "Copy Profile", live = false, localOnly = true },
    { label = "Delete Local Profile Copy", live = false, localOnly = true },
    { label = "Save / Load / Delete Backup", live = false, localOnly = true },
    { section = "Roles & Rights" },
    { label = "Promote / Degrade", live = true, localOnly = false },
    { label = "Change permission policy", live = true, localOnly = false },
    { section = "Raid Settings" },
    { label = "Change thumbnail", live = true, localOnly = false },
    { section = "Roll Settings" },
    { label = "Template / add / edit / delete", live = true, localOnly = false },
    { section = "Point Settings" },
    { label = "LidTable / custom rules", live = true, localOnly = false },
    { section = "Session" },
    { label = "Prepare Session / Accept Join", live = true, localOnly = false },
    { label = "Start / Stop Session", live = true, localOnly = false },
    { label = "Send Invite / Remove Member", live = true, localOnly = false },
    { label = "Differences / Push / Preview / Pull / Board", live = true, localOnly = false },
    { label = "Auto Pass Setting", live = true, localOnly = true },
  }, nil)

  local tSyncNotes = CreateTutorialSyncTable(tSyncFrame, "|cffa87ae84. Sync List - Live Board, Loot List, and local-only areas|r", {
    { section = "Live Board" },
    { label = "Loot item opens roll popup", live = true, localOnly = false },
    { label = "Player roll choice", live = true, localOnly = false },
    { label = "Council vote", live = true, localOnly = false },
    { label = "Assign / Unassign item", live = true, localOnly = false },
    { label = "Pop Out position / collapse state", live = false, localOnly = true },
    { label = "Preview Simulation", live = false, localOnly = true },
    { label = "Test Raid Items", live = true, localOnly = false },
    { label = "Test Live Delivery", live = true, localOnly = false },
    { section = "Loot List" },
    { label = "Manual points add / delete", live = true, localOnly = false },
    { label = "Main / Alt change", live = true, localOnly = false },
    { label = "Character note", live = true, localOnly = false },
    { label = "Delete Character", live = true, localOnly = false },
    { label = "Search, sorting, view mode", live = false, localOnly = true },
    { section = "Local client areas" },
    { label = "UI Settings: editor size, collapse, minimap", live = false, localOnly = true },
    { label = "Tutorial / About / Developer Mode toggles", live = false, localOnly = true },
  },
    "- |cff4adf6bLive Sync|r means the action is sent during a prepared or active matching session, usually host-routed.\n" ..
    "- |cff4adf6bLocal|r means it changes only your client/SavedVariables and may become part of a future shared profile if the host later shares that profile.\n" ..
    "- Auto Pass is both: your personal setting is local control, but the current ON/OFF state is shared to the session table.\n" ..
    "- Current profile settings now prefer smaller per-action deltas and queued large snapshots for safer raid-scale sync."
  )

  local tSyncPriorityIntro = tSyncPriorityFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
  tSyncPriorityIntro:SetJustifyH("LEFT")
  tSyncPriorityIntro:SetText("Sync Priority")

  local tSyncPriorityReady = tSyncPriorityFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tSyncPriorityReady:SetJustifyH("LEFT")
  tSyncPriorityReady:SetText(
    "|cffa87ae81. Priority 1 - Roll-ready and live raid traffic|r\n" ..
    "- Session control: prepare, join, leave, start, stop, and joined-member updates.\n" ..
    "- Live loot flow: item opens, roll choices, council votes, awards, assignments, unassigns, and Auto-Pass notices.\n" ..
    "- A member without a shared profile first receives the smaller bootstrap: profile settings, permissions, and the current live loot data needed to receive and answer roll popups.\n" ..
    "- This is the roll-ready gate. A member can participate as soon as this priority is applied and acknowledged; full history is not required."
  )

  local tSyncPrioritySession = tSyncPriorityFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tSyncPrioritySession:SetJustifyH("LEFT")
  tSyncPrioritySession:SetText(
    "|cffa87ae82. Priority 2 - Session and profile continuity|r\n" ..
    "- Current-session checkpoint details and exact Current Session State proof follow after Priority 1 has room in the queue.\n" ..
    "- Targeted profile repairs use controlled queued work and keep everyone converging without blocking roll-ready players from raiding.\n" ..
    "- Small patches are preferred whenever LidCouncil can identify the changed rows or subdomains."
  )

  local tSyncPriorityHistory = tSyncPriorityFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tSyncPriorityHistory:SetJustifyH("LEFT")
  tSyncPriorityHistory:SetText(
    "|cffa87ae83. Priority 3 - Historical and statistical data|r\n" ..
    "- Older session and item history, deferred Loot List archive rows, and Fun Award/statistical history.\n" ..
    "- This work is intentionally paced in the background and yields whenever Priority 1 or Priority 2 traffic needs the queue.\n" ..
    "- It improves historical completeness but is not required to roll on, vote on, or receive current raid loot."
  )

  local tSyncPriorityRaid = tSyncPriorityFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tSyncPriorityRaid:SetJustifyH("LEFT")
  tSyncPriorityRaid:SetText(
    "|cffa87ae84. What happens when 20 members join|r\n" ..
    "- LidCouncil does not finish every package for Member A before helping Member B. All queued roll-ready work shares the highest priority band.\n" ..
    "- The host sends within WoW's addon-message limits, so transfers are not literally parallel, but background history cannot overtake roll-ready bootstrap work.\n" ..
    "- In the header, an outgoing transfer shows P1 Live, P1 Roll-ready, P2 Session/Profile, or P3 History/Statistics. This is read-only queue visibility.\n" ..
    "- Force Start begins immediately with the host as authoritative. Members already marked roll-ready can continue while remaining history syncs in the background.\n" ..
    "- Members still bootstrap pending stay joined, but may miss early LidCouncil roll popups and should not be relied on for early rolls, votes, or assignments. Their Priority 1 bootstrap continues; Force Start does not queue another full profile copy."
  )

  local tRolesIntro = tRolesFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
  tRolesIntro:SetJustifyH("LEFT")
  tRolesIntro:SetText("Roles")

  local tRolesBasics = tRolesFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tRolesBasics:SetJustifyH("LEFT")
  tRolesBasics:SetText(
    "|cffa87ae81. Bare minimum role purpose|r\n" ..
    "- |cff4adf6bProfile Master|r is the owner of the profile and has full access.\n" ..
    "- |cff4adf6bSession Host|r is responsible for assigning and unassigning loot during the active session.\n" ..
    "- |cff4adf6bCouncil Member|r is responsible for voting and contributing to fair loot sharing decisions.\n" ..
    "- |cff4adf6bRaider|r has transparent access to the Loot List and Loot List details, but is limited in manager options."
  )

  local tRolesMinimum = tRolesFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tRolesMinimum:SetJustifyH("LEFT")
  tRolesMinimum:SetText(
    "|cffa87ae82. Non-revokable minimum rights|r\n" ..
    "- Manage Rights defaults to |cff4adf6bProfile Master|r only. The Master can grant it to Session Host or Council when trusted helpers should manage rights too.\n" ..
    "- Session Host must keep |cff4adf6bManage / Stop Session|r, |cff4adf6bOpen Live Board|r, |cff4adf6bUse Voting|r, and |cff4adf6bAssign Loot|r.\n" ..
    "- Council Member must keep |cff4adf6bOpen Live Board|r and |cff4adf6bUse Voting|r.\n" ..
    "- These rights are not editable, even by the Profile Master, because removing them would eliminate the purpose of the role.\n" ..
    "- If you try to revoke one of these rights, LidCouncil denies the change instead of creating a broken role."
  )

  local tRolesDefaults = tRolesFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tRolesDefaults:SetJustifyH("LEFT")
  tRolesDefaults:SetText(
    "|cffa87ae83. Default LidCouncil rights|r\n" ..
    "- The explanations above describe only the bare minimum function of each role.\n" ..
    "- LidCouncil's default right settings grant more access to Session Host and Council than the minimum.\n" ..
    "- Those defaults are intentional: LidCouncil has carefully studied the practical right settings needed to manage many field situations properly.\n" ..
    "- In real raids, trusted helpers may need profile, session, or Loot List tools even when those tools are above the strict role minimum.\n" ..
    "- The Profile Master can still revoke non-critical access when a stricter setup is needed."
  )

  local tRolesPractical = tRolesFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tRolesPractical:SetJustifyH("LEFT")
  tRolesPractical:SetText(
    "|cffa87ae84. Practical model|r\n" ..
    "- Use |cff4adf6bProfile Master|r for the person who owns and maintains the profile.\n" ..
    "- Use |cff4adf6bSession Host|r for the person currently running loot assignment on the Live Board.\n" ..
    "- Use |cff4adf6bCouncil Member|r for trusted voters who help decide loot distribution.\n" ..
    "- Use |cff4adf6bRaider|r for regular participants who should see transparent loot data without management access."
  )

  local tRightsIntro = tRightsFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
  tRightsIntro:SetJustifyH("LEFT")
  tRightsIntro:SetText("Rights Tutorial")

  local tRightsProfile = tRightsFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tRightsProfile:SetJustifyH("LEFT")
  tRightsProfile:SetText(
    "|cffa87ae81. Profile rights|r\n" ..
    "- Profile roles and permission policy can be reviewed from the selected profile; there is no separate View Roles & Rights permission.\n" ..
    "- |cff4adf6bManage Member Roles|r allows changing member roles. |cff4adf6bManage Rights|r separately allows granting or revoking permission-policy rights. Manage Rights defaults to Master only, but the Master can grant it to Session Host or Council.\n" ..
    "- |cff4adf6bEdit Raid Settings|r allows changing the selected raid thumbnail and raid association for the profile.\n" ..
    "- |cff4adf6bEdit Roll Settings|r allows adding, removing, reordering, importing, and editing roll options.\n" ..
    "- |cff4adf6bEdit Point Settings|r allows changing the point mode and point rules.\n" ..
    "- |cff4adf6bDelete Local Profile Copy|r allows deleting only your local profile copy. This right is fixed on for every role, never deletes a profile for everyone, and is blocked while any LidCouncil session is active."
  )

  local tRightsSession = tRightsFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tRightsSession:SetJustifyH("LEFT")
  tRightsSession:SetText(
    "|cffa87ae82. Session rights|r\n" ..
    "- |cff4adf6bStart Session|r allows preparing a new lobby and starting its LidCouncil session for the active profile when no other session is running.\n" ..
    "- |cff4adf6bManage / Stop Session|r is the active-session administration right. For the active host it enables Stop Session, Send Invite, Refresh Readiness, Remove Member, precise-difference and repair controls, Send Board Sync, and Targeted Host Override.\n" ..
    "- |cff4adf6bOpen Live Board|r allows opening and reviewing the Live Board section. This does not by itself grant item assignment.\n" ..
    "- |cff4adf6bUse Voting|r allows casting and removing your vote on Live Board member rows.\n" ..
    "- |cff4adf6bAssign Loot|r allows the active session host to assign and unassign items on the Live Board. The client must still be the actual session host; granting this right alone does not let a non-host assign loot."
  )

  local tRightsLootList = tRightsFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tRightsLootList:SetJustifyH("LEFT")
  tRightsLootList:SetText(
    "|cffa87ae83. Loot List rights|r\n" ..
    "- |cff4adf6bEdit Loot List|r allows direct Loot List edits such as changing loot-related member data where the UI exposes edit controls.\n" ..
    "- |cff4adf6bEdit Manual Points|r allows adding or deleting manual point entries.\n" ..
    "- |cff4adf6bEdit Main / Alt|r allows changing main/alt relationships in Loot List details.\n" ..
    "- |cff4adf6bEdit Notes|r allows editing character notes.\n" ..
    "- |cff4adf6bDelete Loot List Character|r allows deleting a character and all their points from Loot List details. It defaults to Master, Session Host, and Council, but the Profile Master can revoke it for Session Host, Council, or Raider.\n" ..
    "- Viewing the Loot List and Loot List details is intentionally available to Raiders by default, but edit controls are disabled unless the relevant edit right is granted."
  )

  local tRightsSync = tRightsFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
  tRightsSync:SetJustifyH("LEFT")
  tRightsSync:SetText(
    "|cffa87ae84. When changed rights reach other players|r\n" ..
    "- Changing rights with no shared session updates only your local authoritative profile copy immediately.\n" ..
    "- During a matching prepared or active session, role/profile synchronization can carry the changed state. Compact readiness proof, a later join, or manual |cff4adf6bPush Repair|r can detect and repair a copy that missed it.\n" ..
    "- If another member has a newer or diverged shared copy, LidCouncil shows the sync/review flow instead of blindly overwriting."
  )

  editor.tutorialBeginnerScroll = tBeginnerScroll
  editor.tutorialBeginnerFrame = tBeginnerFrame
  editor.tutorialBeginnerHeader = tBeginnerHeader
  editor.tutorialBeginnerCards = tBeginnerCards
  editor.tutorialBeginnerIntro = tBeginnerIntro
  editor.tutorialBeginnerNote = tBeginnerNote
  editor.tutorialBeginnerAfter = tBeginnerAfter
  editor.tutorialBeginnerPost = tBeginnerPost
  editor.tutorialPanelsScroll = tPanelsScroll
  editor.tutorialPanelsFrame = tPanelsFrame
  editor.tutorialPanelsHeader = tPanelsHeader
  editor.tutorialPanelsCards = tPanelsCards
  editor.tutorialPanelsIntro = tPanelsIntro
  editor.tutorialPanelsTitle = tPanelsTitle
  editor.tutorialPanelsNote = tPanelsNote
  editor.tutorialPanelsAfter = tPanelsAfter
  editor.tutorialPanelsPost = tPanelsPost
  editor.tutorialProfilesScroll = tProfilesScroll
  editor.tutorialProfilesFrame = tProfilesFrame
  editor.tutorialProfilesHeader = tProfilesHeader
  editor.tutorialProfilesCards = tProfilesCards
  editor.tutorialProfilesIntro = tProfilesIntro
  editor.tutorialProfilesGroupsTitle = tProfilesGroupsTitle
  editor.tutorialProfilesGroupsNote = tProfilesGroupsNote
  editor.tutorialProfilesGroupsAfter = tProfilesGroupsAfter
  editor.tutorialProfilesGroupsPost = tProfilesGroupsPost

  tBeginnerScroll:Hide()
  tProfilesScroll:Hide()
  tCustomScroll:Hide()
  tSimulationScroll:Hide()
  tAddFolderScroll:Hide()
  tGroupsScroll:Hide()
  tWorkflowScroll:Hide()
  tAutoPassScroll:Hide()
  tCombatScroll:Hide()
  tSyncScroll:Hide()
  tSyncPriorityScroll:Hide()
  editor.tutorialSyncAudit.scroll:Hide()
  editor.tutorialSyncProgress.scroll:Hide()
  editor.tutorialSyncWorkflow4.scroll:Hide()
  editor.tutorialSyncWorkflow5.scroll:Hide()
  editor.tutorialSyncWorkflow6.scroll:Hide()
  editor.tutorialSyncWorkflow7.scroll:Hide()
  editor.tutorialSyncWorkflow8.scroll:Hide()
  editor.tutorialSyncWorkflow10.scroll:Hide()
  editor.tutorialSyncWorkflow11.scroll:Hide()
  editor.tutorialSyncWorkflow12.scroll:Hide()
  editor.tutorialSyncWorkflow13.scroll:Hide()
  editor.tutorialSyncWorkflow14.scroll:Hide()
  editor.tutorialSyncWorkflow15.scroll:Hide()
  editor.tutorialPreflight.scroll:Hide()
  editor.tutorialProfileRecovery.scroll:Hide()
  tRolesScroll:Hide()
  tRightsScroll:Hide()

  editor.tutorialCustomScroll = tCustomScroll
  editor.tutorialCustomFrame = tCustomFrame
  editor.tutorialCustomHeader = tCustomHeader
  editor.tutorialCustomCards = tCustomCards
  editor.tutorialCustomIntro = tCustomIntro
  editor.tutorialCustomTitle = tCustomTitle
  editor.tutorialCustomNote = tCustomNote
  editor.tutorialCustomAfter = tCustomAfter
  editor.tutorialCustomPost = tCustomPost
  editor.tutorialSimulationScroll = tSimulationScroll
  editor.tutorialSimulationFrame = tSimulationFrame
  editor.tutorialSimulationHeader = tSimulationHeader
  editor.tutorialSimulationCards = tSimulationCards
  editor.tutorialSimulationIntro = tSimulationIntro
  editor.tutorialSimulationNote = tSimulationNote
  editor.tutorialSimulationAfter = tSimulationAfter
  editor.tutorialSimulationPost = tSimulationPost
  editor.tutorialAddFolderScroll = tAddFolderScroll
  editor.tutorialAddFolderFrame = tAddFolderFrame
  editor.tutorialAddFolderHeader = tAddFolderHeader
  editor.tutorialAddFolderCards = tAddFolderCards
  editor.tutorialAddFolderIntro = tAddFolderIntro
  editor.tutorialAddFolderStepTitle = tAddFolderStepTitle
  editor.tutorialAddFolderNote = tAddFolderNote
  editor.tutorialAddFolderImage = tAddFolderImage
  editor.tutorialAddFolderAfter = tAddFolderAfter
  editor.tutorialAddFolderPost = tAddFolderPost
  editor.tutorialAddFolderPostImage = tAddFolderPostImage
  editor.tutorialAddFolderPreviewNote = tAddFolderPreviewNote
  editor.tutorialAddFolderPreviewImage = tAddFolderPreviewImage
  editor.tutorialGroupsScroll = tGroupsScroll
  editor.tutorialGroupsFrame = tGroupsFrame
  editor.tutorialGroupsHeader = tGroupsHeader
  editor.tutorialGroupsCards = tGroupsCards
  editor.tutorialGroupsIntro = tGroupsIntro
  editor.tutorialGroupsNote = tGroupsNote
  editor.tutorialGroupsAfter = tGroupsAfter
  editor.tutorialGroupsPost = tGroupsPost
  editor.tutorialWorkflowScroll = tWorkflowScroll
  editor.tutorialWorkflowFrame = tWorkflowFrame
  editor.tutorialWorkflowHeader = tWorkflowHeader
  editor.tutorialWorkflowCards = tWorkflowCards
  editor.tutorialWorkflowIntro = tWorkflowIntro
  editor.tutorialWorkflowNote = tWorkflowNote
  editor.tutorialWorkflowAfter = tWorkflowAfter
  editor.tutorialWorkflowLive = tWorkflowLive
  editor.tutorialWorkflowPost = tWorkflowPost
  editor.tutorialAutoPassScroll = tAutoPassScroll
  editor.tutorialAutoPassFrame = tAutoPassFrame
  editor.tutorialAutoPassHeader = tAutoPassHeader
  editor.tutorialAutoPassCards = tAutoPassCards
  editor.tutorialAutoPassIntro = tAutoPassIntro
  editor.tutorialAutoPassBasics = tAutoPassBasics
  editor.tutorialAutoPassDefaults = tAutoPassDefaults
  editor.tutorialAutoPassControl = tAutoPassControl
  editor.tutorialAutoPassExamples = tAutoPassExamples
  editor.tutorialCombatScroll = tCombatScroll
  editor.tutorialCombatFrame = tCombatFrame
  editor.tutorialCombatHeader = tCombatHeader
  editor.tutorialCombatCards = tCombatCards
  editor.tutorialCombatIntro = tCombatIntro
  editor.tutorialCombatAllowed = tCombatAllowed
  editor.tutorialCombatBlocked = tCombatBlocked
  editor.tutorialCombatBehavior = tCombatBehavior
  editor.tutorialSyncScroll = tSyncScroll
  editor.tutorialSyncFrame = tSyncFrame
  editor.tutorialSyncHeader = tSyncHeader
  editor.tutorialSyncCards = tSyncCards
  editor.tutorialSyncIntro = tSyncIntro
  editor.tutorialSyncTriggers = tSyncTriggers
  editor.tutorialSyncPaths = tSyncPaths
  editor.tutorialSyncGaps = tSyncGaps
  editor.tutorialSyncNotes = tSyncNotes
  editor.tutorialSyncPriorityScroll = tSyncPriorityScroll
  editor.tutorialSyncPriorityFrame = tSyncPriorityFrame
  editor.tutorialSyncPriorityHeader = tSyncPriorityHeader
  editor.tutorialSyncPriorityCards = tSyncPriorityCards
  editor.tutorialSyncPriorityIntro = tSyncPriorityIntro
  editor.tutorialSyncPriorityReady = tSyncPriorityReady
  editor.tutorialSyncPrioritySession = tSyncPrioritySession
  editor.tutorialSyncPriorityHistory = tSyncPriorityHistory
  editor.tutorialSyncPriorityRaid = tSyncPriorityRaid
  editor.tutorialRolesScroll = tRolesScroll
  editor.tutorialRolesFrame = tRolesFrame
  editor.tutorialRolesHeader = tRolesHeader
  editor.tutorialRolesCards = tRolesCards
  editor.tutorialRolesIntro = tRolesIntro
  editor.tutorialRolesBasics = tRolesBasics
  editor.tutorialRolesMinimum = tRolesMinimum
  editor.tutorialRolesDefaults = tRolesDefaults
  editor.tutorialRolesPractical = tRolesPractical
  editor.tutorialRightsScroll = tRightsScroll
  editor.tutorialRightsFrame = tRightsFrame
  editor.tutorialRightsHeader = tRightsHeader
  editor.tutorialRightsCards = tRightsCards
  editor.tutorialRightsIntro = tRightsIntro
  editor.tutorialRightsProfile = tRightsProfile
  editor.tutorialRightsSession = tRightsSession
  editor.tutorialRightsLootList = tRightsLootList
  editor.tutorialRightsSync = tRightsSync

  local tutorialBanner = CreateFrame("Frame", nil, tutorialRightBg)
  tutorialBanner:SetPoint("TOPLEFT", 6, -6)
  tutorialBanner:SetPoint("BOTTOMRIGHT", -6, 6)
  local bannerTex = tutorialBanner:CreateTexture(nil, "ARTWORK")
  bannerTex:SetPoint("TOPLEFT", 18, -18)
  RegisterTutorialTexture(editor, bannerTex, "Interface\\AddOns\\LidCouncil\\Media\\LidCouncil\\Tutorials\\CBG_1_Custom_Banner.tga")
  bannerTex:SetTexCoord(0, 1, 0, 1)
  local BANNER_W, BANNER_H = 3173, 1117
  local function LayoutBanner()
    local avail = (tProfilesFrame and tProfilesFrame:GetWidth()) or (tutorialRightBg:GetWidth() - 62)
    if not avail or avail < 1 then avail = 1 end
    local leftPad = 18
    local cardGap = 20
    local rowW = avail - leftPad
    local cardW = math.max(1, math.floor((rowW - cardGap) / 2))
    local headerW = cardW * 2 + cardGap
    local scale = headerW / BANNER_W
    if scale > 1 then scale = 1 end
    local headerH = math.floor((BANNER_H * scale) + 0.5)
    bannerTex:SetSize(headerW, headerH)
  end
  tutorialBanner:SetScript("OnShow", LayoutBanner)
  tutorialRightBg:HookScript("OnSizeChanged", LayoutBanner)
  editor.tutorialBanner = tutorialBanner

  function editor:ShowTutorialSection(which)
    if self.RefreshTutorialAccentMarkup then
      self:RefreshTutorialAccentMarkup()
    end
    local requestedSection = which == "Groups" and "Profiles" or (which or "Beginner")
    if CHECKLIST_DEVELOPER_SECTIONS[requestedSection] and not ChecklistDeveloperPagesVisible() then
      requestedSection = "Beginner"
    end
    self.tutorialSection = requestedSection
    local function ResetScrollTop(scroll)
      if scroll and scroll.SetVerticalScroll then
        scroll:SetVerticalScroll(0)
      end
    end
    local function HideTutorialScrolls()
      if self.tutorialBanner then self.tutorialBanner:Hide() end
      if self.tutorialBeginnerScroll then self.tutorialBeginnerScroll:Hide() end
      if self.tutorialPanelsScroll then self.tutorialPanelsScroll:Hide() end
      if self.tutorialProfilesScroll then self.tutorialProfilesScroll:Hide() end
      if self.tutorialCustomScroll then self.tutorialCustomScroll:Hide() end
      if self.tutorialSimulationScroll then self.tutorialSimulationScroll:Hide() end
      if self.tutorialAddFolderScroll then self.tutorialAddFolderScroll:Hide() end
      if self.tutorialGroupsScroll then self.tutorialGroupsScroll:Hide() end
      if self.tutorialWorkflowScroll then self.tutorialWorkflowScroll:Hide() end
      if self.tutorialAutoPassScroll then self.tutorialAutoPassScroll:Hide() end
      if self.tutorialCombatScroll then self.tutorialCombatScroll:Hide() end
      if self.tutorialSyncScroll then self.tutorialSyncScroll:Hide() end
      if self.tutorialSyncPriorityScroll then self.tutorialSyncPriorityScroll:Hide() end
      if self.tutorialSyncAudit and self.tutorialSyncAudit.scroll then self.tutorialSyncAudit.scroll:Hide() end
      if self.tutorialSyncProgress and self.tutorialSyncProgress.scroll then self.tutorialSyncProgress.scroll:Hide() end
      if self.tutorialSyncWorkflow4 and self.tutorialSyncWorkflow4.scroll then self.tutorialSyncWorkflow4.scroll:Hide() end
      if self.tutorialSyncWorkflow5 and self.tutorialSyncWorkflow5.scroll then self.tutorialSyncWorkflow5.scroll:Hide() end
      if self.tutorialSyncWorkflow6 and self.tutorialSyncWorkflow6.scroll then self.tutorialSyncWorkflow6.scroll:Hide() end
      if self.tutorialSyncWorkflow7 and self.tutorialSyncWorkflow7.scroll then self.tutorialSyncWorkflow7.scroll:Hide() end
      if self.tutorialSyncWorkflow8 and self.tutorialSyncWorkflow8.scroll then self.tutorialSyncWorkflow8.scroll:Hide() end
      if self.tutorialSyncWorkflow10 and self.tutorialSyncWorkflow10.scroll then self.tutorialSyncWorkflow10.scroll:Hide() end
      if self.tutorialSyncWorkflow11 and self.tutorialSyncWorkflow11.scroll then self.tutorialSyncWorkflow11.scroll:Hide() end
      if self.tutorialSyncWorkflow12 and self.tutorialSyncWorkflow12.scroll then self.tutorialSyncWorkflow12.scroll:Hide() end
      if self.tutorialSyncWorkflow13 and self.tutorialSyncWorkflow13.scroll then self.tutorialSyncWorkflow13.scroll:Hide() end
      if self.tutorialSyncWorkflow14 and self.tutorialSyncWorkflow14.scroll then self.tutorialSyncWorkflow14.scroll:Hide() end
      if self.tutorialSyncWorkflow15 and self.tutorialSyncWorkflow15.scroll then self.tutorialSyncWorkflow15.scroll:Hide() end
      if self.tutorialPossibleFeatures and self.tutorialPossibleFeatures.scroll then self.tutorialPossibleFeatures.scroll:Hide() end
      if self.tutorialPreflight and self.tutorialPreflight.scroll then self.tutorialPreflight.scroll:Hide() end
      if self.tutorialProfileRecovery and self.tutorialProfileRecovery.scroll then self.tutorialProfileRecovery.scroll:Hide() end
      if self.tutorialRolesScroll then self.tutorialRolesScroll:Hide() end
      if self.tutorialRightsScroll then self.tutorialRightsScroll:Hide() end
    end
    HideTutorialScrolls()
    if self.tutorialSection == "Beginner" then
      if self.tutorialBeginnerScroll then self.tutorialBeginnerScroll:Show() end
      ResetScrollTop(self.tutorialBeginnerScroll)
    elseif self.tutorialSection == "Profiles" then
      if self.tutorialBanner then self.tutorialBanner:Hide() end
      if self.tutorialPanelsScroll then self.tutorialPanelsScroll:Hide() end
      if self.tutorialCustomScroll then self.tutorialCustomScroll:Hide() end
      if self.tutorialAddFolderScroll then self.tutorialAddFolderScroll:Hide() end
      if self.tutorialGroupsScroll then self.tutorialGroupsScroll:Hide() end
      if self.tutorialWorkflowScroll then self.tutorialWorkflowScroll:Hide() end
      if self.tutorialAutoPassScroll then self.tutorialAutoPassScroll:Hide() end
      if self.tutorialSyncScroll then self.tutorialSyncScroll:Hide() end
      if self.tutorialRightsScroll then self.tutorialRightsScroll:Hide() end
      if self.tutorialProfilesScroll then self.tutorialProfilesScroll:Show() end
      ResetScrollTop(self.tutorialProfilesScroll)
    elseif self.tutorialSection == "Custom" then
      if self.tutorialBanner then self.tutorialBanner:Hide() end
      if self.tutorialPanelsScroll then self.tutorialPanelsScroll:Hide() end
      if self.tutorialProfilesScroll then self.tutorialProfilesScroll:Hide() end
      if self.tutorialAddFolderScroll then self.tutorialAddFolderScroll:Hide() end
      if self.tutorialGroupsScroll then self.tutorialGroupsScroll:Hide() end
      if self.tutorialWorkflowScroll then self.tutorialWorkflowScroll:Hide() end
      if self.tutorialAutoPassScroll then self.tutorialAutoPassScroll:Hide() end
      if self.tutorialSyncScroll then self.tutorialSyncScroll:Hide() end
      if self.tutorialRightsScroll then self.tutorialRightsScroll:Hide() end
      if self.tutorialCustomScroll then self.tutorialCustomScroll:Show() end
      ResetScrollTop(self.tutorialCustomScroll)
    elseif self.tutorialSection == "RunSimulation" then
      if self.tutorialSimulationScroll then self.tutorialSimulationScroll:Show() end
      ResetScrollTop(self.tutorialSimulationScroll)
    elseif self.tutorialSection == "AddFolder" then
      if self.tutorialBanner then self.tutorialBanner:Hide() end
      if self.tutorialPanelsScroll then self.tutorialPanelsScroll:Hide() end
      if self.tutorialProfilesScroll then self.tutorialProfilesScroll:Hide() end
      if self.tutorialCustomScroll then self.tutorialCustomScroll:Hide() end
      if self.tutorialGroupsScroll then self.tutorialGroupsScroll:Hide() end
      if self.tutorialWorkflowScroll then self.tutorialWorkflowScroll:Hide() end
      if self.tutorialAutoPassScroll then self.tutorialAutoPassScroll:Hide() end
      if self.tutorialSyncScroll then self.tutorialSyncScroll:Hide() end
      if self.tutorialRightsScroll then self.tutorialRightsScroll:Hide() end
      if self.tutorialAddFolderScroll then self.tutorialAddFolderScroll:Show() end
      ResetScrollTop(self.tutorialAddFolderScroll)
    elseif self.tutorialSection == "Groups" then
      if self.tutorialBanner then self.tutorialBanner:Hide() end
      if self.tutorialPanelsScroll then self.tutorialPanelsScroll:Hide() end
      if self.tutorialProfilesScroll then self.tutorialProfilesScroll:Hide() end
      if self.tutorialCustomScroll then self.tutorialCustomScroll:Hide() end
      if self.tutorialAddFolderScroll then self.tutorialAddFolderScroll:Hide() end
      if self.tutorialWorkflowScroll then self.tutorialWorkflowScroll:Hide() end
      if self.tutorialAutoPassScroll then self.tutorialAutoPassScroll:Hide() end
      if self.tutorialSyncScroll then self.tutorialSyncScroll:Hide() end
      if self.tutorialRightsScroll then self.tutorialRightsScroll:Hide() end
      if self.tutorialGroupsScroll then self.tutorialGroupsScroll:Show() end
      ResetScrollTop(self.tutorialGroupsScroll)
    elseif self.tutorialSection == "Workflow" then
      if self.tutorialBanner then self.tutorialBanner:Hide() end
      if self.tutorialPanelsScroll then self.tutorialPanelsScroll:Hide() end
      if self.tutorialProfilesScroll then self.tutorialProfilesScroll:Hide() end
      if self.tutorialCustomScroll then self.tutorialCustomScroll:Hide() end
      if self.tutorialAddFolderScroll then self.tutorialAddFolderScroll:Hide() end
      if self.tutorialGroupsScroll then self.tutorialGroupsScroll:Hide() end
      if self.tutorialAutoPassScroll then self.tutorialAutoPassScroll:Hide() end
      if self.tutorialSyncScroll then self.tutorialSyncScroll:Hide() end
      if self.tutorialRightsScroll then self.tutorialRightsScroll:Hide() end
      if self.tutorialWorkflowScroll then self.tutorialWorkflowScroll:Show() end
      ResetScrollTop(self.tutorialWorkflowScroll)
    elseif self.tutorialSection == "AutoPass" then
      if self.tutorialBanner then self.tutorialBanner:Hide() end
      if self.tutorialPanelsScroll then self.tutorialPanelsScroll:Hide() end
      if self.tutorialProfilesScroll then self.tutorialProfilesScroll:Hide() end
      if self.tutorialCustomScroll then self.tutorialCustomScroll:Hide() end
      if self.tutorialAddFolderScroll then self.tutorialAddFolderScroll:Hide() end
      if self.tutorialGroupsScroll then self.tutorialGroupsScroll:Hide() end
      if self.tutorialWorkflowScroll then self.tutorialWorkflowScroll:Hide() end
      if self.tutorialSyncScroll then self.tutorialSyncScroll:Hide() end
      if self.tutorialRightsScroll then self.tutorialRightsScroll:Hide() end
      if self.tutorialAutoPassScroll then self.tutorialAutoPassScroll:Show() end
      ResetScrollTop(self.tutorialAutoPassScroll)
    elseif self.tutorialSection == "CombatRestrictions" then
      if self.tutorialBanner then self.tutorialBanner:Hide() end
      if self.tutorialPanelsScroll then self.tutorialPanelsScroll:Hide() end
      if self.tutorialProfilesScroll then self.tutorialProfilesScroll:Hide() end
      if self.tutorialCustomScroll then self.tutorialCustomScroll:Hide() end
      if self.tutorialAddFolderScroll then self.tutorialAddFolderScroll:Hide() end
      if self.tutorialGroupsScroll then self.tutorialGroupsScroll:Hide() end
      if self.tutorialWorkflowScroll then self.tutorialWorkflowScroll:Hide() end
      if self.tutorialAutoPassScroll then self.tutorialAutoPassScroll:Hide() end
      if self.tutorialSyncScroll then self.tutorialSyncScroll:Hide() end
      if self.tutorialRightsScroll then self.tutorialRightsScroll:Hide() end
      if self.tutorialCombatScroll then self.tutorialCombatScroll:Show() end
      ResetScrollTop(self.tutorialCombatScroll)
    elseif self.tutorialSection == "SyncWorkflow" then
      if self.tutorialBanner then self.tutorialBanner:Hide() end
      if self.tutorialPanelsScroll then self.tutorialPanelsScroll:Hide() end
      if self.tutorialProfilesScroll then self.tutorialProfilesScroll:Hide() end
      if self.tutorialCustomScroll then self.tutorialCustomScroll:Hide() end
      if self.tutorialAddFolderScroll then self.tutorialAddFolderScroll:Hide() end
      if self.tutorialGroupsScroll then self.tutorialGroupsScroll:Hide() end
      if self.tutorialWorkflowScroll then self.tutorialWorkflowScroll:Hide() end
      if self.tutorialAutoPassScroll then self.tutorialAutoPassScroll:Hide() end
      if self.tutorialRightsScroll then self.tutorialRightsScroll:Hide() end
      if self.tutorialSyncScroll then self.tutorialSyncScroll:Show() end
      ResetScrollTop(self.tutorialSyncScroll)
    elseif self.tutorialSection == "SyncPriority" then
      if self.tutorialSyncPriorityScroll then self.tutorialSyncPriorityScroll:Show() end
      ResetScrollTop(self.tutorialSyncPriorityScroll)
    elseif self.tutorialSection == "SyncWorkflow2" then
      if self.tutorialSyncAudit and self.tutorialSyncAudit.scroll then self.tutorialSyncAudit.scroll:Show() end
      ResetScrollTop(self.tutorialSyncAudit and self.tutorialSyncAudit.scroll)
    elseif self.tutorialSection == "SyncWorkflow3" then
      if self.tutorialSyncProgress and self.tutorialSyncProgress.scroll then self.tutorialSyncProgress.scroll:Show() end
      ResetScrollTop(self.tutorialSyncProgress and self.tutorialSyncProgress.scroll)
    elseif self.tutorialSection == "SyncWorkflow4" then
      if self.tutorialSyncWorkflow4 and self.tutorialSyncWorkflow4.scroll then self.tutorialSyncWorkflow4.scroll:Show() end
      ResetScrollTop(self.tutorialSyncWorkflow4 and self.tutorialSyncWorkflow4.scroll)
    elseif self.tutorialSection == "SyncWorkflow5" then
      if self.tutorialSyncWorkflow5 and self.tutorialSyncWorkflow5.scroll then self.tutorialSyncWorkflow5.scroll:Show() end
      ResetScrollTop(self.tutorialSyncWorkflow5 and self.tutorialSyncWorkflow5.scroll)
    elseif self.tutorialSection == "SyncWorkflow6" then
      if self.tutorialSyncWorkflow6 and self.tutorialSyncWorkflow6.scroll then self.tutorialSyncWorkflow6.scroll:Show() end
      ResetScrollTop(self.tutorialSyncWorkflow6 and self.tutorialSyncWorkflow6.scroll)
    elseif self.tutorialSection == "SyncWorkflow7" then
      if self.tutorialSyncWorkflow7 and self.tutorialSyncWorkflow7.scroll then self.tutorialSyncWorkflow7.scroll:Show() end
      ResetScrollTop(self.tutorialSyncWorkflow7 and self.tutorialSyncWorkflow7.scroll)
    elseif self.tutorialSection == "SyncWorkflow8" then
      if self.tutorialSyncWorkflow8 and self.tutorialSyncWorkflow8.scroll then self.tutorialSyncWorkflow8.scroll:Show() end
      ResetScrollTop(self.tutorialSyncWorkflow8 and self.tutorialSyncWorkflow8.scroll)
    elseif self.tutorialSection == "SyncWorkflow10" then
      if self.tutorialSyncWorkflow10 and self.tutorialSyncWorkflow10.scroll then self.tutorialSyncWorkflow10.scroll:Show() end
      ResetScrollTop(self.tutorialSyncWorkflow10 and self.tutorialSyncWorkflow10.scroll)
    elseif self.tutorialSection == "SyncWorkflow11" then
      if self.tutorialSyncWorkflow11 and self.tutorialSyncWorkflow11.scroll then self.tutorialSyncWorkflow11.scroll:Show() end
      ResetScrollTop(self.tutorialSyncWorkflow11 and self.tutorialSyncWorkflow11.scroll)
    elseif self.tutorialSection == "SyncWorkflow12" then
      if self.tutorialSyncWorkflow12 and self.tutorialSyncWorkflow12.scroll then self.tutorialSyncWorkflow12.scroll:Show() end
      ResetScrollTop(self.tutorialSyncWorkflow12 and self.tutorialSyncWorkflow12.scroll)
    elseif self.tutorialSection == "SyncWorkflow13" then
      if self.tutorialSyncWorkflow13 and self.tutorialSyncWorkflow13.scroll then self.tutorialSyncWorkflow13.scroll:Show() end
      ResetScrollTop(self.tutorialSyncWorkflow13 and self.tutorialSyncWorkflow13.scroll)
    elseif self.tutorialSection == "SyncWorkflow14" then
      if self.tutorialSyncWorkflow14 and self.tutorialSyncWorkflow14.scroll then self.tutorialSyncWorkflow14.scroll:Show() end
      ResetScrollTop(self.tutorialSyncWorkflow14 and self.tutorialSyncWorkflow14.scroll)
    elseif self.tutorialSection == "SyncWorkflow15" then
      if self.tutorialSyncWorkflow15 and self.tutorialSyncWorkflow15.scroll then self.tutorialSyncWorkflow15.scroll:Show() end
      ResetScrollTop(self.tutorialSyncWorkflow15 and self.tutorialSyncWorkflow15.scroll)
    elseif self.tutorialSection == "PossibleFeatures" then
      if self.tutorialPossibleFeatures and self.tutorialPossibleFeatures.scroll then self.tutorialPossibleFeatures.scroll:Show() end
      ResetScrollTop(self.tutorialPossibleFeatures and self.tutorialPossibleFeatures.scroll)
    elseif self.tutorialSection == "Preflight" then
      if self.tutorialPreflight and self.tutorialPreflight.scroll then self.tutorialPreflight.scroll:Show() end
      ResetScrollTop(self.tutorialPreflight and self.tutorialPreflight.scroll)
    elseif self.tutorialSection == "ProfileRecovery" then
      if self.tutorialProfileRecovery and self.tutorialProfileRecovery.scroll then self.tutorialProfileRecovery.scroll:Show() end
      ResetScrollTop(self.tutorialProfileRecovery and self.tutorialProfileRecovery.scroll)
    elseif self.tutorialSection == "Roles" then
      if self.tutorialBanner then self.tutorialBanner:Hide() end
      if self.tutorialPanelsScroll then self.tutorialPanelsScroll:Hide() end
      if self.tutorialProfilesScroll then self.tutorialProfilesScroll:Hide() end
      if self.tutorialCustomScroll then self.tutorialCustomScroll:Hide() end
      if self.tutorialAddFolderScroll then self.tutorialAddFolderScroll:Hide() end
      if self.tutorialGroupsScroll then self.tutorialGroupsScroll:Hide() end
      if self.tutorialWorkflowScroll then self.tutorialWorkflowScroll:Hide() end
      if self.tutorialAutoPassScroll then self.tutorialAutoPassScroll:Hide() end
      if self.tutorialSyncScroll then self.tutorialSyncScroll:Hide() end
      if self.tutorialRightsScroll then self.tutorialRightsScroll:Hide() end
      if self.tutorialRolesScroll then self.tutorialRolesScroll:Show() end
      ResetScrollTop(self.tutorialRolesScroll)
    elseif self.tutorialSection == "Rights" then
      if self.tutorialBanner then self.tutorialBanner:Hide() end
      if self.tutorialPanelsScroll then self.tutorialPanelsScroll:Hide() end
      if self.tutorialProfilesScroll then self.tutorialProfilesScroll:Hide() end
      if self.tutorialCustomScroll then self.tutorialCustomScroll:Hide() end
      if self.tutorialAddFolderScroll then self.tutorialAddFolderScroll:Hide() end
      if self.tutorialGroupsScroll then self.tutorialGroupsScroll:Hide() end
      if self.tutorialWorkflowScroll then self.tutorialWorkflowScroll:Hide() end
      if self.tutorialAutoPassScroll then self.tutorialAutoPassScroll:Hide() end
      if self.tutorialSyncScroll then self.tutorialSyncScroll:Hide() end
      if self.tutorialRightsScroll then self.tutorialRightsScroll:Show() end
      ResetScrollTop(self.tutorialRightsScroll)
    elseif self.tutorialSection == "Panels" then
      if self.tutorialBanner then self.tutorialBanner:Hide() end
      if self.tutorialProfilesScroll then self.tutorialProfilesScroll:Hide() end
      if self.tutorialCustomScroll then self.tutorialCustomScroll:Hide() end
      if self.tutorialAddFolderScroll then self.tutorialAddFolderScroll:Hide() end
      if self.tutorialGroupsScroll then self.tutorialGroupsScroll:Hide() end
      if self.tutorialWorkflowScroll then self.tutorialWorkflowScroll:Hide() end
      if self.tutorialAutoPassScroll then self.tutorialAutoPassScroll:Hide() end
      if self.tutorialSyncScroll then self.tutorialSyncScroll:Hide() end
      if self.tutorialRightsScroll then self.tutorialRightsScroll:Hide() end
      if self.tutorialPanelsScroll then self.tutorialPanelsScroll:Show() end
      ResetScrollTop(self.tutorialPanelsScroll)
    else
      if self.tutorialProfilesScroll then self.tutorialProfilesScroll:Hide() end
      if self.tutorialPanelsScroll then self.tutorialPanelsScroll:Hide() end
      if self.tutorialCustomScroll then self.tutorialCustomScroll:Hide() end
      if self.tutorialAddFolderScroll then self.tutorialAddFolderScroll:Hide() end
      if self.tutorialGroupsScroll then self.tutorialGroupsScroll:Hide() end
      if self.tutorialWorkflowScroll then self.tutorialWorkflowScroll:Hide() end
      if self.tutorialAutoPassScroll then self.tutorialAutoPassScroll:Hide() end
      if self.tutorialSyncScroll then self.tutorialSyncScroll:Hide() end
      if self.tutorialRightsScroll then self.tutorialRightsScroll:Hide() end
      if self.tutorialBanner then self.tutorialBanner:Show() end
    end
    if self.LayoutTutorial then self:LayoutTutorial() end
  end

  function editor:LayoutTutorial()
    if self.RefreshTutorialAccentMarkup then
      self:RefreshTutorialAccentMarkup()
    end
    local function LayoutSection(frame, header, cards, scroll, note, noteImage, noteAfter, postText, postImage, previewNote, previewImage, noteAfterCards, introText, stepTitle)
      if not frame or not cards then return 400 end
      local avail = frame:GetWidth()
      if not avail or avail < 1 then return 400 end

      local leftPad = 18
      local cardGap = 20
      local rowW = avail - leftPad
      local cardW = math.max(1, math.floor((rowW - cardGap) / 2))
      local cardH = math.floor(cardW * 0.75)
      local headerW = cardW * 2 + cardGap
      local headerH = 0

      if header then
        headerH = math.floor(cardH * 0.6)
        header:SetSize(headerW, headerH)
        header:ClearAllPoints()
        header:SetPoint("TOPLEFT", leftPad, -18)
      end
      local y = -18
      if header then
        y = y - headerH - 20
      end

      local function LayoutNoteBlocks()
        if note then
          note:ClearAllPoints()
          note:SetPoint("TOPLEFT", leftPad, y)
          note:SetWidth(headerW)
          local noteH = note.GetStringHeight and note:GetStringHeight() or 0
          y = y - noteH - 14
        end
        if noteImage then
          local imgW = math.min(headerW, 520)
          local imgH = math.floor(imgW * 0.55)
          noteImage:ClearAllPoints()
          noteImage:SetPoint("TOPLEFT", leftPad, y)
          noteImage:SetSize(imgW, imgH)
          y = y - imgH - 20
        end
        if noteAfter then
          noteAfter:ClearAllPoints()
          noteAfter:SetPoint("TOPLEFT", leftPad, y)
          noteAfter:SetWidth(headerW)
          local afterH = noteAfter.GetStringHeight and noteAfter:GetStringHeight() or 0
          y = y - afterH - 20
        end
      end

      local function LayoutCards()
        local idx = 1
        local total = (cards and #cards) or 0
        local rows = math.ceil(total / 2)
        for _ = 1, rows do
          local placed = 0
          for col = 1, 2 do
            local card = cards[idx]
            if not card then break end
            card:SetSize(cardW, cardH)
            local x = leftPad + (col - 1) * (cardW + cardGap)
            card:ClearAllPoints()
            card:SetPoint("TOPLEFT", x, y)
            idx = idx + 1
            placed = placed + 1
          end
          if placed > 0 then
            y = y - cardH - 20
          end
        end
      end

      if introText then
        introText:ClearAllPoints()
        introText:SetPoint("TOPLEFT", leftPad, y)
        introText:SetWidth(headerW)
        local introH = introText.GetStringHeight and introText:GetStringHeight() or 0
        y = y - introH - 14
      end

      if noteAfterCards then
        LayoutCards()
        if stepTitle then
          stepTitle:ClearAllPoints()
          stepTitle:SetPoint("TOPLEFT", leftPad, y)
          stepTitle:SetWidth(headerW)
          local stepTitleH = stepTitle.GetStringHeight and stepTitle:GetStringHeight() or 0
          y = y - stepTitleH - 18
        end
        LayoutNoteBlocks()
      else
        LayoutNoteBlocks()
        LayoutCards()
      end

      if previewNote then
        previewNote:ClearAllPoints()
        previewNote:SetPoint("TOPLEFT", leftPad, y)
        if type(previewNote.LayoutForWidth) == "function" then
          previewNote:LayoutForWidth(headerW)
        elseif previewNote.SetWidth then
          previewNote:SetWidth(headerW)
        end
        local previewH = previewNote.GetStringHeight and previewNote:GetStringHeight()
          or (previewNote.GetHeight and previewNote:GetHeight())
          or 0
        y = y - previewH - 14
      end
      if previewImage then
        local imgW = math.min(headerW, 520)
        local imgH = math.floor(imgW * 0.55)
        previewImage:ClearAllPoints()
        previewImage:SetPoint("TOPLEFT", leftPad, y)
        previewImage:SetSize(imgW, imgH)
        y = y - imgH - 20
      end
      if postText then
        postText:ClearAllPoints()
        postText:SetPoint("TOPLEFT", leftPad, y)
        if type(postText.LayoutForWidth) == "function" then
          postText:LayoutForWidth(headerW)
        elseif postText.SetWidth then
          postText:SetWidth(headerW)
        end
        local postH = postText.GetStringHeight and postText:GetStringHeight()
          or (postText.GetHeight and postText:GetHeight())
          or 0
        y = y - postH - 14
      end
      if postImage then
        local imgW = math.min(headerW, 520)
        local imgH = math.floor(imgW * 0.55)
        postImage:ClearAllPoints()
        postImage:SetPoint("TOPLEFT", leftPad, y)
        postImage:SetSize(imgW, imgH)
        y = y - imgH - 20
      end

      local contentHeight = math.max(400, (-y) + 120)
      frame:SetHeight(contentHeight)
      if scroll and scroll.UpdateScrollChildRect then scroll:UpdateScrollChildRect() end
      return contentHeight
    end

    local which = self.tutorialSection or "Beginner"
    if which == "Beginner" then
      tutorialPage.__contentHeight = LayoutSection(
        self.tutorialBeginnerFrame,
        self.tutorialBeginnerHeader,
        self.tutorialBeginnerCards,
        self.tutorialBeginnerScroll,
        self.tutorialBeginnerNote,
        nil,
        self.tutorialBeginnerAfter,
        self.tutorialBeginnerPost,
        nil,
        nil,
        nil,
        false,
        self.tutorialBeginnerIntro,
        nil
      )
    elseif which == "Profiles" then
      tutorialPage.__contentHeight = LayoutSection(
        self.tutorialProfilesFrame,
        self.tutorialProfilesHeader,
        self.tutorialProfilesCards,
        self.tutorialProfilesScroll,
        self.tutorialProfilesGroupsNote,
        nil,
        self.tutorialProfilesGroupsAfter,
        self.tutorialProfilesGroupsPost,
        nil,
        nil,
        nil,
        true,
        self.tutorialProfilesIntro,
        self.tutorialProfilesGroupsTitle
      )
    elseif which == "Custom" then
      tutorialPage.__contentHeight = LayoutSection(
        self.tutorialCustomFrame,
        self.tutorialCustomHeader,
        self.tutorialCustomCards,
        self.tutorialCustomScroll,
        self.tutorialCustomNote,
        nil,
        self.tutorialCustomAfter,
        self.tutorialCustomPost,
        nil,
        nil,
        nil,
        true,
        self.tutorialCustomIntro,
        self.tutorialCustomTitle
      )
    elseif which == "RunSimulation" then
      tutorialPage.__contentHeight = LayoutSection(
        self.tutorialSimulationFrame,
        self.tutorialSimulationHeader,
        self.tutorialSimulationCards,
        self.tutorialSimulationScroll,
        self.tutorialSimulationNote,
        nil,
        self.tutorialSimulationAfter,
        self.tutorialSimulationPost,
        nil,
        nil,
        nil,
        false,
        self.tutorialSimulationIntro,
        nil
      )
    elseif which == "AddFolder" then
      tutorialPage.__contentHeight = LayoutSection(
        self.tutorialAddFolderFrame,
        self.tutorialAddFolderHeader,
        self.tutorialAddFolderCards,
        self.tutorialAddFolderScroll,
        self.tutorialAddFolderNote,
        nil,
        self.tutorialAddFolderAfter,
        self.tutorialAddFolderPost,
        nil,
        nil,
        nil,
        true,
        self.tutorialAddFolderIntro,
        self.tutorialAddFolderStepTitle
      )
    elseif which == "Groups" then
      tutorialPage.__contentHeight = LayoutSection(
        self.tutorialGroupsFrame,
        self.tutorialGroupsHeader,
        self.tutorialGroupsCards,
        self.tutorialGroupsScroll,
        self.tutorialGroupsNote,
        nil,
        self.tutorialGroupsAfter,
        self.tutorialGroupsPost,
        nil,
        nil,
        nil,
        false,
        self.tutorialGroupsIntro,
        nil
      )
    elseif which == "Workflow" then
      tutorialPage.__contentHeight = LayoutSection(
        self.tutorialWorkflowFrame,
        self.tutorialWorkflowHeader,
        self.tutorialWorkflowCards,
        self.tutorialWorkflowScroll,
        self.tutorialWorkflowNote,
        nil,
        self.tutorialWorkflowAfter,
        self.tutorialWorkflowPost,
        nil,
        self.tutorialWorkflowLive,
        nil,
        false,
        self.tutorialWorkflowIntro,
        nil
      )
    elseif which == "AutoPass" then
      tutorialPage.__contentHeight = LayoutSection(
        self.tutorialAutoPassFrame,
        self.tutorialAutoPassHeader,
        self.tutorialAutoPassCards,
        self.tutorialAutoPassScroll,
        self.tutorialAutoPassBasics,
        nil,
        self.tutorialAutoPassDefaults,
        self.tutorialAutoPassExamples,
        nil,
        self.tutorialAutoPassControl,
        nil,
        false,
        self.tutorialAutoPassIntro,
        nil
      )
    elseif which == "CombatRestrictions" then
      tutorialPage.__contentHeight = LayoutSection(
        self.tutorialCombatFrame,
        self.tutorialCombatHeader,
        self.tutorialCombatCards,
        self.tutorialCombatScroll,
        self.tutorialCombatAllowed,
        nil,
        self.tutorialCombatBlocked,
        nil,
        nil,
        self.tutorialCombatBehavior,
        nil,
        false,
        self.tutorialCombatIntro,
        nil
      )
    elseif which == "SyncWorkflow" then
      tutorialPage.__contentHeight = LayoutSection(
        self.tutorialSyncFrame,
        self.tutorialSyncHeader,
        self.tutorialSyncCards,
        self.tutorialSyncScroll,
        self.tutorialSyncTriggers,
        nil,
        self.tutorialSyncPaths,
        self.tutorialSyncNotes,
        nil,
        self.tutorialSyncGaps,
        nil,
        false,
        self.tutorialSyncIntro,
        nil
      )
    elseif which == "SyncPriority" then
      tutorialPage.__contentHeight = LayoutSection(
        self.tutorialSyncPriorityFrame,
        self.tutorialSyncPriorityHeader,
        self.tutorialSyncPriorityCards,
        self.tutorialSyncPriorityScroll,
        self.tutorialSyncPriorityReady,
        nil,
        self.tutorialSyncPrioritySession,
        self.tutorialSyncPriorityRaid,
        nil,
        self.tutorialSyncPriorityHistory,
        nil,
        false,
        self.tutorialSyncPriorityIntro,
        nil
      )
    elseif which == "SyncWorkflow2" then
      tutorialPage.__contentHeight = self.tutorialSyncAudit and self.tutorialSyncAudit:Layout() or 400
    elseif which == "SyncWorkflow3" then
      tutorialPage.__contentHeight = self.tutorialSyncProgress and self.tutorialSyncProgress:Layout() or 400
    elseif which == "SyncWorkflow4" then
      tutorialPage.__contentHeight = self.tutorialSyncWorkflow4 and self.tutorialSyncWorkflow4:Layout() or 400
    elseif which == "SyncWorkflow5" then
      tutorialPage.__contentHeight = self.tutorialSyncWorkflow5 and self.tutorialSyncWorkflow5:Layout() or 400
    elseif which == "SyncWorkflow6" then
      tutorialPage.__contentHeight = self.tutorialSyncWorkflow6 and self.tutorialSyncWorkflow6:Layout() or 400
    elseif which == "SyncWorkflow7" then
      tutorialPage.__contentHeight = self.tutorialSyncWorkflow7 and self.tutorialSyncWorkflow7:Layout() or 400
    elseif which == "SyncWorkflow8" then
      tutorialPage.__contentHeight = self.tutorialSyncWorkflow8 and self.tutorialSyncWorkflow8:Layout() or 400
    elseif which == "SyncWorkflow10" then
      tutorialPage.__contentHeight = self.tutorialSyncWorkflow10 and self.tutorialSyncWorkflow10:Layout() or 400
    elseif which == "SyncWorkflow11" then
      tutorialPage.__contentHeight = self.tutorialSyncWorkflow11 and self.tutorialSyncWorkflow11:Layout() or 400
    elseif which == "SyncWorkflow12" then
      tutorialPage.__contentHeight = self.tutorialSyncWorkflow12 and self.tutorialSyncWorkflow12:Layout() or 400
    elseif which == "SyncWorkflow13" then
      tutorialPage.__contentHeight = self.tutorialSyncWorkflow13 and self.tutorialSyncWorkflow13:Layout() or 400
    elseif which == "SyncWorkflow14" then
      tutorialPage.__contentHeight = self.tutorialSyncWorkflow14 and self.tutorialSyncWorkflow14:Layout() or 400
    elseif which == "SyncWorkflow15" then
      tutorialPage.__contentHeight = self.tutorialSyncWorkflow15 and self.tutorialSyncWorkflow15:Layout() or 400
    elseif which == "PossibleFeatures" then
      tutorialPage.__contentHeight = self.tutorialPossibleFeatures and self.tutorialPossibleFeatures:Layout() or 400
    elseif which == "Preflight" then
      tutorialPage.__contentHeight = self.tutorialPreflight and self.tutorialPreflight:Layout() or 400
    elseif which == "ProfileRecovery" then
      tutorialPage.__contentHeight = self.tutorialProfileRecovery and self.tutorialProfileRecovery:Layout() or 400
    elseif which == "Roles" then
      tutorialPage.__contentHeight = LayoutSection(
        self.tutorialRolesFrame,
        self.tutorialRolesHeader,
        self.tutorialRolesCards,
        self.tutorialRolesScroll,
        self.tutorialRolesBasics,
        nil,
        self.tutorialRolesMinimum,
        self.tutorialRolesPractical,
        nil,
        self.tutorialRolesDefaults,
        nil,
        false,
        self.tutorialRolesIntro,
        nil
      )
    elseif which == "Rights" then
      tutorialPage.__contentHeight = LayoutSection(
        self.tutorialRightsFrame,
        self.tutorialRightsHeader,
        self.tutorialRightsCards,
        self.tutorialRightsScroll,
        self.tutorialRightsProfile,
        nil,
        self.tutorialRightsSession,
        self.tutorialRightsSync,
        nil,
        self.tutorialRightsLootList,
        nil,
        false,
        self.tutorialRightsIntro,
        nil
      )
    elseif which == "Panels" then
      tutorialPage.__contentHeight = LayoutSection(
        self.tutorialPanelsFrame,
        self.tutorialPanelsHeader,
        self.tutorialPanelsCards,
        self.tutorialPanelsScroll,
        self.tutorialPanelsNote,
        nil,
        self.tutorialPanelsAfter,
        self.tutorialPanelsPost,
        nil,
        nil,
        nil,
        true,
        self.tutorialPanelsIntro,
        self.tutorialPanelsTitle
      )
    else
      tutorialPage.__contentHeight = (self.tutorialBanner and self.tutorialBanner:GetHeight()) or 600
    end
  end

  tutorialPage:HookScript("OnShow", function()
    if editor and editor.RefreshTutorialChecklistNavigation then
      editor:RefreshTutorialChecklistNavigation()
    end
  end)
  editor:RefreshTutorialChecklistNavigation()
  editor:ShowTutorialSection("Beginner")
  tutorialPage.__contentHeight = 1200
end

INTERNAL_API.LIDCOUNCIL_BuildTutorialPage_Impl = BuildTutorialPage
