From 9cac2654442fc8a2aefb76a41496e9cd01794314 Mon Sep 17 00:00:00 2001 From: Andraxion Date: Sat, 27 Jun 2026 02:41:12 -0400 Subject: [PATCH] Refine launcher admin workspace --- src/WorldshaperLauncher.tsx | 1341 +++++++++++++++++++++++------------ src/index.css | 380 +++++++++- 2 files changed, 1256 insertions(+), 465 deletions(-) diff --git a/src/WorldshaperLauncher.tsx b/src/WorldshaperLauncher.tsx index f7efd48..ea8e857 100644 --- a/src/WorldshaperLauncher.tsx +++ b/src/WorldshaperLauncher.tsx @@ -22,6 +22,7 @@ type LaunchState = "ready" | "opening" | "opened" | "blocked" | "error"; type BoardTab = "news" | "requests"; type LauncherWindowMode = "public" | "admin"; type LauncherRequestStatus = "pending" | "active" | "implemented"; +type AdminDetailTab = "routing" | "analysis"; type LauncherRequestAnalysisRouting = { summary?: string; @@ -146,11 +147,232 @@ function readLauncherWindowMode(): LauncherWindowMode { return searchParams.get("admin") === "requests" ? "admin" : "public"; } -function normalizeCommaSeparatedList(value: string): string[] { - return value - .split(",") - .map((entry) => entry.trim()) +function normalizeStringList(values: string[]): string[] { + return Array.from(new Set( + values + .map((entry) => String(entry || "").trim()) + .filter(Boolean), + )).sort((left, right) => left.localeCompare(right)); +} + +function appendUniqueString(values: string[], value: string): string[] { + const normalizedValue = String(value || "").trim(); + if (!normalizedValue) { + return normalizeStringList(values); + } + return normalizeStringList([...values, normalizedValue]); +} + +function removeStringValue(values: string[], value: string): string[] { + const normalizedValue = String(value || "").trim().toLowerCase(); + return normalizeStringList(values.filter((entry) => entry.trim().toLowerCase() !== normalizedValue)); +} + +function toggleStringSelection(current: string[], value: string): string[] { + return current.includes(value) + ? current.filter((entry) => entry !== value) + : [...current, value].sort((left, right) => left.localeCompare(right)); +} + +function normalizeSearchText(value: string): string { + return String(value || "").replace(/\s+/g, " ").trim().toLowerCase(); +} + +function extractRoutingTerms(requestEntry: LauncherRequest): string[] { + const tagTerms = requestEntry.tags + .map((entry) => String(entry || "").trim()) .filter(Boolean); + if (tagTerms.length > 0) { + return tagTerms.slice(0, 6); + } + const seen = new Set(); + const stopWords = new Set([ + "the", "and", "for", "with", "that", "this", "from", "into", "have", "need", + "want", "make", "more", "just", "like", "does", "dont", "cannot", "should", + "would", "could", "about", "because", "there", "their", "they", "them", "then", + "than", "over", "under", "your", "while", "where", + ]); + const matches = `${requestEntry.title} ${requestEntry.sourceText}`.match(/[A-Za-z][A-Za-z0-9/-]{2,}/g) || []; + return matches + .map((entry) => entry.trim()) + .filter((entry) => { + const normalized = entry.toLowerCase(); + if (stopWords.has(normalized) || seen.has(normalized)) { + return false; + } + seen.add(normalized); + return true; + }) + .slice(0, 6); +} + +function buildRoutingSummaryFallback(requestEntry: LauncherRequest, firstItem: LauncherRequestAnalysisItem | null): string { + const normalizedSummary = String(requestEntry.summary || "").trim(); + if (normalizedSummary && normalizedSummary !== "Awaiting parsing and categorization.") { + return normalizedSummary; + } + if (firstItem?.parsedInterpretation) { + return String(firstItem.parsedInterpretation).trim(); + } + const normalizedSource = String(requestEntry.sourceText || "").replace(/\s+/g, " ").trim(); + if (normalizedSource) { + return normalizedSource.length > 220 + ? `${normalizedSource.slice(0, 217).trim()}...` + : normalizedSource; + } + return "No routing summary has been stored yet."; +} + +function buildFallbackRouting(requestEntry: LauncherRequest): LauncherRequestAnalysisRouting { + const firstItem = Array.isArray(requestEntry.analysis?.items) ? requestEntry.analysis.items[0] : null; + const routedTags = Array.isArray(firstItem?.tags) && firstItem.tags.length > 0 + ? firstItem.tags + : requestEntry.tags.length > 0 + ? requestEntry.tags + : (requestEntry.category && requestEntry.category !== "Unsorted" ? [requestEntry.category] : []); + const likelySystems = Array.isArray(firstItem?.affectedSystems) && firstItem.affectedSystems.length > 0 + ? firstItem.affectedSystems + : (routedTags.length > 0 ? routedTags : []); + const possibleDirections = Array.isArray(firstItem?.reviewOptions) && firstItem.reviewOptions.length > 0 + ? firstItem.reviewOptions + : (requestEntry.implementationNotes.trim() ? [requestEntry.implementationNotes.trim()] : []); + return { + summary: buildRoutingSummaryFallback(requestEntry, firstItem), + ambiguity: requestEntry.status === "pending" ? "medium" : "low", + matchedTerms: extractRoutingTerms(requestEntry), + suggestedTags: Array.isArray(routedTags) ? routedTags : [], + suggestedSystems: likelySystems, + suggestedModules: [], + rationale: String( + firstItem?.reviewRationale + || requestEntry.implementationNotes + || `Routing was reconstructed from the saved request title, tags, and submission text for "${requestEntry.title}".` + ).trim(), + possibleDirections, + kbSections: [], + }; +} + +function mergeRoutingWithFallback( + requestEntry: LauncherRequest, + existingRouting: LauncherRequestAnalysisRouting | undefined, +): LauncherRequestAnalysisRouting { + const fallbackRouting = buildFallbackRouting(requestEntry); + const normalizedRouting = existingRouting || {}; + return { + summary: String(normalizedRouting.summary || "").trim() || fallbackRouting.summary, + ambiguity: normalizedRouting.ambiguity || fallbackRouting.ambiguity, + matchedTerms: Array.isArray(normalizedRouting.matchedTerms) && normalizedRouting.matchedTerms.length > 0 + ? normalizedRouting.matchedTerms + : fallbackRouting.matchedTerms, + suggestedTags: Array.isArray(normalizedRouting.suggestedTags) && normalizedRouting.suggestedTags.length > 0 + ? normalizedRouting.suggestedTags + : fallbackRouting.suggestedTags, + suggestedSystems: Array.isArray(normalizedRouting.suggestedSystems) && normalizedRouting.suggestedSystems.length > 0 + ? normalizedRouting.suggestedSystems + : fallbackRouting.suggestedSystems, + suggestedModules: Array.isArray(normalizedRouting.suggestedModules) && normalizedRouting.suggestedModules.length > 0 + ? normalizedRouting.suggestedModules + : fallbackRouting.suggestedModules, + rationale: String(normalizedRouting.rationale || "").trim() || fallbackRouting.rationale, + possibleDirections: Array.isArray(normalizedRouting.possibleDirections) && normalizedRouting.possibleDirections.length > 0 + ? normalizedRouting.possibleDirections + : fallbackRouting.possibleDirections, + kbSections: Array.isArray(normalizedRouting.kbSections) && normalizedRouting.kbSections.length > 0 + ? normalizedRouting.kbSections + : fallbackRouting.kbSections, + }; +} + +function hydrateLauncherRequestForUi(requestEntry: LauncherRequest): LauncherRequest { + const nextRequest = cloneLauncherRequest(requestEntry); + nextRequest.analysis = { + ...(nextRequest.analysis || {}), + createdAt: nextRequest.analysis?.createdAt || nextRequest.createdAt, + updatedAt: nextRequest.analysis?.updatedAt || nextRequest.updatedAt, + itemCount: nextRequest.analysis?.itemCount ?? (Array.isArray(nextRequest.analysis?.items) ? nextRequest.analysis?.items.length : 0), + items: Array.isArray(nextRequest.analysis?.items) ? nextRequest.analysis.items : [], + routing: mergeRoutingWithFallback(nextRequest, nextRequest.analysis?.routing), + }; + return nextRequest; +} + +function buildRequestSearchCorpus(requestEntry: LauncherRequest): string { + return normalizeSearchText([ + requestEntry.title, + requestEntry.category, + requestEntry.tags.join(" "), + requestEntry.sourceText, + requestEntry.summary, + requestEntry.implementationNotes, + requestEntry.analysis?.routing?.summary, + requestEntry.analysis?.routing?.rationale, + ...(Array.isArray(requestEntry.analysis?.routing?.matchedTerms) ? requestEntry.analysis?.routing?.matchedTerms : []), + ...(Array.isArray(requestEntry.analysis?.items) + ? requestEntry.analysis.items.flatMap((item) => [ + item.title, + item.primaryCategory, + item.parsedInterpretation, + item.implementationApproach, + ...(Array.isArray(item.tags) ? item.tags : []), + ]) + : []), + ].filter(Boolean).join(" ")); +} + +function matchesRequestFilterToken(requestEntry: LauncherRequest, token: string): boolean { + if (token === "pending") { + return requestEntry.status === "pending"; + } + if (token === "queued") { + return isQueuedPendingRequest(requestEntry); + } + if (token === "review") { + return isNeedsReviewRequest(requestEntry); + } + if (token === "active") { + return requestEntry.status === "active"; + } + if (token === "implemented") { + return requestEntry.status === "implemented"; + } + return false; +} + +function requestMatchesFilters( + requestEntry: LauncherRequest, + searchText: string, + statusSelections: string[], + tagSelections: string[], +): boolean { + const normalizedSearchText = normalizeSearchText(searchText); + if (normalizedSearchText && !buildRequestSearchCorpus(requestEntry).includes(normalizedSearchText)) { + return false; + } + if (statusSelections.length > 0 && !statusSelections.some((token) => matchesRequestFilterToken(requestEntry, token))) { + return false; + } + if (tagSelections.length > 0 && !tagSelections.some((tag) => requestEntry.tags.includes(tag))) { + return false; + } + return true; +} + +function FilterIcon() { + return ( + + ); +} + +function LogsIcon() { + return ( + + ); } function SaveIcon() { @@ -178,6 +400,70 @@ function PlayIcon() { ); } +type LauncherChipSelectorProps = { + label: string; + values: string[]; + options: string[]; + placeholder: string; + emptyLabel?: string; + onAdd: (value: string) => void; + onRemove: (value: string) => void; +}; + +function LauncherChipSelector({ + label, + values, + options, + placeholder, + emptyLabel = "No tags selected yet.", + onAdd, + onRemove, +}: LauncherChipSelectorProps) { + const availableOptions = options.filter((option) => !values.includes(option)); + return ( +
+
+ {label} + +
+
+ {values.length > 0 ? values.map((value) => ( + + {value} + + + )) : ( + {emptyLabel} + )} +
+
+ ); +} + async function resolveDefaultWorldId(): Promise { const response = await fetch("/api/world-default"); if (!response.ok) { @@ -388,7 +674,6 @@ function WorldshaperLauncher() { const launcherWindowMode = readLauncherWindowMode(); const adminWindowMode = launcherWindowMode === "admin"; const [launchState, setLaunchState] = useState("ready"); - const [status, setStatus] = useState("Launch Worldshaper Studio in its floating window."); const [error, setError] = useState(""); const [worldId, setWorldId] = useState(DEFAULT_EDITOR_WORLD_ID_FALLBACK); const [activeBoardTab, setActiveBoardTab] = useState(adminWindowMode ? "requests" : "news"); @@ -399,7 +684,10 @@ function WorldshaperLauncher() { const [requestDraft, setRequestDraft] = useState(""); const [requestSubmitting, setRequestSubmitting] = useState(false); const [requestMutatingId, setRequestMutatingId] = useState(""); - const [requestFilter, setRequestFilter] = useState("all"); + const [requestSearchText, setRequestSearchText] = useState(""); + const [requestFilterMenuOpen, setRequestFilterMenuOpen] = useState(false); + const [requestStatusFilters, setRequestStatusFilters] = useState([]); + const [requestTagFilters, setRequestTagFilters] = useState([]); const [allowedRequestTags, setAllowedRequestTags] = useState([]); const [expandedRequestIds, setExpandedRequestIds] = useState([]); const [adminAccessGranted, setAdminAccessGranted] = useState(false); @@ -409,10 +697,16 @@ function WorldshaperLauncher() { const [adminPasswordError, setAdminPasswordError] = useState(""); const [selectedAdminRequestId, setSelectedAdminRequestId] = useState(""); const [selectedAdminAnalysisIndex, setSelectedAdminAnalysisIndex] = useState(0); + const [adminSearchText, setAdminSearchText] = useState(""); + const [adminFilterMenuOpen, setAdminFilterMenuOpen] = useState(false); + const [adminStatusFilters, setAdminStatusFilters] = useState([]); + const [adminTagFilters, setAdminTagFilters] = useState([]); const [adminEditorDraft, setAdminEditorDraft] = useState(null); + const [adminDetailTab, setAdminDetailTab] = useState("routing"); const [adminSaving, setAdminSaving] = useState(false); const [recentSaveEvents, setRecentSaveEvents] = useState([]); const [logsLoading, setLogsLoading] = useState(false); + const [logsModalOpen, setLogsModalOpen] = useState(false); const [logsError, setLogsError] = useState(""); const [queueTriggering, setQueueTriggering] = useState(false); const [requeueingMode, setRequeueingMode] = useState<"" | "saved" | "draft">(""); @@ -447,7 +741,7 @@ function WorldshaperLauncher() { } try { const payload = await fetchJsonOrThrow("/api/launcher-requests"); - setRequests(Array.isArray(payload.requests) ? payload.requests : []); + setRequests(Array.isArray(payload.requests) ? payload.requests.map(hydrateLauncherRequestForUi) : []); setRequestsError(""); } catch (nextError: unknown) { setRequestsError(String(nextError || "Failed to load requests.")); @@ -527,7 +821,7 @@ function WorldshaperLauncher() { try { const payload = await fetchJsonOrThrow("/api/launcher-requests"); if (!cancelled) { - setRequests(Array.isArray(payload.requests) ? payload.requests : []); + setRequests(Array.isArray(payload.requests) ? payload.requests.map(hydrateLauncherRequestForUi) : []); } } catch { // Keep the current list visible during background refresh failures. @@ -568,39 +862,38 @@ function WorldshaperLauncher() { const selectedRequest = requests.find((entry) => entry.id === selectedAdminRequestId); if (selectedRequest) { if (!adminEditorDraft || adminEditorDraft.id !== selectedRequest.id) { - setAdminEditorDraft(cloneLauncherRequest(selectedRequest)); + setAdminEditorDraft(cloneLauncherRequest(hydrateLauncherRequestForUi(selectedRequest))); } return; } setSelectedAdminRequestId(requests[0].id); - setAdminEditorDraft(cloneLauncherRequest(requests[0])); + setAdminEditorDraft(cloneLauncherRequest(hydrateLauncherRequestForUi(requests[0]))); }, [adminPanelOpen, adminAccessGranted, requests, selectedAdminRequestId, adminEditorDraft]); useEffect(() => { setSelectedAdminAnalysisIndex(0); }, [selectedAdminRequestId]); + useEffect(() => { + setAdminDetailTab("routing"); + }, [selectedAdminRequestId]); + async function handleLaunch(): Promise { setError(""); const nextWorldId = worldId || DEFAULT_EDITOR_WORLD_ID_FALLBACK; setLaunchState("opening"); - setStatus(`Opening Worldshaper Studio for ${nextWorldId}...`); try { const resolvedWorldId = nextWorldId || await resolveDefaultWorldId().catch(() => DEFAULT_EDITOR_WORLD_ID_FALLBACK); setWorldId(resolvedWorldId); - setStatus(`Opening Worldshaper Studio for ${resolvedWorldId}...`); if (openStudioPopup(resolvedWorldId)) { setLaunchState("opened"); - setStatus("Worldshaper Studio opened in a separate window."); return; } setLaunchState("blocked"); - setStatus("Your browser blocked the studio window. Use the launch button again after allowing popups."); } catch (nextError: unknown) { const nextErrorText = String(nextError || "Failed to prepare Worldshaper Studio."); setLaunchState("error"); setError(nextErrorText); - setStatus("Worldshaper Studio unavailable."); } } @@ -619,7 +912,7 @@ function WorldshaperLauncher() { }, body: JSON.stringify({ text }), }); - setRequests(Array.isArray(payload.requests) ? payload.requests : []); + setRequests(Array.isArray(payload.requests) ? payload.requests.map(hydrateLauncherRequestForUi) : []); setRequestDraft(""); setRequestDraftOpen(false); setRequestsError(""); @@ -676,7 +969,7 @@ function WorldshaperLauncher() { const nextRequest = requests.find((entry) => entry.id === requestId); setSelectedAdminRequestId(requestId); setSelectedAdminAnalysisIndex(0); - setAdminEditorDraft(nextRequest ? cloneLauncherRequest(nextRequest) : null); + setAdminEditorDraft(nextRequest ? cloneLauncherRequest(hydrateLauncherRequestForUi(nextRequest)) : null); setAdminNotice(""); setAdminPasswordError(""); } @@ -742,10 +1035,10 @@ function WorldshaperLauncher() { `/api/launcher-requests/${encodeURIComponent(adminEditorDraft.id)}`, buildAdminSavePayload(adminEditorDraft), ); - const nextRequests = Array.isArray(payload.requests) ? payload.requests : requests; + const nextRequests = Array.isArray(payload.requests) ? payload.requests.map(hydrateLauncherRequestForUi) : requests; setRequests(nextRequests); - const refreshed = nextRequests.find((entry) => entry.id === adminEditorDraft.id) || payload.request || adminEditorDraft; - setAdminEditorDraft(cloneLauncherRequest(refreshed)); + const refreshed = nextRequests.find((entry) => entry.id === adminEditorDraft.id) || hydrateLauncherRequestForUi(payload.request || adminEditorDraft); + setAdminEditorDraft(cloneLauncherRequest(hydrateLauncherRequestForUi(refreshed))); setAdminNotice(`Saved admin changes for "${adminEditorDraft.title}".`); if (adminPanelOpen) { void loadRecentSaveEvents(); @@ -772,6 +1065,10 @@ function WorldshaperLauncher() { return; } const items = Array.isArray(nextDraft.analysis.items) ? nextDraft.analysis.items : []; + if (items.length === 0) { + setLogsError("This request does not have a structured analysis item to approve yet."); + return; + } nextDraft.analysis.items = items.map((item) => ({ ...item, statusRecommendation: "active", @@ -798,11 +1095,11 @@ function WorldshaperLauncher() { }), }, ); - const nextRequests = Array.isArray(promotePayload.requests) ? promotePayload.requests : []; + const nextRequests = Array.isArray(promotePayload.requests) ? promotePayload.requests.map(hydrateLauncherRequestForUi) : []; setRequests(nextRequests); const fallbackSelection = nextRequests[0] || null; setSelectedAdminRequestId(fallbackSelection?.id || ""); - setAdminEditorDraft(fallbackSelection ? cloneLauncherRequest(fallbackSelection) : null); + setAdminEditorDraft(fallbackSelection ? cloneLauncherRequest(hydrateLauncherRequestForUi(fallbackSelection)) : null); setAdminNotice(`Approved "${nextDraft.title}" and promoted its active request item${(nextDraft.analysis.items?.length || 0) === 1 ? "" : "s"}.`); if (adminPanelOpen) { void loadRecentSaveEvents(); @@ -848,10 +1145,10 @@ function WorldshaperLauncher() { }), }, ); - const nextRequests = Array.isArray(payload.requests) ? payload.requests : requests; + const nextRequests = Array.isArray(payload.requests) ? payload.requests.map(hydrateLauncherRequestForUi) : requests; setRequests(nextRequests); - const refreshed = nextRequests.find((entry) => entry.id === adminEditorDraft.id) || payload.request || adminEditorDraft; - setAdminEditorDraft(cloneLauncherRequest(refreshed)); + const refreshed = nextRequests.find((entry) => entry.id === adminEditorDraft.id) || hydrateLauncherRequestForUi(payload.request || adminEditorDraft); + setAdminEditorDraft(cloneLauncherRequest(hydrateLauncherRequestForUi(refreshed))); if (payload.launched) { setAdminNotice(mode === "draft" ? "Edited draft resubmitted to the analyzer." @@ -901,7 +1198,7 @@ function WorldshaperLauncher() { method: "DELETE", headers: buildAdminHeaders(adminPassword), }); - setRequests(Array.isArray(payload.requests) ? payload.requests : []); + setRequests(Array.isArray(payload.requests) ? payload.requests.map(hydrateLauncherRequestForUi) : []); setRequestsError(""); setExpandedRequestIds((current) => current.filter((entry) => entry !== requestEntry.id)); setAdminNotice(`Deleted request "${requestEntry.title}".`); @@ -977,38 +1274,54 @@ function WorldshaperLauncher() { const requestTagFilterOptions = requestTags .map((tag) => ({ tag, - count: requests.filter((entry) => entry.status !== "pending" && entry.tags.includes(tag)).length, + count: requests.filter((entry) => entry.tags.includes(tag)).length, })) .filter((entry) => entry.count > 0); - const filteredRequests = requests.filter((entry) => { - if (requestFilter === "status:pending") { - return entry.status === "pending"; - } - if (requestFilter === "status:queued") { - return isQueuedPendingRequest(entry); - } - if (requestFilter === "status:review") { - return isNeedsReviewRequest(entry); - } - if (requestFilter === "status:active") { - return entry.status === "active"; - } - if (requestFilter === "status:implemented") { - return entry.status === "implemented"; - } - if (requestFilter.startsWith("tag:")) { - const tag = requestFilter.slice(4); - return entry.status !== "pending" && entry.tags.includes(tag); - } - return true; - }); + const requestStatusFilterOptions = [ + { id: "pending", label: "Pending", count: pendingRequestCount }, + { id: "queued", label: "Queued", count: queuedPendingRequestCount }, + { id: "review", label: "Needs Review", count: needsReviewRequestCount }, + { id: "active", label: "Active", count: activeRequestCount }, + { id: "implemented", label: "Implemented", count: implementedRequestCount }, + ].filter((entry) => entry.count > 0); + const filteredRequests = requests.filter((entry) => requestMatchesFilters( + entry, + requestSearchText, + requestStatusFilters, + requestTagFilters, + )); + const adminFilteredRequests = requests.filter((entry) => requestMatchesFilters( + entry, + adminSearchText, + adminStatusFilters, + adminTagFilters, + )); + const selectedAnalysisItem = adminEditorDraft?.analysis?.items?.[selectedAdminAnalysisIndex] || null; + const standardizedTagOptions = normalizeStringList([ + ...allowedRequestTags, + ...requestTags, + ...requests.flatMap((entry) => [ + ...entry.tags, + ...(Array.isArray(entry.analysis?.routing?.suggestedTags) ? entry.analysis.routing.suggestedTags : []), + ...(Array.isArray(entry.analysis?.items) ? entry.analysis.items.flatMap((item) => Array.isArray(item.tags) ? item.tags : []) : []), + ]), + ...(adminEditorDraft?.tags || []), + ...(Array.isArray(adminEditorDraft?.analysis?.routing?.suggestedTags) ? adminEditorDraft.analysis.routing.suggestedTags : []), + ...(Array.isArray(selectedAnalysisItem?.tags) ? selectedAnalysisItem.tags : []), + ]); + const categoryOptions = normalizeStringList([ + ...standardizedTagOptions, + ...requests.map((entry) => entry.category), + ...requests.flatMap((entry) => Array.isArray(entry.analysis?.items) ? entry.analysis.items.map((item) => String(item.primaryCategory || "")) : []), + String(adminEditorDraft?.category || ""), + String(selectedAnalysisItem?.primaryCategory || ""), + ]); const boardTitle = adminWindowMode ? "Worldshaper Admin" : "Worldshaper Board"; const boardHint = adminWindowMode ? `${queuedPendingRequestCount} queued, ${needsReviewRequestCount} review, ${activeRequestCount} active, ${implementedRequestCount} implemented` : (activeBoardTab === "news" ? "Latest announcements" : `${queuedPendingRequestCount} queued, ${needsReviewRequestCount} review, ${activeRequestCount} active, ${implementedRequestCount} implemented`); - const selectedAnalysisItem = adminEditorDraft?.analysis?.items?.[selectedAdminAnalysisIndex] || null; return (
-
-

{status}

- {launchState === "blocked" ? ( -

- Allow the popup, then use Launch again to open the floating editor window. -

- ) : null} - {launchState === "opened" ? ( -

- The studio is open in its own slim window. This page stays behind as your release board and relaunch point. -

- ) : null} - {launchState === "ready" ? ( -

- The editor is designed to live in its own floating window, so the launcher keeps the first step clean. -

- ) : null} - {error ?

{error}

: null} -
+ {launchState === "blocked" ?

Popup blocked. Allow popups, then press Launch again.

: null} + {error ?

{error}

: null} @@ -1114,9 +1410,10 @@ function WorldshaperLauncher() { ) : (
+ {!adminWindowMode ? (
-
{adminWindowMode ? "Protected Review Workspace" : "Shared Request Board"}
-
{adminWindowMode ? "Admin Review Console" : "Requests"}
+
Shared Request Board
+
Requests
{requestCount} saved request{requestCount === 1 ? "" : "s"}: {queuedPendingRequestCount} queued, {needsReviewRequestCount} in review, {activeRequestCount} active, and {implementedRequestCount} implemented.
@@ -1130,7 +1427,7 @@ function WorldshaperLauncher() { }} disabled={requestSubmitting} > - {requestDraftOpen && !adminPanelOpen ? "Hide Request Form" : "Add New Request"} + {requestDraftOpen ? "Hide Request Form" : "Add New Request"} {!adminPanelOpen ? (
- +
+ setRequestSearchText(event.target.value)} + placeholder="Search requests..." + /> +
+ + {requestFilterMenuOpen ? ( +
+ {requestStatusFilterOptions.length > 0 ? ( +
+
Status
+ {requestStatusFilterOptions.map((option) => ( + + ))} +
+ ) : null} + {requestTagFilterOptions.length > 0 ? ( +
+
Tags
+ {requestTagFilterOptions.map(({ tag, count }) => ( + + ))} +
+ ) : null} +
+ ) : null} +
+
+ ) : null} {adminPanelOpen ? (
{!adminAccessGranted ? ( @@ -1201,13 +1535,35 @@ function WorldshaperLauncher() { ) : ( <> -
-
-
Moderation Tools
-

Admin Panel

-

- Run the VPS queue worker, review the latest request-analysis events, and manage deletions from one place. -

+
+
+ + +
{queuedPendingRequestCount} queued @@ -1217,38 +1573,79 @@ function WorldshaperLauncher() { {implementedRequestCount} implemented
-
- - -
{adminNotice ?

{adminNotice}

: null} {adminPasswordError ?

{adminPasswordError}

: null} {logsError ?

{logsError}

: null}
-
+

Request Management

Select a request to load it on the right.
+
+ setAdminSearchText(event.target.value)} + placeholder="Search requests..." + /> +
+ + {adminFilterMenuOpen ? ( +
+ {requestStatusFilterOptions.length > 0 ? ( +
+
Status
+ {requestStatusFilterOptions.map((option) => ( + + ))} +
+ ) : null} + {requestTagFilterOptions.length > 0 ? ( +
+
Tags
+ {requestTagFilterOptions.map(({ tag, count }) => ( + + ))} +
+ ) : null} +
+ ) : null} +
+
- {requests.map((requestEntry) => { + {!requestsLoading && adminFilteredRequests.length === 0 ? ( +
No requests match the current search or filters.
+ ) : null} + {adminFilteredRequests.map((requestEntry) => { const isMutating = requestMutatingId === requestEntry.id; const isSelected = requestEntry.id === selectedAdminRequestId; + const requestDisplayState = getRequestDisplayStateLabel(requestEntry); + const requestDisplayStateClassName = getRequestDisplayStateClassName(requestEntry); return (
handleSelectAdminRequest(requestEntry.id)} >
-
{requestEntry.title}
+
+
{requestEntry.title}
+
+ {requestDisplayState} +
+
- {getRequestDisplayStateLabel(requestEntry)} {formatRequestTimestamp(requestEntry.updatedAt)}
{requestEntry.tags.length > 0 ? ( @@ -1287,29 +1688,6 @@ function WorldshaperLauncher() { })}
-
-
-

Recent Logs

-
Newest events first.
-
-
- {logsLoading && recentSaveEvents.length === 0 ? ( -
Loading admin logs...
- ) : null} - {!logsLoading && recentSaveEvents.length === 0 ? ( -
No admin logs have been recorded yet.
- ) : null} - {recentSaveEvents.map((eventEntry, index) => ( -
-
-
{formatEventLabel(eventEntry)}
-
{formatRequestTimestamp(String(eventEntry.at || ""))}
-
-
{formatEventDetail(eventEntry) || "No extra details recorded."}
-
- ))} -
-
{!adminEditorDraft ? ( @@ -1321,6 +1699,26 @@ function WorldshaperLauncher() {
Selected Request

{adminEditorDraft.title}

+
+ + +
-
-
-
-
-
Routing Pass
-
KB Routing Summary
-
-
-
- - -
-