<?xml version="1.0" encoding="utf-8"?><roblox version="4"><Item class="Script" referent="RBX0"><Properties><string name="Name">BuildingBridge</string><ProtectedString name="Source">local Http = game:GetService("HttpService")
local Selection = game:GetService("Selection")
local History = game:GetService("ChangeHistoryService")
local RunService = game:GetService("RunService")
local ScriptEditor = game:GetService("ScriptEditorService")
local LogService = game:GetService("LogService")
local BASE = "https://sodapop.gg"
local session = Http:GenerateGUID(false)
local token, pending, busy, stopped = nil, nil, false, false
local applied = {}

local toolbar = plugin:CreateToolbar("Sodapop")
local toggle = toolbar:CreateButton("BuildingBridge", "Open Sodapop Studio connection", "", "Sodapop")
local widget = plugin:CreateDockWidgetPluginGui("BuildingBridgeV1", DockWidgetPluginGuiInfo.new(Enum.InitialDockState.Right, false, false, 310, 380, 280, 340))
widget.Title = "Sodapop"
local frame = Instance.new("ScrollingFrame")
frame.Size = UDim2.fromScale(1, 1)
frame.CanvasSize = UDim2.fromOffset(0, 0)
frame.AutomaticCanvasSize = Enum.AutomaticSize.Y
frame.ScrollingDirection = Enum.ScrollingDirection.Y
frame.ScrollBarThickness = 6
frame.BorderSizePixel = 0
frame.BackgroundColor3 = Color3.fromRGB(32, 34, 35)
frame.Parent = widget
local padding = Instance.new("UIPadding")
padding.PaddingTop = UDim.new(0, 16)
padding.PaddingLeft = UDim.new(0, 14)
padding.PaddingRight = UDim.new(0, 14)
padding.PaddingBottom = UDim.new(0, 18)
padding.Parent = frame
local layout = Instance.new("UIListLayout")
layout.Padding = UDim.new(0, 10)
layout.SortOrder = Enum.SortOrder.LayoutOrder
layout.Parent = frame
local order = 0
local function control(className, text, height)
  order += 1
  local item = Instance.new(className)
  item.Size = UDim2.new(1, 0, 0, height)
  item.LayoutOrder = order
  item.Text = text
  item.TextColor3 = Color3.fromRGB(237, 240, 237)
  item.BackgroundColor3 = Color3.fromRGB(47, 52, 50)
  item.BorderSizePixel = 0
  item.Font = Enum.Font.SourceSans
  item.TextSize = 16
  item.TextWrapped = true
  if className == "TextLabel" then item.AutomaticSize = Enum.AutomaticSize.Y end
  item.Parent = frame
  return item
end
local status = control("TextLabel", "Not connected", 48)
status.BackgroundTransparency = 1
local code = control("TextBox", "", 38)
code.PlaceholderText = "Pairing code from website"
code.ClearTextOnFocus = false
local connect = control("TextButton", "Connect", 36)
local incoming = control("TextLabel", "No pending building", 44)
incoming.BackgroundTransparency = 1
local apply = control("TextButton", "Import new building", 38)
apply.BackgroundColor3 = Color3.fromRGB(32, 130, 94)
local replace = control("TextButton", "Replace selected model", 38)
local note = control("TextLabel", "Replacement overwrites the model's contents. Undo is available. Your game is not published.", 64)
note.TextSize = 14
note.BackgroundTransparency = 1
incoming.LayoutOrder = 2
apply.LayoutOrder = 3
replace.LayoutOrder = 4
note.LayoutOrder = 5
code.LayoutOrder = 6
connect.LayoutOrder = 7
local share = control("TextButton", "Share selected objects", 38)
share.LayoutOrder = 8
local contextNote = control("TextLabel", "Shares names, hierarchy and part properties with Sodapop. Script contents are excluded.", 56)
local shareScripts = control("TextButton", "Share selected scripts (including source)", 48)
shareScripts.LayoutOrder = 10
local allowReads = false
local readAccess = control("TextButton", "[ ] Allow script reads this session", 48)
local readNotice = control("TextLabel", "When enabled, website requests can read matching script source. Reads do not approve edits. Reconnecting resets permission.", 64)
readNotice.BackgroundTransparency = 1
local captureErrors = false
local capturedErrors = {}
local captureStarted, captureEnded = nil, nil
local captureToggle = control("TextButton", "[ ] Capture new errors this session", 48)
local captureStatus = control("TextLabel", "No errors captured. Capture is best-effort; it does not prove a game is error-free.", 48)
local reviewErrors = control("TextButton", "Review or paste errors", 36)
local errorPreview = control("TextBox", "", 180)
errorPreview.PlaceholderText = "Paste the relevant Output error here. Remove private values before sharing."
errorPreview.MultiLine = true
errorPreview.ClearTextOnFocus = false
errorPreview.TextXAlignment = Enum.TextXAlignment.Left
errorPreview.TextYAlignment = Enum.TextYAlignment.Top
errorPreview.Font = Enum.Font.Code
errorPreview.TextSize = 13
local shareErrorsButton = control("TextButton", "Share reviewed errors with website", 44)
local errorNotice = control("TextLabel", "Remove private values before sharing. Nothing is sent automatically. Including errors in an AI request requires a separate choice on the website.", 64)
errorPreview.Visible = false
shareErrorsButton.Visible = false
errorNotice.Visible = false
local function resetCapturedErrors()
  captureErrors = false
  capturedErrors = {}
  captureStarted, captureEnded = nil, nil
  captureToggle.Text = "[ ] Capture new errors this session"
  captureStatus.Text = "No errors captured."
  errorPreview.Text = ""
  errorPreview.Visible = false
  shareErrorsButton.Visible = false
  errorNotice.Visible = false
end
captureToggle.MouseButton1Click:Connect(function()
  if busy then return end
  if captureErrors then
    captureErrors = false
    captureEnded = DateTime.now().UnixTimestampMillis / 1000
    captureToggle.Text = "[ ] Capture new errors this session"
  else
    resetCapturedErrors()
    captureErrors = true
    captureStarted = DateTime.now().UnixTimestampMillis / 1000
    captureToggle.Text = "[x] Capture new errors this session"
  end
end)
local function collectError(message, messageType)
  if type(message) ~= "string" or messageType ~= Enum.MessageType.MessageError then return end
  local cutoff = utf8.offset(message, 401)
  local text = cutoff and (message:sub(1, cutoff - 1) .. " [truncated]") or message
  if table.find(capturedErrors, text) then return end
  table.insert(capturedErrors, text)
  while #capturedErrors &gt; 12 or #table.concat(capturedErrors, "\n\n") &gt; 8000 do table.remove(capturedErrors, 1) end
  captureStatus.Text = #capturedErrors .. " recent error entries captured locally."
end
local errorConnection = LogService.MessageOut:Connect(function(message, messageType)
  -- Editor plugins can observe Output while their own RunService is not running.
  if stopped or not captureErrors then return end
  collectError(message, messageType)
end)
reviewErrors.MouseButton1Click:Connect(function()
  if busy then return end
  captureErrors = false
  captureEnded = captureEnded or DateTime.now().UnixTimestampMillis / 1000
  captureToggle.Text = "[ ] Capture new errors this session"
  if captureStarted then
    -- Recover available history only within the explicitly opted-in interval.
    local ok, history = pcall(function() return LogService:GetLogHistory() end)
    if ok then
      for _, entry in history do
        if type(entry.timestamp) == "number" and entry.timestamp &gt;= captureStarted and entry.timestamp &lt;= captureEnded then
          collectError(entry.message, entry.messageType)
        end
      end
    end
  end
  if not errorPreview.Visible then errorPreview.Text = table.concat(capturedErrors, "\n\n") end
  errorPreview.Visible = true
  shareErrorsButton.Visible = true
  errorNotice.Visible = true
  if #capturedErrors == 0 then
    captureStatus.Text = "No errors captured. Studio may omit Play output from plugin logs. Paste the relevant error below."
  end
end)
contextNote.LayoutOrder = 9
contextNote.BackgroundTransparency = 1
local codeInfo = control("TextLabel", "", 60)
local codeSource = control("TextBox", "", 260)
codeSource.MultiLine = true
codeSource.ClearTextOnFocus = false
codeSource.TextEditable = false
codeSource.Font = Enum.Font.Code
codeSource.TextSize = 13
codeSource.TextXAlignment = Enum.TextXAlignment.Left
codeSource.TextYAlignment = Enum.TextYAlignment.Top
local approveCode = control("TextButton", "Approve: install disabled scripts", 38)
local enableAfterApproval = plugin:GetSetting("SodapopEnableApprovedScripts") == true
local enableSetting = control("TextButton", "", 48)
local function refreshEnableSetting()
  enableSetting.Text = (enableAfterApproval and "[x] " or "[ ] ") .. "Enable scripts after approval"
  approveCode.Text = enableAfterApproval and "Approve: install and enable scripts" or "Approve: install disabled scripts"
end
refreshEnableSetting()
local rejectCode = control("TextButton", "Reject code proposal", 36)
local codeReview, codeAck, sharedContextId = nil, nil, nil
local sharedObjects = {}
local function showCodeReview(value)
  codeReview = value
  codeInfo.Visible = value ~= nil
  codeSource.Visible = value ~= nil
  approveCode.Visible = value ~= nil
  enableSetting.Visible = value ~= nil
  rejectCode.Visible = value ~= nil
  if value then
    local isUpdate = #value.scripts == 1 and value.scripts[1].action == "update"
    enableSetting.Visible = not isUpdate
    approveCode.Text = isUpdate and "Approve script edit" or (enableAfterApproval and "Approve: install and enable scripts" or "Approve: install disabled scripts")
    codeInfo.Text = value.summary .. "\n" .. value.warnings .. "\nScripts have normal game permissions. " .. (isUpdate and "This replaces the reviewed script source. Its enabled state stays unchanged. Undo is available." or (enableAfterApproval and "Approved scripts will run when you enter Play mode." or "New server/client scripts are installed disabled."))
    local sources = {}
    for _, scriptData in value.scripts do
      table.insert(sources, "-- " .. scriptData.className .. ": " .. scriptData.name .. "\n" .. (scriptData.action == "update" and ("-- BEFORE\n" .. scriptData.beforeSource .. "\n\n-- AFTER\n") or "") .. scriptData.source)
    end
    codeSource.Text = table.concat(sources, "\n\n")
    widget.Enabled = true
  end
end
showCodeReview(nil)
enableSetting.MouseButton1Click:Connect(function()
  if busy then return end
  enableAfterApproval = not enableAfterApproval
  plugin:SetSetting("SodapopEnableApprovedScripts", enableAfterApproval)
  refreshEnableSetting()
  showCodeReview(codeReview)
end)
local function refreshPanel(healthy)
  code.Visible = not healthy
  connect.Text = healthy and "Reconnect" or "Connect"
  incoming.Visible = pending ~= nil
  apply.Visible = pending ~= nil
  replace.Visible = pending ~= nil
  note.Visible = pending ~= nil
  apply.Active = not busy
  replace.Active = not busy
  apply.AutoButtonColor = not busy
  replace.AutoButtonColor = not busy
  apply.Text = busy and "Importing..." or "Import / update linked building"
end
refreshPanel(false)

local function request(path, data)
  local headers = {}
  if token then headers.Authorization = "Bearer " .. token; headers["X-Studio-Session"] = session end
  if data then headers["Content-Type"] = "application/json" end
  local response = Http:RequestAsync({ Url = BASE .. path, Method = data and "POST" or "GET", Headers = headers, Body = data and Http:JSONEncode(data) or nil })
  local decoded = Http:JSONDecode(response.Body)
  if not response.Success then error(decoded.error or "Bridge request failed") end
  return decoded
end
toggle.Click:Connect(function() widget.Enabled = not widget.Enabled end)
shareErrorsButton.MouseButton1Click:Connect(function()
  if busy then return end
  if not token then status.Text = "Connect before sharing errors."; return end
  if RunService:IsRunning() then status.Text = "Stop Play mode before sharing reviewed errors."; return end
  if #errorPreview.Text == 0 or #errorPreview.Text &gt; 8000 then captureStatus.Text = "Share between 1 and 8,000 bytes of reviewed error text."; return end
  busy = true
  local ok, result = pcall(function() return request("/bridge/errors", {entries = {errorPreview.Text}}) end)
  captureStatus.Text = ok and "Reviewed errors shared. No AI request was made." or tostring(result)
  busy = false
end)
local objectIds = setmetatable({}, { __mode = "k" })
local function scriptSnapshot(selected)
  assert(#selected &gt; 0 and #selected &lt;= 8, "Use 1 to 8 matching scripts. Narrow the script name or feature.")
  local nodes, references, bytes = {}, {}, 0
  for _, item in selected do
    assert(item:IsA("LuaSourceContainer") and item:IsDescendantOf(game), "Only scripts in this game can be read.")
    local source = ScriptEditor:GetEditorSource(item)
    bytes += #source
    assert(bytes &lt;= 50000, "Matching source exceeds 50 KB. Narrow the request.")
    objectIds[item] = objectIds[item] or Http:GenerateGUID(false)
    table.insert(nodes, {id = objectIds[item], parentId = false, name = item.Name, className = item.ClassName, source = source})
    references[objectIds[item]] = {instance = item, parent = item.Parent, name = item.Name}
  end
  return nodes, references
end
readAccess.MouseButton1Click:Connect(function()
  if busy then return end
  if not token then status.Text = "Connect before allowing script reads."; return end
  busy = true
  local ok, result = pcall(function() return request("/bridge/read-access", {allowed = not allowReads}) end)
  if ok then
    allowReads = result.allowed
    readAccess.Text = (allowReads and "[x] " or "[ ] ") .. "Allow script reads this session"
    if not allowReads then sharedObjects = {}; sharedContextId = nil; showCodeReview(nil) end
  else status.Text = tostring(result) end
  busy = false
end)
local ignoredTerms = {the=true, this=true, that=true, make=true, change=true, update=true, script=true, scripts=true, code=true, please=true, with=true, from=true, into=true, faster=true, slower=true, more=true, less=true}
local function findScripts(query)
  local terms = {}
  for word in string.lower(query):gmatch("[%w_]+") do
    if #word &gt;= 3 and not ignoredTerms[word] then terms[word] = true end
  end
  assert(next(terms), "Include a script name or feature such as sprint.")
  local pendingNodes, matches = {}, {}
  for _, name in {"Workspace", "ServerScriptService", "ReplicatedStorage", "ServerStorage", "StarterPlayer", "StarterGui", "StarterPack", "ReplicatedFirst"} do
    table.insert(pendingNodes, game:GetService(name))
  end
  local visited = 0
  while #pendingNodes &gt; 0 do
    visited += 1
    assert(visited &lt;= 10000, "Project search exceeds 10,000 objects. Use manual selection sharing instead.")
    local item = table.remove(pendingNodes)
    if item:IsA("LuaSourceContainer") then
      local name = string.lower(item.Name)
      for term in terms do
        if name:find(term, 1, true) then table.insert(matches, item); break end
      end
      assert(#matches &lt;= 8, "More than eight scripts match. Use a more specific script name.")
    end
    for _, child in item:GetChildren() do
      assert(#pendingNodes + visited &lt; 10000, "Project search exceeds 10,000 objects. Use manual selection sharing instead.")
      table.insert(pendingNodes, child)
    end
    if visited % 200 == 0 then task.wait() end
  end
  assert(#matches &gt; 0, "No script names matched. Include the feature or script name, or share a selection manually.")
  table.sort(matches, function(a, b) return a:GetFullName() &lt; b:GetFullName() end)
  return matches
end
local function handleRead(taskData)
  if not allowReads then return end
  busy = true
  local references
  local ok, payload = pcall(function()
    assert(not RunService:IsRunning(), "Stop Play mode before reading scripts.")
    local selected = findScripts(taskData.query)
    local nodes
    nodes, references = scriptSnapshot(selected)
    return {id = taskData.id, nodes = nodes, truncated = false}
  end)
  local sent, response = pcall(function() return request("/bridge/read-result", ok and payload or {id = taskData.id, error = tostring(payload)}) end)
  if sent and ok then
    sharedContextId = response.contextId
    sharedObjects = references
    showCodeReview(nil)
    contextNote.Text = "Read " .. response.count .. " matching scripts for the website. No code was changed."
  elseif not sent then status.Text = tostring(response)
  else contextNote.Text = tostring(payload) end
  busy = false
end
shareScripts.MouseButton1Click:Connect(function()
  if busy then return end
  if not token then status.Text = "Connect before sharing."; return end
  if RunService:IsRunning() then status.Text = "Stop Play mode before sharing."; return end
  busy = true
  local ok, result = pcall(function()
    local selected = Selection:Get()
    local nodes, references = scriptSnapshot(selected)
    local response = request("/bridge/scripts", {nodes = nodes, truncated = false})
    sharedContextId = response.contextId
    sharedObjects = references
    showCodeReview(nil)
    return response
  end)
  contextNote.Text = ok and ("Shared " .. result.count .. " scripts including source. Choose Shared selection on the website to include it in an AI request.") or tostring(result)
  busy = false
end)
share.MouseButton1Click:Connect(function()
  if busy then return end
  if not token then status.Text = "Connect before sharing."; return end
  if RunService:IsRunning() then status.Text = "Stop Play mode before sharing."; return end
  busy = true
  share.Text = "Sharing..."
  local ok, result = pcall(function()
    local selected = Selection:Get()
    assert(#selected &gt; 0, "Select objects in Explorer first.")
    local nodes, visited, truncated, references = {}, {}, false, {}
    local function vector(v) return { v.X, v.Y, v.Z } end
    local function visit(item, parentId, depth)
      if visited[item] then return end
      if #nodes &gt;= 2500 or depth &gt; 64 then truncated = true; return end
      visited[item] = true
      objectIds[item] = objectIds[item] or Http:GenerateGUID(false)
      local node = { id = objectIds[item], name = item.Name, className = item.ClassName, parentId = parentId }
      references[node.id] = { instance = item, parent = item.Parent, name = item.Name }
      if item:IsA("BasePart") then
        node.position = vector(item.Position)
        node.size = vector(item.Size)
        node.color = { item.Color.R, item.Color.G, item.Color.B }
        node.anchored = item.Anchored
      end
      table.insert(nodes, node)
      for _, child in item:GetChildren() do
        if #nodes &gt;= 2500 then truncated = true; break end
        visit(child, node.id, depth + 1)
      end
    end
    local selectedSet = {}
    for _, item in selected do selectedSet[item] = true end
    for _, item in selected do
      assert(item:IsDescendantOf(game), "Only objects in this game can be shared.")
      local ancestor, covered = item.Parent, false
      while ancestor and ancestor ~= game do
        if selectedSet[ancestor] then covered = true; break end
        ancestor = ancestor.Parent
      end
      if not covered then visit(item, nil, 0) end
    end
    -- JSONEncode omits nil fields; use false for root markers on the wire.
    for _, node in nodes do if not node.parentId then node.parentId = false end end
    -- Attest the imported version only when every selected object belongs to it.
    local buildingJobId, mixedSelection = nil, false
    for _, item in selected do
      local ancestor, version = item, nil
      while ancestor and ancestor ~= game do
        version = ancestor:GetAttribute("BuildingBridgeVersion")
        if version then break end
        ancestor = ancestor.Parent
      end
      if not version or (buildingJobId and buildingJobId ~= version) then mixedSelection = true end
      buildingJobId = buildingJobId or version
    end
    local response = request("/bridge/context", { nodes = nodes, truncated = truncated, buildingJobId = not mixedSelection and buildingJobId or nil })
    sharedContextId = response.contextId
    sharedObjects = references
    showCodeReview(nil)
    return response
  end)
  contextNote.Text = ok and ("Shared " .. result.count .. " objects" .. (result.truncated and " (partial selection)" or "") .. ". Script contents excluded.") or tostring(result)
  share.Text = "Share selected objects"
  busy = false
end)
connect.MouseButton1Click:Connect(function()
  if busy then return end
  if not code.Visible then
    code.Visible = true
    connect.Text = "Connect"
    status.Text = "Enter a fresh pairing code from sodapop.gg"
    return
  end
  busy = true
  local ok, result = pcall(function()
    return request("/bridge/pair", { code = code.Text:gsub("%s", ""), session = session, place = game.Name })
  end)
  if ok then token = result.token; code.Text = ""; status.Text = "Connected to Sodapop"; sharedContextId = nil; sharedObjects = {}; codeAck = nil; showCodeReview(nil); allowReads = false; readAccess.Text = "[ ] Allow script reads this session"; resetCapturedErrors()
  else status.Text = tostring(result) end
  busy = false
  refreshPanel(ok)
end)

local function isImporterRoot(item, model, names)
  return item.ClassName == "Part" and item.Parent == model and model.PrimaryPart == item
    and item.Name == "RootPart" and item.Transparency == 1 and not names[item.Name]
    and not item:FindFirstChildWhichIsA("DataModelMesh")
end

local function applyModelMaterials(model, data)
  local count, matched = 0, 0
  local unmatched = {}
  for _, item in model:GetDescendants() do
    if item:IsA("LuaSourceContainer") or item:IsA("PackageLink") then item:Destroy()
    elseif item:IsA("BasePart") then
      item.Anchored = true
      if isImporterRoot(item, model, data.names) then
        -- Roblox creates this invisible pivot; it is not a Blender surface.
        item.CanCollide = false
        item.CanTouch = false
        item.CanQuery = false
      else
        count += 1
        local source = item
        while source and source ~= model and not data.names[source.Name] do source = source.Parent end
        local material = source and data.materials[data.names[source.Name]]
        if material then
          matched += 1
          local color = Color3.fromRGB(table.unpack(material.rgb))
          item.Color = color
          item.Material = Enum.Material[material.kind]
          item.Transparency = material.transparency
          local surface = item:FindFirstChildWhichIsA("SurfaceAppearance")
          if surface then surface.Color = color end
        elseif #unmatched &lt; 3 then
          table.insert(unmatched, string.sub(item.Name, 1, 42) .. " [" .. string.sub(item.Parent.Name, 1, 42) .. "]")
        end
      end
    end
  end
  assert(count &gt; 0 and matched == count, "Material mapping " .. matched .. "/" .. count .. ". Unmatched part [parent]: " .. table.concat(unmatched, "; ") .. ". Existing model unchanged.")
  return count
end

local function import(replaceSelected)
  if busy or not pending then return end
  if RunService:IsRunning() then status.Text = "Stop Play mode before importing."; return end
  busy = true
  refreshPanel(true)
  local id = pending.id
  local model, recording
  local ok, result = pcall(function()
    local data = request("/bridge/manifest/" .. id)
    local target = nil
    for _, child in workspace:GetChildren() do
      if child:IsA("Model") and child:GetAttribute("BuildingBridgeId") == data.buildingId then
        assert(not target, "Multiple linked models found. Resolve duplicates before updating.")
        target = child
      end
    end
    if replaceSelected then
      local selected = Selection:Get()
      assert(#selected == 1 and selected[1]:IsA("Model") and selected[1].Parent == workspace, "Select one top-level building Model in Workspace.")
      assert(not target or target == selected[1], "A different model is already linked to this building.")
      target = selected[1]
    end
    -- A repeated delivery is acknowledged without importing another copy.
    if target and target:GetAttribute("BuildingBridgeDelivery") == id then return end
    request("/bridge/ack", { id = id, status = "applying" })
    status.Text = "Loading Roblox asset..."
    local objects = game:GetObjects("rbxassetid://" .. data.assetId)
    assert(#objects == 1 and objects[1]:IsA("Model"), "Roblox asset did not contain one building Model.")
    model = objects[1]
    local count = applyModelMaterials(model, data)
    model.Name = data.title
    model:SetAttribute("BuildingBridgeId", data.buildingId)
    model:SetAttribute("BuildingBridgeVersion", data.jobId)
    model:SetAttribute("BuildingBridgeDelivery", id)
    if target then model:PivotTo(target:GetPivot()) end
    recording = History:TryBeginRecording("BuildingBridgeImport", "Import building version")
    assert(recording, "Finish your current Studio action, then retry.")
    model.Parent = workspace
    if target then target.Parent = nil end
    Selection:Set({ model })
    History:FinishRecording(recording, Enum.FinishRecordingOperation.Commit)
    recording = nil
    model = nil
    status.Text = "Import successful\n" .. count .. " colored parts"
  end)
  if recording then History:FinishRecording(recording, Enum.FinishRecordingOperation.Cancel) end
  if model then model:Destroy() end
  if ok then applied[id] = true else status.Text = tostring(result) end
  local acknowledged = pcall(function() request("/bridge/ack", { id = id, status = ok and "delivered" or "failed", error = ok and nil or tostring(result) }) end)
  if acknowledged then pending = nil; incoming.Text = "No pending building" end
  busy = false
  refreshPanel(token ~= nil)
end
apply.MouseButton1Click:Connect(function() import(false) end)
replace.MouseButton1Click:Connect(function() import(true) end)
local function sendCodeAck()
  if not codeAck then return end
  request("/bridge/code/ack", codeAck)
  codeAck = nil
end
rejectCode.MouseButton1Click:Connect(function()
  if busy or not codeReview then return end
  codeAck = { id = codeReview.id, status = "rejected" }
  showCodeReview(nil)
  pcall(sendCodeAck)
end)
approveCode.MouseButton1Click:Connect(function()
  if busy or not codeReview then return end
  if RunService:IsRunning() then status.Text = "Stop Play mode before installing scripts."; return end
  busy = true
  local proposal, created, recording = codeReview, {}, nil
  local enableApproved = enableAfterApproval
  local ok, result = pcall(function()
    assert(proposal.contextId == sharedContextId, "Selection changed. Generate a new proposal.")
    assert(#proposal.scripts &gt; 0 and #proposal.scripts &lt;= 8, "Invalid script count.")
    request("/bridge/code/ack", { id = proposal.id, status = "applying" })
    if proposal.scripts[1].action == "update" then
      assert(#proposal.scripts == 1, "Only one existing script can be edited per proposal.")
      local edit = proposal.scripts[1]
      local ref = sharedObjects[edit.targetId]
      assert(ref and ref.instance:IsDescendantOf(game) and ref.instance.Parent == ref.parent and ref.instance.Name == ref.name and ref.instance.ClassName == edit.className, "Script moved or changed. Share it again.")
      assert(ScriptEditor:GetEditorSource(ref.instance) == edit.beforeSource, "Script changed since sharing. No edit applied; share again.")
      recording = History:TryBeginRecording("SodapopCodeEdit", "Edit Sodapop script")
      assert(recording, "Finish the current Studio action first.")
      ScriptEditor:UpdateSourceAsync(ref.instance, function(current)
        assert(not RunService:IsRunning(), "Stop Play mode before applying edits.")
        assert(current == edit.beforeSource, "Script changed during review. No edit applied; share again.")
        return edit.source
      end)
      History:FinishRecording(recording, Enum.FinishRecordingOperation.Commit)
      recording = nil
      return
    end
    local targets = {}
    for _, scriptData in proposal.scripts do
      local parent
      if scriptData.className == "Script" then parent = game:GetService("ServerScriptService")
      elseif scriptData.className == "LocalScript" then parent = game:GetService("StarterPlayer"):FindFirstChildOfClass("StarterPlayerScripts")
      elseif scriptData.className == "ModuleScript" then parent = game:GetService("ReplicatedStorage") end
      assert(parent, "Unsupported script destination.")
      assert(not parent:FindFirstChild(scriptData.name), "A script named " .. scriptData.name .. " already exists. Nothing was overwritten.")
      local scriptObject = Instance.new(scriptData.className)
      table.insert(created, scriptObject)
      scriptObject.Name = scriptData.name
      if scriptObject:IsA("BaseScript") then scriptObject.Enabled = false end
      local bindings = Instance.new("Folder")
      bindings.Name = "SodapopBindings"
      bindings.Parent = scriptObject
      for _, binding in scriptData.bindings do
        local ref = sharedObjects[binding.objectId]
        assert(ref and ref.instance:IsDescendantOf(game) and ref.instance.Parent == ref.parent and ref.instance.Name == ref.name, "A referenced object moved or changed. Share again and generate a fresh proposal.")
        local value = Instance.new("ObjectValue")
        value.Name = binding.name
        value.Value = ref.instance
        value.Parent = bindings
      end
      ScriptEditor:UpdateSourceAsync(scriptObject, function() return scriptData.source end)
      table.insert(targets, parent)
    end
    assert(not RunService:IsRunning(), "Stop Play mode before installing scripts.")
    for index, item in created do assert(not targets[index]:FindFirstChild(item.Name), "Script name collision. Nothing was overwritten.") end
    recording = History:TryBeginRecording("SodapopCode", "Install Sodapop script proposal")
    assert(recording, "Finish the current Studio action first.")
    for index, item in created do
      item.Parent = targets[index]
      if item:IsA("BaseScript") then item.Enabled = enableApproved end
    end
    History:FinishRecording(recording, Enum.FinishRecordingOperation.Commit)
    recording = nil
  end)
  if not ok then
    for _, item in created do item:Destroy() end
    if recording then History:FinishRecording(recording, Enum.FinishRecordingOperation.Cancel) end
  end
  codeAck = { id = proposal.id, status = ok and "applied" or "failed", error = ok and nil or tostring(result) }
  showCodeReview(nil)
  pcall(sendCodeAck)
  status.Text = ok and (proposal.scripts[1].action == "update" and "Script updated. Enabled state preserved; Undo is available. Share again before the next edit." or (enableApproved and "Scripts installed and enabled. Enter Play mode to test. Undo is available." or "Scripts installed disabled. Inspect them, then enable server/client scripts for a Play test. Undo is available.")) or tostring(result)
  if ok and #created &gt; 0 then Selection:Set(created) end
  busy = false
end)
plugin.Unloading:Connect(function() stopped = true; token = nil; errorConnection:Disconnect(); resetCapturedErrors() end)
task.spawn(function()
  local wasOffline = false
  while not stopped do
    if token and not busy then
      local ok, result = pcall(function() return request("/bridge/poll") end)
      if ok then
        if result.read then handleRead(result.read) end
        if codeAck then pcall(sendCodeAck) end
        if result.code and not codeAck and (not codeReview or codeReview.id ~= result.code.id) then
          local readOk, proposal = pcall(function() return request("/bridge/code") end)
          if readOk then showCodeReview(proposal) end
        elseif not result.code and not busy then showCodeReview(nil) end
        pending = result.transfer
        if pending and applied[pending.id] then
          pcall(function() request("/bridge/ack", { id = pending.id, status = "delivered" }) end)
          pending = nil
        end
        incoming.Text = pending and pending.title or "No pending building"
        if wasOffline then status.Text = "Reconnected to Sodapop" end
        wasOffline = false
        -- Leave a manually opened pairing field visible until the user submits it.
        if connect.Text ~= "Connect" then refreshPanel(true)
        else
          incoming.Visible = pending ~= nil
          apply.Visible = pending ~= nil
          replace.Visible = pending ~= nil
          note.Visible = pending ~= nil
        end
        if pending then widget.Enabled = true end
      else
        wasOffline = true
        status.Text = "Connection lost. Retrying...\nIf the worker restarted, enter a new pairing code."
        refreshPanel(false)
      end
    end
    task.wait(3)
  end
end)
</ProtectedString></Properties></Item></roblox>