<roblox xmlns:xmime="http://www.w3.org/2005/05/xmlmime" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.roblox.com/roblox.xsd" version="4">
	<Meta name="ExplicitAutoJoints">true</Meta>
	<External>null</External>
	<External>nil</External>
	<Item class="Script" referent="RBX1">
		<Properties>
			<BinaryString name="AttributesSerialize"></BinaryString>
			<bool name="Disabled">false</bool>
			<Content name="LinkedSource"><null></null></Content>
			<string name="Name">NovaAI_Nexus</string>
			<string name="ScriptGuid">{F83A202A-8853-48DF-9828-A7851A940001}</string>
			<ProtectedString name="Source"><![CDATA[-- Brixwave - Plugin v6.6

local HttpService = game:GetService("HttpService")
local StudioService = game:GetService("StudioService")

local PLUGIN_VERSION = "6.6"
local BASE_URL = "https://www.brixwave.dev"
local HEARTBEAT_URL = BASE_URL .. "/api/heartbeat"
local SYNC_URL = BASE_URL .. "/api/poll"
local CONFIRM_URL = BASE_URL .. "/api/plugin-confirm"
local SEARCH_URL = BASE_URL .. "/api/toolbox/search"
local RESULTS_URL = BASE_URL .. "/api/plugin-results"

-- Two independent loops keep the website's "Studio linked" badge alive. Both
-- refresh `lastSeen` server-side, so one of them stalling is not enough to drop
-- the connection. The server treats the plugin as offline after 45s of silence.
local HEARTBEAT_INTERVAL = 5 -- Seconds between heartbeats
local SYNC_INTERVAL = 2      -- Seconds between command polls

-- A single failed request means nothing: Vercel cold starts, Wi-Fi hiccups and
-- Studio pausing the scheduler all cause one-off failures. Only report a
-- dropped connection after this many consecutive failures.
local FAILURE_TOLERANCE = 3

local userId = StudioService:GetUserId()
local lastTimestamp = nil
local isConnected = true

-- Connection health, shared by both loops
local consecutiveFailures = 0
local isReconnecting = false

-- Incremented on every connect so stale loops from a previous session exit
-- instead of running in parallel with the new ones.
local sessionId = 0

-- Upload public/brixwave-mark-512.png to Roblox (Creator Dashboard > Decals), then
-- paste the resulting id here. It drives BOTH the toolbar button and the mark
-- inside the widget, so there is only ever one value to change.
local MARK_ASSET_ID = "rbxassetid://91957613172132"

-- Roblox only accepts the full URI, but pasting the bare number from the
-- Creator Dashboard is the natural thing to do — so accept either.
if MARK_ASSET_ID ~= "" and not MARK_ASSET_ID:match("^rbx") then
	MARK_ASSET_ID = "rbxassetid://" .. MARK_ASSET_ID:gsub("%D", "")
end

-- 1. Create Toolbar & Button
local toolbar = plugin:CreateToolbar("Brixwave")
local button = toolbar:CreateButton(
	"Toggle AI",
	"Turn Brixwave sync on/off",
	MARK_ASSET_ID ~= "" and MARK_ASSET_ID or "rbxassetid://15079421896"
)

-- 2. Create DockWidget UI
local widgetInfo = DockWidgetPluginGuiInfo.new(
	Enum.InitialDockState.Right,
	false, -- Initially Enabled?
	false, -- Override Previous State?
	300, -- Default Width
	240, -- Default Height
	260, -- Minimum Width
	230  -- Minimum Height
)
local widget = plugin:CreateDockWidgetPluginGui("NovaAINexus_Widget", widgetInfo)
widget.Title = "Brixwave"

local isPluginConnected = false

-- ---------------------------------------------------------------- palette --
-- Mirrors the website's water theme so the plugin does not look like a
-- different product docked inside Studio.
local COL = {
	abyss   = Color3.fromRGB(4, 34, 46),
	deep    = Color3.fromRGB(10, 52, 68),
	panel   = Color3.fromRGB(15, 66, 84),
	line    = Color3.fromRGB(28, 92, 114),
	lagoon  = Color3.fromRGB(13, 125, 158),
	shallow = Color3.fromRGB(111, 211, 234),
	foam    = Color3.fromRGB(234, 250, 255),
	muted   = Color3.fromRGB(140, 178, 192),
	ok      = Color3.fromRGB(63, 207, 165),
	warn    = Color3.fromRGB(235, 170, 50),
	bad     = Color3.fromRGB(214, 99, 99),
}

local function corner(inst, radius)
	local c = Instance.new("UICorner")
	c.CornerRadius = UDim.new(0, radius or 8)
	c.Parent = inst
	return c
end

--[[ The Brixwave mark.

	Roblox only accepts an uploaded asset for imagery, so this reads one id from
	MARK_ASSET_ID above. Drawing the droplet from primitives was the previous
	approach and it looked crude — a rotated square standing in for a teardrop
	never had the right silhouette.

	If the id is not set the mark is simply omitted rather than replaced by a
	placeholder: no logo reads as deliberate, a bad logo reads as broken.
]]
local function makeMark(parent, size)
	if MARK_ASSET_ID == "" then return nil end

	local img = Instance.new("ImageLabel")
	img.Size = UDim2.new(0, size, 0, size)
	img.BackgroundTransparency = 1
	img.Image = MARK_ASSET_ID
	img.ScaleType = Enum.ScaleType.Fit
	img.Parent = parent
	return img
end

-- Positions the mark when there is one, and shifts the label to fill the gap
-- when there is not, so the header never has a hole in it.
local function placeMark(parent, size, pos)
	local mark = makeMark(parent, size)
	if mark then mark.Position = pos end
	return mark ~= nil
end

-- ------------------------------------------------------------- login view --

local loginFrame = Instance.new("Frame")
loginFrame.Size = UDim2.new(1, 0, 1, 0)
loginFrame.BackgroundColor3 = COL.abyss
loginFrame.BorderSizePixel = 0
loginFrame.Parent = widget

-- Depth: light at the surface, darkness below. Same idea as the website.
local loginGrad = Instance.new("UIGradient")
loginGrad.Color = ColorSequence.new(COL.deep, COL.abyss)
loginGrad.Rotation = 90
loginGrad.Parent = loginFrame

placeMark(loginFrame, 44, UDim2.new(0.5, -22, 0.5, -96))

local loginTitle = Instance.new("TextLabel")
loginTitle.Size = UDim2.new(1, -40, 0, 22)
loginTitle.Position = UDim2.new(0, 20, 0.5, -44)
loginTitle.BackgroundTransparency = 1
loginTitle.Text = "Brixwave"
loginTitle.TextColor3 = COL.foam
loginTitle.Font = Enum.Font.GothamBold
loginTitle.TextSize = 17
loginTitle.Parent = loginFrame

local loginHint = Instance.new("TextLabel")
loginHint.Size = UDim2.new(1, -40, 0, 32)
loginHint.Position = UDim2.new(0, 20, 0.5, -20)
loginHint.BackgroundTransparency = 1
loginHint.Text = "Pair this place with your Brixwave account to start building."
loginHint.TextColor3 = COL.muted
loginHint.Font = Enum.Font.Gotham
loginHint.TextSize = 12
loginHint.TextWrapped = true
loginHint.Parent = loginFrame

local connectBtn = Instance.new("TextButton")
connectBtn.Size = UDim2.new(1, -40, 0, 38)
connectBtn.Position = UDim2.new(0, 20, 0.5, 26)
connectBtn.BackgroundColor3 = COL.lagoon
connectBtn.Text = "Connect"
connectBtn.TextColor3 = COL.foam
connectBtn.Font = Enum.Font.GothamBold
connectBtn.TextSize = 14
connectBtn.AutoButtonColor = true
connectBtn.BorderSizePixel = 0
connectBtn.Parent = loginFrame
corner(connectBtn, 8)

-- -------------------------------------------------------------- main view --

local mainFrame = Instance.new("Frame")
mainFrame.Size = UDim2.new(1, 0, 1, 0)
mainFrame.BackgroundColor3 = COL.abyss
mainFrame.BorderSizePixel = 0
mainFrame.Visible = false
mainFrame.Parent = widget

local mainGrad = Instance.new("UIGradient")
mainGrad.Color = ColorSequence.new(COL.deep, COL.abyss)
mainGrad.Rotation = 90
mainGrad.Parent = mainFrame

local hasMark = placeMark(mainFrame, 22, UDim2.new(0, 16, 0, 14))

local titleLabel = Instance.new("TextLabel")
titleLabel.Size = UDim2.new(1, hasMark and -52 or -32, 0, 22)
titleLabel.Position = UDim2.new(0, hasMark and 46 or 16, 0, 14)
titleLabel.BackgroundTransparency = 1
titleLabel.Text = "Brixwave"
titleLabel.TextColor3 = COL.foam
titleLabel.TextXAlignment = Enum.TextXAlignment.Left
titleLabel.Font = Enum.Font.GothamBold
titleLabel.TextSize = 15
titleLabel.Parent = mainFrame

-- Status card, so state is one glanceable block instead of loose labels.
local statusCard = Instance.new("Frame")
statusCard.Size = UDim2.new(1, -32, 0, 74)
statusCard.Position = UDim2.new(0, 16, 0, 48)
statusCard.BackgroundColor3 = COL.panel
statusCard.BorderSizePixel = 0
statusCard.Parent = mainFrame
corner(statusCard, 10)

local cardStroke = Instance.new("UIStroke")
cardStroke.Color = COL.line
cardStroke.Thickness = 1
cardStroke.Parent = statusCard

local statusColor = Instance.new("Frame")
statusColor.Size = UDim2.new(0, 8, 0, 8)
statusColor.Position = UDim2.new(0, 14, 0, 15)
statusColor.BackgroundColor3 = COL.bad
statusColor.BorderSizePixel = 0
statusColor.Parent = statusCard
corner(statusColor, 999)

local statusLabel = Instance.new("TextLabel")
statusLabel.Size = UDim2.new(1, -40, 0, 16)
statusLabel.Position = UDim2.new(0, 30, 0, 11)
statusLabel.BackgroundTransparency = 1
statusLabel.Text = "Disconnected"
statusLabel.TextColor3 = COL.foam
statusLabel.TextXAlignment = Enum.TextXAlignment.Left
statusLabel.Font = Enum.Font.GothamMedium
statusLabel.TextSize = 13
statusLabel.Parent = statusCard

local projectLabel = Instance.new("TextLabel")
projectLabel.Size = UDim2.new(1, -28, 0, 15)
projectLabel.Position = UDim2.new(0, 14, 0, 33)
projectLabel.BackgroundTransparency = 1
projectLabel.Text = "Project: Loading..."
projectLabel.TextColor3 = COL.shallow
projectLabel.TextXAlignment = Enum.TextXAlignment.Left
projectLabel.TextTruncate = Enum.TextTruncate.AtEnd
projectLabel.Font = Enum.Font.GothamMedium
projectLabel.TextSize = 12
projectLabel.Parent = statusCard

local userLabel = Instance.new("TextLabel")
userLabel.Size = UDim2.new(1, -28, 0, 14)
userLabel.Position = UDim2.new(0, 14, 0, 51)
userLabel.BackgroundTransparency = 1
userLabel.Text = "User ID: " .. tostring(userId)
userLabel.TextColor3 = COL.muted
userLabel.TextXAlignment = Enum.TextXAlignment.Left
userLabel.Font = Enum.Font.Gotham
userLabel.TextSize = 11
userLabel.Parent = statusCard

local syncBtn = Instance.new("TextButton")
syncBtn.Size = UDim2.new(1, -32, 0, 36)
syncBtn.Position = UDim2.new(0, 16, 0, 136)
syncBtn.BackgroundColor3 = COL.lagoon
syncBtn.Text = "Sync from Brixwave"
syncBtn.TextColor3 = COL.foam
syncBtn.Font = Enum.Font.GothamBold
syncBtn.TextSize = 13
syncBtn.AutoButtonColor = true
syncBtn.BorderSizePixel = 0
syncBtn.Parent = mainFrame
corner(syncBtn, 8)

local disconnectBtn = Instance.new("TextButton")
disconnectBtn.Size = UDim2.new(1, -32, 0, 28)
disconnectBtn.Position = UDim2.new(0, 16, 0, 180)
disconnectBtn.BackgroundTransparency = 1
disconnectBtn.Text = "Disconnect"
disconnectBtn.TextColor3 = COL.muted
disconnectBtn.Font = Enum.Font.GothamMedium
disconnectBtn.TextSize = 12
disconnectBtn.AutoButtonColor = false
disconnectBtn.Parent = mainFrame
corner(disconnectBtn, 8)

-- Hover affordance: the destructive action only turns red when aimed at.
disconnectBtn.MouseEnter:Connect(function()
	disconnectBtn.TextColor3 = COL.bad
end)
disconnectBtn.MouseLeave:Connect(function()
	disconnectBtn.TextColor3 = COL.muted
end)

-- Button Logic
button.Click:Connect(function()
	widget.Enabled = not widget.Enabled
end)

local function updateUI()
	-- `isReconnecting` is checked first on purpose: during the tolerance window
	-- `isConnected` is deliberately still true (so the link is not torn down),
	-- but the user should see that we are retrying.
	if isReconnecting then
		statusColor.BackgroundColor3 = COL.warn
		statusLabel.Text = "Reconnecting"
	elseif isConnected then
		statusColor.BackgroundColor3 = COL.ok
		statusLabel.Text = "Connected"
	else
		statusColor.BackgroundColor3 = COL.bad
		statusLabel.Text = "Disconnected"
	end
end

-- Records the outcome of any server request and derives the connection state
-- from it, so a single blip never flips the plugin (or the website) offline.
local function reportRequestResult(ok)
	if ok then
		consecutiveFailures = 0
		isReconnecting = false
		isConnected = true
	else
		consecutiveFailures = consecutiveFailures + 1
		if consecutiveFailures >= FAILURE_TOLERANCE then
			isConnected = false
			isReconnecting = false
		else
			-- Keep reporting connected while we retry; the server window is
			-- wide enough to cover this.
			isReconnecting = true
		end
	end
	updateUI()
end

local function purgeScripts(model)
	if not model then return end
	local scriptsRemoved = 0
	for _, desc in pairs(model:GetDescendants()) do
		if desc:IsA("Script") or desc:IsA("LocalScript") or desc:IsA("ModuleScript") then
			desc:Destroy()
			scriptsRemoved = scriptsRemoved + 1
		end
	end
	if model:IsA("Script") or model:IsA("LocalScript") or model:IsA("ModuleScript") then
		model:Destroy()
		scriptsRemoved = scriptsRemoved + 1
	end
	if scriptsRemoved > 0 then
		warn("[Brixwave Security] Purged " .. tostring(scriptsRemoved) .. " scripts from the inserted asset to prevent backdoors.")
	end
end

local function applyProperties(inst, properties)
	if not properties or type(properties) ~= "table" then return end
	for propName, propData in pairs(properties) do
		pcall(function()
			if inst:IsA("Model") then
				if propName == "Scale" and type(propData) == "number" then
					inst:ScaleTo(propData)
					return
				elseif propName == "Position" and type(propData) == "table" and propData.type == "Vector3" then
					local pos = Vector3.new(unpack(propData.value))
					if inst.PrimaryPart then
						inst:PivotTo(CFrame.new(pos))
					else
						inst:MoveTo(pos)
					end
					return
				elseif propName == "CFrame" and type(propData) == "table" and propData.type == "CFrame" then
					inst:PivotTo(CFrame.new(unpack(propData.value)))
					return
				elseif propName == "Anchored" then
					for _, desc in pairs(inst:GetDescendants()) do
						if desc:IsA("BasePart") then
							desc.Anchored = propData
						end
					end
					return
				end
			end
			
			if type(propData) == "table" and propData.type then
				if propData.type == "Vector3" then
					inst[propName] = Vector3.new(unpack(propData.value))
				elseif propData.type == "Color3" then
					inst[propName] = Color3.fromRGB(unpack(propData.value))
				elseif propData.type == "UDim2" then
					inst[propName] = UDim2.new(unpack(propData.value))
				elseif propData.type == "UDim" then
					inst[propName] = UDim.new(unpack(propData.value))
				elseif propData.type == "CFrame" then
					inst[propName] = CFrame.new(unpack(propData.value))
				elseif propData.type == "Enum" then
					inst[propName] = Enum[propData.enumType][propData.enumValue]
				elseif propData.type == "Vector2" then
					inst[propName] = Vector2.new(unpack(propData.value))
				elseif propData.type == "NumberRange" then
					inst[propName] = NumberRange.new(unpack(propData.value))
				elseif propData.type == "BrickColor" then
					inst[propName] = BrickColor.new(propData.value)
				elseif propData.type == "NumberSequence" then
					-- value is [[time, value], ...] — used by UIGradient.Transparency
					-- and every ParticleEmitter curve.
					local keys = {}
					for _, pair in ipairs(propData.value) do
						table.insert(keys, NumberSequenceKeypoint.new(pair[1], pair[2]))
					end
					inst[propName] = NumberSequence.new(keys)
				elseif propData.type == "ColorSequence" then
					-- value is [[time, [r,g,b]], ...]
					local keys = {}
					for _, pair in ipairs(propData.value) do
						table.insert(keys, ColorSequenceKeypoint.new(pair[1], Color3.fromRGB(unpack(pair[2]))))
					end
					inst[propName] = ColorSequence.new(keys)
				end
			elseif type(propData) == "string" and typeof(inst[propName]) == "EnumItem" then
				-- Models emit bare strings for enums ("Horizontal", "GothamBold")
				-- however firmly the prompt asks for the verbose form. Rather than
				-- let that fail silently, resolve it against the property's own
				-- EnumType — which is exactly the set of legal values.
				local ok, item = pcall(function()
					return inst[propName].EnumType[propData]
				end)
				if ok and item then
					inst[propName] = item
				else
					warn("[Brixwave] Unknown enum value '" .. propData .. "' for " .. propName)
				end
			else
				inst[propName] = propData
			end
		end)
	end
end

--[[
	Queries Brixwave's Toolbox endpoint instead of InsertService:GetFreeModelsAsync.

	GetFreeModelsAsync is a legacy call that returns whatever Roblox ranks first,
	with no filtering at all: paid assets, avatar/UGC clutter, 600k-triangle meshes
	and — worst of all — models packed with scripts, which this plugin then strips
	on insert, leaving a gutted, non-functional model in the place.

	The web endpoint filters on the metadata Roblox actually exposes (hasScripts,
	instanceCounts, triangles, objectTypes, endorsement, votes) and ranks by
	relevance, so both sides of the product return the same trustworthy results.

	assetTypeId: 10 = Model, 3 = Audio, 13 = Decal.
	Returns an array of { id, name, creator } or nil.
]]
local function searchToolbox(query, assetTypeId)
	local url = SEARCH_URL
		.. "?q=" .. HttpService:UrlEncode(query)
		.. "&type=" .. tostring(assetTypeId or 10)
		.. "&page=1"

	local ok, response = pcall(function()
		return HttpService:RequestAsync({ Url = url, Method = "GET" })
	end)

	if not ok or not response or not response.Success then
		warn("[Brixwave] Toolbox search request failed for: " .. tostring(query))
		return nil
	end

	local decoded, data = pcall(function() return HttpService:JSONDecode(response.Body) end)
	if not decoded or not data or not data.items then
		warn("[Brixwave] Could not read Toolbox response for: " .. tostring(query))
		return nil
	end

	local results = {}
	for _, item in ipairs(data.items) do
		if item.id then
			table.insert(results, {
				id = item.id,
				name = item.name or "Model",
				creator = item.creator or "Roblox user",
			})
		end
	end
	return results
end

local function buildArchitecture(fullText, chatId)
	local jsonString = fullText
	
	-- Try to extract markdown JSON block
	local s, e = string.find(fullText, "```json.-```")
	if s and e then
		jsonString = string.sub(fullText, s + 7, e - 3)
	else
		-- Fallback to first [ and last ]
		local arrayStart = string.find(fullText, "%[")
		local arrayEnd
		for i = string.len(fullText), 1, -1 do
			if string.sub(fullText, i, i) == "]" then
				arrayEnd = i
				break
			end
		end
		if arrayStart and arrayEnd and arrayStart < arrayEnd then
			jsonString = string.sub(fullText, arrayStart, arrayEnd)
		end
	end

	local success, parsed = pcall(function()
		return HttpService:JSONDecode(jsonString)
	end)
	
	if not success then
		warn("[Brixwave] Failed to parse architecture JSON.")
		return
	end

	-- Containers already refreshed during THIS build. A rebuild that reuses an
	-- existing ScreenGui/Model must empty it the first time it is touched,
	-- otherwise the new UI is layered on top of the old one and the user sees
	-- two of everything. Once per batch only, so children created later in the
	-- same array are not wiped by a subsequent item.
	local refreshed = {}

	-- Ancestors invented by the path resolver below. They are placeholders: if a
	-- later item declares the same path with a real class, the placeholder must
	-- hand over its children rather than take them to the grave.
	local placeholders = {}

	local function isContainer(inst)
		return inst:IsA("ScreenGui") or inst:IsA("GuiObject") or inst:IsA("Model")
			or inst:IsA("Folder") or inst:IsA("BillboardGui") or inst:IsA("SurfaceGui")
	end

	-- Only UI is auto-cleared. Maps are legitimately built across several passes,
	-- so emptying a reused Model or Folder would delete work the AI intends to
	-- keep; a stacked ScreenGui, by contrast, is always a duplicate.
	local function isGuiContainer(inst)
		return inst:IsA("ScreenGui") or inst:IsA("GuiObject")
			or inst:IsA("BillboardGui") or inst:IsA("SurfaceGui")
	end

	-- Empties a reused UI container the first time this build touches it.
	local function refreshOnce(inst)
		if not inst or refreshed[inst] or not isGuiContainer(inst) then return end
		refreshed[inst] = true
		for _, child in ipairs(inst:GetChildren()) do
			child:Destroy()
		end
	end

	-- Replaces `old` with a fresh instance of `newType`, carrying the children
	-- across so a container swap does not silently delete the UI inside it.
	local function replacePreservingChildren(old, newType, name, parentNode)
		local createOk, fresh = pcall(function() return Instance.new(newType) end)
		if not createOk then return nil end
		fresh.Name = name
		if old and isContainer(old) and isContainer(fresh) and placeholders[old] then
			for _, child in ipairs(old:GetChildren()) do
				child.Parent = fresh
			end
		end
		if old then old:Destroy() end
		fresh.Parent = parentNode
		return fresh
	end

	--[[ Resolves a full dotted Explorer path to an existing instance.

		"StarterGui.HUD.CoinPanel" -> that Frame, or nil. Used by the `set`
		action, which must never create anything.
	]]
	local function resolvePath(path)
		if type(path) ~= "string" then return nil end
		local parts = string.split(path, ".")
		if #parts == 0 then return nil end

		local ok, current = pcall(function() return game:GetService(parts[1]) end)
		if not ok or not current then return nil end

		for i = 2, #parts do
			current = current:FindFirstChild(parts[i])
			if not current then return nil end
		end
		return current
	end

	for _, item in ipairs(parsed) do
		--[[ Precise property patch.

			Unlike `create`, this never adds, removes or clears anything — it
			finds one existing instance and writes properties onto it. That
			distinction matters: reusing `create` here would send the node
			through refreshOnce, which empties a GUI container, so nudging a
			panel two pixels would delete everything inside it.
		]]
		if item.action == "set" and item.path then
			local target = resolvePath(item.path)
			if target and item.properties then
				applyProperties(target, item.properties)
				print("[Brixwave] Updated " .. item.path)
			else
				warn("[Brixwave] Cannot set: no instance at " .. tostring(item.path))
			end
			continue
		end

		--[[ Game settings.

			Whitelisted on purpose. These are place-wide switches, so a typo in a
			service name should do nothing rather than reach for something the AI
			has no business touching. Each assignment is pcall'd individually so
			one bad property never aborts the rest of the build.
		]]
		if item.action == "configure" and item.service then
			local ALLOWED = {
				HttpService = true, Workspace = true, Lighting = true,
				Players = true, StarterPlayer = true, SoundService = true,
				ReplicatedStorage = true, StarterGui = true, TextChatService = true,
				PhysicsService = true, MaterialService = true,
			}

			if not ALLOWED[item.service] then
				warn("[Brixwave] Refusing to configure non-whitelisted service: " .. tostring(item.service))
				continue
			end

			local okService, service = pcall(function()
				return game:GetService(item.service)
			end)

			if okService and service and item.properties then
				for propName, propData in pairs(item.properties) do
					local okProp, err = pcall(function()
						applyProperties(service, { [propName] = propData })
					end)
					if okProp then
						print("[Brixwave] " .. item.service .. "." .. propName .. " set")
					else
						warn("[Brixwave] Could not set " .. item.service .. "." .. propName .. ": " .. tostring(err))
					end
				end
			end
			continue
		end

		if item.action == "search_toolbox" and item.query then
			print("[Brixwave] Searching Toolbox for: " .. item.query)
			local resultsTable = searchToolbox(item.query, item.assetTypeId)

			if resultsTable and #resultsTable > 0 then
				if chatId then
					local req = {
						Url = RESULTS_URL,
						Method = "POST",
						Headers = { ["Content-Type"] = "application/json" },
						Body = HttpService:JSONEncode({
							userId = tostring(userId),
							chatId = chatId,
							results = resultsTable
						})
					}
					task.spawn(function()
						pcall(function() HttpService:RequestAsync(req) end)
					end)
				end
			else
				warn("[Brixwave] No usable Toolbox results for: " .. item.query)
			end
			continue
		end

		if item.action == "insert_toolbox" and item.query then
			print("[Brixwave] Inserting Toolbox item for: " .. item.query)
			local searchResults = searchToolbox(item.query, item.assetTypeId)

			if searchResults and #searchResults > 0 then
				-- Already ranked server-side, so the first entry is the best match.
				local bestItem = searchResults[1]
				local assetId = bestItem.id

				if not assetId then
					warn("[Brixwave] Toolbox result had no asset id for: " .. item.query)
					continue
				end

				local successInsert, model = pcall(function()
					local inserted = game:GetService("InsertService"):LoadAsset(tonumber(assetId))
					return inserted:GetChildren()[1]
				end)
				if successInsert and model then
					purgeScripts(model)
					-- Name it after the request and drop any previous copy: this
					-- branch used to parent straight to workspace with no dedup,
					-- so every rebuild piled up another identical model.
					model.Name = item.name or model.Name
					local previous = workspace:FindFirstChild(model.Name)
					if previous and previous ~= model then previous:Destroy() end
					model.Parent = workspace
					if item.properties then
						applyProperties(model, item.properties)
					else
						if model:IsA("Model") and model.PrimaryPart then
							model:PivotTo(CFrame.new(0, 5, 0))
						elseif model:IsA("Model") then
							model:MoveTo(Vector3.new(0, 5, 0))
						end
					end
					print("[Brixwave] Inserted " .. (bestItem.name or "Model") .. " successfully!")
				else
					warn("[Brixwave] Failed to load model ID " .. tostring(assetId) .. " : " .. tostring(model))
				end
			else
				warn("[Brixwave] No toolbox results found for: " .. item.query)
			end
			continue
		end

		local instType = item.type
		local name = item.name
		local parentName = item.parent
		local sourceCode = item.source
		
		local parentNode = game
		local pcallSuccess, err = pcall(function()
			if parentName == "ServerScriptService" then parentNode = game:GetService("ServerScriptService")
			elseif parentName == "ReplicatedStorage" then parentNode = game:GetService("ReplicatedStorage")
			elseif parentName == "StarterPlayerScripts" then parentNode = game:GetService("StarterPlayer"):WaitForChild("StarterPlayerScripts")
			elseif parentName == "StarterCharacterScripts" then parentNode = game:GetService("StarterPlayer"):WaitForChild("StarterCharacterScripts")
			elseif parentName == "StarterGui" then parentNode = game:GetService("StarterGui")
			elseif parentName == "StarterPack" then parentNode = game:GetService("StarterPack")
			elseif parentName == "StarterPlayer" then parentNode = game:GetService("StarterPlayer")
			elseif parentName == "Lighting" then parentNode = game:GetService("Lighting")
			elseif parentName == "SoundService" then parentNode = game:GetService("SoundService")
			elseif parentName == "Workspace" then parentNode = game:GetService("Workspace")
			elseif parentName == "ServerStorage" then parentNode = game:GetService("ServerStorage")
			else
				local parts = string.split(parentName, ".")
				local current = game:GetService(parts[1]) or game:GetService("ServerScriptService")
				for i = 2, #parts do
					local found = current:FindFirstChild(parts[i])
					if not found then
						found = Instance.new("Folder")
						found.Name = parts[i]
						found.Parent = current
						placeholders[found] = true
					end
					current = found
				end
				parentNode = current
			end
		end)
		
		if not pcallSuccess then
			parentNode = game:GetService("ServerScriptService")
		end
		
		local existing = parentNode:FindFirstChild(name)
		
		if item.action == "delete" then
			if existing then
				existing:Destroy()
			end
			continue
		end

		if item.action == "edit" then
			if existing and (existing:IsA("Script") or existing:IsA("LocalScript") or existing:IsA("ModuleScript")) then
				local currentSource = existing.Source
				if item.replacements then
					for _, rep in ipairs(item.replacements) do
						local plainTarget = rep.target
						local replacementText = rep.replacement
						
						local startIdx, endIdx = string.find(currentSource, plainTarget, 1, true)
						if startIdx then
							currentSource = string.sub(currentSource, 1, startIdx - 1) .. replacementText .. string.sub(currentSource, endIdx + 1)
						else
							warn("[Brixwave] Target string not found in " .. name .. ": " .. plainTarget)
						end
					end
					existing.Source = currentSource
				end
			else
				warn("[Brixwave] Cannot edit: Script " .. name .. " not found.")
			end
			continue
		end

		local inst
		if item.action == "insert" and item.assetId then
			local assetIdNum = tonumber(item.assetId)
			local insertSuccess = false
			
			-- Method 1: Try InsertService:LoadAsset (works for free models)
			local successGet, insertedModel = pcall(function()
				return game:GetService("InsertService"):LoadAsset(assetIdNum)
			end)
			if successGet and insertedModel then
				local objects = insertedModel:GetChildren()
				if #objects > 0 then
					inst = objects[1]
					insertSuccess = true
				end
				-- Clean up the container model
				insertedModel:Destroy()
			end
			
			-- Method 2: Fallback to GetObjects (works for some restricted assets)
			if not insertSuccess then
				local successGet2, objects2 = pcall(function()
					return game:GetObjects("rbxassetid://" .. tostring(assetIdNum))
				end)
				if successGet2 and objects2 and #objects2 > 0 then
					inst = objects2[1]
					insertSuccess = true
				end
			end
			
			if insertSuccess and inst then
				if existing then existing:Destroy() end
				purgeScripts(inst)
				inst.Name = name or inst.Name
				inst.Parent = parentNode
				print("[Brixwave] Inserted asset " .. tostring(item.assetId) .. " successfully!")
			else
				warn("[Brixwave] Failed to load assetId " .. tostring(item.assetId) .. " (tried both methods)")
				continue
			end
		else
			if not instType or instType == "" then
				warn("[Brixwave] Skipping item with no type: " .. tostring(name))
				continue
			end
			if existing and existing.ClassName == instType then
				inst = existing
				-- Reusing a container: clear it out so this build replaces the
				-- previous contents instead of stacking a second copy on top.
				refreshOnce(inst)
			else
				inst = replacePreservingChildren(existing, instType, name, parentNode)
				if not inst then
					warn("[Brixwave] Failed to create instance type: " .. tostring(instType))
					continue
				end
				-- A real instance now owns this path; it is no longer a stub.
				placeholders[inst] = nil
				refreshed[inst] = true
			end
		end
		
		if instType ~= "Folder" and sourceCode then
			local successSource, _ = pcall(function() inst.Source = sourceCode end)
		end
		
		if item.properties then
			applyProperties(inst, item.properties)
		end
	end
	print("[Brixwave] Architecture Synced successfully!")
end

local function heartbeatLoop(mySession)
	while isPluginConnected and mySession == sessionId do
		local payload = { userId = tostring(userId), version = PLUGIN_VERSION }
		local req = {
			Url = HEARTBEAT_URL,
			Method = "POST",
			Headers = { ["Content-Type"] = "application/json" },
			Body = HttpService:JSONEncode(payload)
		}

		local s, r = pcall(function() return HttpService:RequestAsync(req) end)
		local ok = s and r and r.Success

		if ok then
			local successDecode, data = pcall(function() return HttpService:JSONDecode(r.Body) end)
			if successDecode and data.currentProject then
				projectLabel.Text = "Project: " .. data.currentProject
			end
		else
			if not s then
				warn("[Brixwave] Connection error (is HTTP enabled in Game Settings?)")
			elseif r then
				warn("[Brixwave] Server returned error:", r.StatusCode, r.StatusMessage)
			end
		end

		reportRequestResult(ok)
		task.wait(HEARTBEAT_INTERVAL)
	end
end

local function autoSyncLoop(mySession)
	-- Runs regardless of `isConnected`. This poll also refreshes the server-side
	-- liveness timestamp, so it is what recovers the connection after a blip —
	-- gating it on `isConnected` used to make a single failure permanent.
	while isPluginConnected and mySession == sessionId do
		local payload = { userId = tostring(userId), lastTimestamp = lastTimestamp }
		local req = {
			Url = SYNC_URL,
			Method = "POST",
			Headers = { ["Content-Type"] = "application/json" },
			Body = HttpService:JSONEncode(payload)
		}

		local s, r = pcall(function() return HttpService:RequestAsync(req) end)
		local ok = s and r and r.Success

		if ok then
			local successDecode, data = pcall(function() return HttpService:JSONDecode(r.Body) end)
			if successDecode and data.success and data.hasNew then
				if not data.code or data.code == "" then
					lastTimestamp = data.timestamp
				else
					print("[Brixwave] Auto-sync triggered!")
					lastTimestamp = data.timestamp
					local buildOk, buildErr = pcall(buildArchitecture, data.code, data.chatId)
					if buildOk then
						if data.chatId then
							local confirmReq = {
								Url = CONFIRM_URL,
								Method = "POST",
								Headers = { ["Content-Type"] = "application/json" },
								Body = HttpService:JSONEncode({ userId = tostring(userId), chatId = data.chatId })
							}
							pcall(function() HttpService:RequestAsync(confirmReq) end)
						end
					else
						warn("[Brixwave] Build error: " .. tostring(buildErr))
					end
				end
			end
		end

		-- A successful poll proves the link is healthy just as much as a
		-- heartbeat does, so let it clear the failure counter too.
		if ok then
			reportRequestResult(true)
		end

		task.wait(SYNC_INTERVAL)
	end
end

syncBtn.MouseButton1Click:Connect(function()
	print("[Brixwave] Syncing architecture...")
	syncBtn.Text = "SYNCING..."
	local payload = { userId = tostring(userId), lastTimestamp = lastTimestamp }
	local req = {
		Url = SYNC_URL,
		Method = "POST",
		Headers = { ["Content-Type"] = "application/json" },
		Body = HttpService:JSONEncode(payload)
	}
	
	task.spawn(function()
		local s, r = pcall(function() return HttpService:RequestAsync(req) end)
		if s and r.Success then
			local data = HttpService:JSONDecode(r.Body)
			if data.success and data.hasNew then
				lastTimestamp = data.timestamp
				buildArchitecture(data.code, data.chatId)
				if data.chatId then
					local confirmReq = {
						Url = "https://www.brixwave.dev/api/plugin-confirm",
						Method = "POST",
						Headers = { ["Content-Type"] = "application/json" },
						Body = HttpService:JSONEncode({ userId = tostring(userId), chatId = data.chatId })
					}
					pcall(function() HttpService:RequestAsync(confirmReq) end)
				end
			elseif data.success and not data.hasNew then
				print("[Brixwave] No new architecture found.")
			end
		else
			warn("[Brixwave] Failed to sync. Check connection.")
		end
		syncBtn.Text = "SYNC FROM AI"
	end)
end)

updateUI()

local wasConnected = plugin:GetSetting("isNovaAiConnected")

disconnectBtn.MouseButton1Click:Connect(function()
	isPluginConnected = false
	isConnected = false
	isReconnecting = false
	sessionId = sessionId + 1 -- stop the running loops
	plugin:SetSetting("isNovaAiConnected", false)
	mainFrame.Visible = false
	loginFrame.Visible = true
	connectBtn.Text = "CONNECT TO BRIXWAVE AI"
	updateUI()
	
	-- Send disconnect explicitly so website updates instantly
	task.spawn(function()
		local payload = { userId = tostring(userId), action = "disconnect" }
		local req = {
			Url = HEARTBEAT_URL,
			Method = "POST",
			Headers = { ["Content-Type"] = "application/json" },
			Body = HttpService:JSONEncode(payload)
		}
		pcall(function() HttpService:RequestAsync(req) end)
	end)
end)

--[[ Turns on HTTP requests for this place.

	This cannot be done through the normal `configure` action: with HTTP off the
	plugin cannot reach the server at all, so no instruction could ever arrive.
	It has to happen locally, before the first request.

	Whether a plugin is permitted to write HttpEnabled depends on Roblox's
	security level for the property, so the assignment is guarded and the
	failure path tells the user exactly where the switch lives.
]]
local function ensureHttpEnabled()
	local http = game:GetService("HttpService")
	if http.HttpEnabled then return true end

	local ok = pcall(function() http.HttpEnabled = true end)
	if ok and http.HttpEnabled then
		print("[Brixwave] Enabled HTTP requests for this place.")
		return true
	end

	warn("[Brixwave] HTTP requests are off and could not be enabled automatically. " ..
		"Turn them on in Home > Game Settings > Security > Allow HTTP Requests.")
	return false
end

local function startConnection()
	ensureHttpEnabled()

	-- Bump the session first so any loop left over from a previous connect
	-- exits before the new pair starts. Without this, clicking CONNECT twice
	-- stacked duplicate loops that fought over `isConnected`.
	sessionId = sessionId + 1
	local mySession = sessionId

	isPluginConnected = true
	isConnected = true
	isReconnecting = false
	consecutiveFailures = 0
	plugin:SetSetting("isNovaAiConnected", true)
	loginFrame.Visible = false
	mainFrame.Visible = true
	updateUI()

	task.spawn(function() heartbeatLoop(mySession) end)
	task.spawn(function() autoSyncLoop(mySession) end)
end

connectBtn.MouseButton1Click:Connect(function()
	connectBtn.Text = "Connecting…"
	startConnection()
end)

-- Auto-connect logic removed as requested, user must click CONNECT.
]]></ProtectedString>
		</Properties>
	</Item>
</roblox>