Files
Samuel LisonandClaude Opus 4.8 3349738779 fix: address adversarial-review findings across engine + plugin
Engine: quest overrides with objective*/reward* fields on nested code quests
now WARN at boot (they merge nothing — QuestData prefers nested tables);
EngineConfig + ConfigAdmin reject non-finite numbers (inf/nan).

Plugin: Config group panels auto-size (fixed-height math clipped the last row
of big groups — Theme group lost its Bold font row); Stats panels get the gap
math right; the Engine Config explainer page now REBUILDS the window when the
engine appears (the old hint was impossible — pages were assembled once at
plugin load); create() refuses ids that already have an override (mirror
guard); failed Create/Override reasons render under the create row; rejected
config edits report in the footer; explicit navigation cancels a pending
debounced search jump; the mount-time page restore no longer clobbers the
saved last-page setting during an engine-less session; undo/redo refresh
skips while typing in one of the plugin's own text boxes; search results past
the 50-cap no longer render empty category headers.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 12:39:56 +10:00

105 lines
2.8 KiB
Luau

--!nonstrict
--[[
SearchPage global content search results. A hidden (rail-less) page the plugin main routes
to while the rail search box has text. Matches ids + `name` attributes across every category
(ContentAdminUi.matchEntry) and renders full EDITABLE entry panels grouped by category you
fix the thing right where you found it.
]]
local Theme = require(script.Parent.Theme)
local MAX_RESULTS = 50
local SearchPage = {}
-- deps = { ContentAdmin, ContentAdminUi, getQuery(), actions }
function SearchPage.mount(container: Frame, deps: any): { refresh: () -> () }
local header = Theme.header(container, "Search")
local scroll = Theme.scroll(32, 0, 8)
scroll.Parent = container
local function refresh()
for _, child in scroll:GetChildren() do
if not child:IsA("UIBase") then
child:Destroy()
end
end
local query = tostring(deps.getQuery() or "")
if string.match(query, "^%s*$") then
Theme.label({
Size = UDim2.new(1, 0, 0, 40),
Text = "Type in the sidebar box to search content by id or name.",
TextColor3 = Theme.COLOR.DIM,
TextWrapped = true,
}).Parent =
scroll
return
end
local lowered = string.lower(query)
local order = 0
local shown = 0
local total = 0
for _, catKey in deps.ContentAdmin.ORDER do
local cat = deps.ContentAdmin.CATEGORIES[catKey]
local matches = {}
for _, entry in deps.ContentAdmin.listRoster(catKey) do
if deps.ContentAdminUi.matchEntry(deps.ContentAdmin, catKey, entry, lowered) then
table.insert(matches, entry)
end
end
total += #matches
if #matches == 0 or shown >= MAX_RESULTS then
continue -- past the cap, count the matches but don't render an empty header
end
order += 1
Theme.label({
Size = UDim2.new(1, 0, 0, 20),
Text = `{cat.title}{#matches} match{if #matches == 1 then "" else "es"}`,
TextColor3 = Theme.COLOR.ACCENT,
TextSize = 13,
Font = Theme.FONT_BOLD,
LayoutOrder = order,
}).Parent =
scroll
for _, entry in matches do
if shown >= MAX_RESULTS then
break
end
shown += 1
order += 1
local panel = deps.ContentAdminUi.buildRosterEntry(catKey, entry, deps.ContentAdmin, deps.actions)
panel.LayoutOrder = order
panel.Parent = scroll
end
end
if total == 0 then
Theme.label({
Size = UDim2.new(1, 0, 0, 40),
Text = `No content matches '{query}'.`,
TextColor3 = Theme.COLOR.DIM,
TextWrapped = true,
}).Parent =
scroll
elseif total > shown then
order += 1
Theme.label({
Size = UDim2.new(1, 0, 0, 22),
Text = `Showing {shown} of {total} — refine your search.`,
TextColor3 = Theme.COLOR.DIM,
TextSize = 12,
LayoutOrder = order,
}).Parent =
scroll
end
end
header.refreshBtn.MouseButton1Click:Connect(refresh)
refresh()
return { refresh = refresh }
end
return SearchPage