import type { CSSProperties } from "react"; import { useEffect, useState } from "react"; import { openWorldshaperStudioWindow } from "./worldshaperStudio/windowing"; import { CHANGELOG_SECTIONS, CHANGELOG_SPLASH_FOOTNOTE, CHANGELOG_SPLASH_KICKER, CHANGELOG_SPLASH_TITLE, CHANGELOG_SPLASH_VERSION, } from "./worldshaperStudio/changelogData"; import type { ChangelogItem } from "./worldshaperStudio/changelogData"; import launcherBackground from "../background.png"; type WorldDefaultPayload = { worldId?: string; world?: { id?: string; }; }; 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; ambiguity?: "low" | "medium" | "high"; matchedTerms?: string[]; suggestedTags?: string[]; suggestedSystems?: string[]; suggestedModules?: string[]; rationale?: string; possibleDirections?: string[]; kbSections?: string[]; }; type LauncherRequest = { id: string; sourceSubmissionId?: string; title: string; status: LauncherRequestStatus; category: string; tags: string[]; sourceText: string; summary: string; implementationNotes: string; analysis?: { state?: "unprocessed" | "processing" | "processed" | "needs_review" | "error"; confidence?: number | null; model?: string; createdAt?: string; updatedAt?: string; error?: string; submissionId?: string; sourceTextSnapshot?: string; routing?: LauncherRequestAnalysisRouting; itemCount?: number; items?: Array<{ title?: string; primaryCategory?: string; tags?: string[]; statusRecommendation?: string; parsedInterpretation?: string; implementationApproach?: string; affectedSystems?: string[]; affectedFiles?: string[]; problemType?: string; rawExcerpt?: string; confidence?: number | null; reviewRationale?: string; reviewOptions?: string[]; notes?: string; }>; }; createdAt: string; updatedAt: string; }; type LauncherRequestAnalysisItem = NonNullable["items"]>[number]; type LauncherRequestsPayload = { requests?: LauncherRequest[]; }; type RecentSaveEvent = { at?: string; type?: string; requestId?: string; textPreview?: string; status?: string; category?: string; itemCount?: number; model?: string; reason?: string; provider?: string; pid?: number; code?: number | null; signal?: string; error?: string; }; type RecentSaveEventsPayload = { saves?: RecentSaveEvent[]; }; type ProcessPendingPayload = { ok?: boolean; launched?: boolean; reason?: string; autorunEnabled?: boolean; configured?: boolean; queuedPendingCount?: number; pid?: number; }; type RequeueAnalysisPayload = { ok?: boolean; launched?: boolean; reason?: string; request?: LauncherRequest; requests?: LauncherRequest[]; requestId?: string; queuedPendingCount?: number; pid?: number; }; type LauncherRequestMetaPayload = { allowedTags?: string[]; }; type AdminAuthPayload = { ok?: boolean; accessGranted?: boolean; adminConfigured?: boolean; error?: string; }; const DEFAULT_EDITOR_WORLD_ID_FALLBACK = "overworld"; function readLauncherWindowMode(): LauncherWindowMode { if (typeof window === "undefined") { return "public"; } const searchParams = new URLSearchParams(window.location.search); return searchParams.get("admin") === "requests" ? "admin" : "public"; } 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() { return ( ); } function CheckIcon() { return ( ); } function PlayIcon() { return ( ); } 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) { throw new Error(`Failed to load default world (${response.status}).`); } const payload = await response.json() as WorldDefaultPayload; const resolvedWorldId = String(payload.worldId || payload.world?.id || "").trim(); return resolvedWorldId || DEFAULT_EDITOR_WORLD_ID_FALLBACK; } async function fetchJsonOrThrow(input: RequestInfo | URL, init?: RequestInit): Promise { const response = await fetch(input, init); if (!response.ok) { let detail = `Request failed (${response.status}).`; try { const payload = await response.json() as { error?: string }; detail = String(payload?.error || detail); } catch { // Ignore JSON parse failures and fall back to status text. } throw new Error(detail); } return response.json() as Promise; } function buildAdminHeaders(password: string, headers?: HeadersInit): HeadersInit { const normalizedPassword = String(password || "").trim(); if (!normalizedPassword) { return { ...(headers || {}), }; } return { ...(headers || {}), "x-worldshaper-admin-password": normalizedPassword, }; } function isAdminAccessError(error: unknown): boolean { const text = String(error || "").toLowerCase(); return text.includes("admin access denied") || text.includes("admin access is not configured"); } function cloneLauncherRequest(requestEntry: LauncherRequest): LauncherRequest { return JSON.parse(JSON.stringify(requestEntry)) as LauncherRequest; } function formatRequestTimestamp(value: string): string { const parsed = Date.parse(String(value || "")); if (!Number.isFinite(parsed)) { return "Saved recently"; } return new Intl.DateTimeFormat(undefined, { month: "short", day: "numeric", hour: "numeric", minute: "2-digit", }).format(parsed); } function normalizeAnalysisState(value: string | undefined): string { return String(value || "").trim().toLowerCase(); } function formatAnalysisStateLabel(value: string | undefined): string { const normalized = normalizeAnalysisState(value); if (normalized === "processing") { return "Processing"; } if (normalized === "processed") { return "Processed"; } if (normalized === "needs_review") { return "Needs Review"; } if (normalized === "error") { return "Error"; } return "Unprocessed"; } function getRequestDisplayStateLabel(requestEntry: LauncherRequest): string { if (requestEntry.status === "implemented") { return "Implemented"; } if (requestEntry.status === "active") { return "Active"; } const analysisState = normalizeAnalysisState(requestEntry.analysis?.state); if (!analysisState || analysisState === "unprocessed") { return "Queued"; } if (analysisState === "needs_review") { return "Needs Review"; } if (analysisState === "error") { return "Analysis Error"; } if (analysisState === "processed") { return "Reviewed"; } return formatAnalysisStateLabel(analysisState); } function getRequestDisplayStateClassName(requestEntry: LauncherRequest): string { if (requestEntry.status === "implemented") { return "implemented"; } if (requestEntry.status === "active") { return "active"; } const analysisState = normalizeAnalysisState(requestEntry.analysis?.state); if (!analysisState || analysisState === "unprocessed") { return "queued"; } if (analysisState === "needs_review") { return "needs-review"; } if (analysisState === "error") { return "error"; } if (analysisState === "processed") { return "processed"; } if (analysisState === "processing") { return "processing"; } return "pending"; } function isQueuedPendingRequest(requestEntry: LauncherRequest): boolean { const analysisState = normalizeAnalysisState(requestEntry.analysis?.state); return requestEntry.status === "pending" && (!analysisState || analysisState === "unprocessed" || analysisState === "processing"); } function isNeedsReviewRequest(requestEntry: LauncherRequest): boolean { const analysisState = normalizeAnalysisState(requestEntry.analysis?.state); return requestEntry.status === "pending" && (analysisState === "needs_review" || analysisState === "error"); } function formatEventLabel(event: RecentSaveEvent): string { switch (String(event.type || "").trim()) { case "launcher-request-add": return "Request submitted"; case "launcher-request-delete": return "Request deleted"; case "launcher-request-update": return "Request updated"; case "launcher-request-review": return "Analysis saved for review"; case "launcher-request-promote": return "Pending request promoted"; case "launcher-request-analysis-error": return "Analysis failed"; case "launcher-request-analysis-launch": return "Queue worker launched"; case "launcher-request-analysis-finish": return "Queue worker finished"; case "launcher-request-analysis-launch-error": return "Queue worker launch error"; case "launcher-request-analysis-requeue": return "Request requeued for review"; default: return String(event.type || "Event"); } } function formatEventDetail(event: RecentSaveEvent): string { const parts = [ event.requestId ? `Request ${event.requestId}` : "", event.category ? `Category ${event.category}` : "", event.status ? `Status ${event.status}` : "", event.itemCount ? `${event.itemCount} item${event.itemCount === 1 ? "" : "s"}` : "", event.provider ? `Provider ${event.provider}` : "", event.model ? `Model ${event.model}` : "", event.reason ? `Reason ${event.reason}` : "", event.pid ? `PID ${event.pid}` : "", Number.isFinite(Number(event.code)) ? `Exit ${event.code}` : "", event.signal ? `Signal ${event.signal}` : "", event.error ? String(event.error) : "", event.textPreview ? `Preview: ${event.textPreview}` : "", ].filter(Boolean); return parts.join(" • "); } function openStudioPopup(worldId: string): boolean { const popup = openWorldshaperStudioWindow(worldId, window, { worldId }); return Boolean(popup); } function openRepo(): void { window.location.assign("https://repo.andraxion.net/"); } function openAdminPanelWindow(): boolean { const nextUrl = new URL(window.location.href); nextUrl.searchParams.set("admin", "requests"); nextUrl.searchParams.set("tab", "requests"); const popup = window.open(nextUrl.toString(), "worldshaper-admin-panel", "popup=yes,width=1620,height=980,resizable=yes,scrollbars=yes"); if (popup) { popup.focus(); } return Boolean(popup); } function WorldshaperLauncher() { const launcherWindowMode = readLauncherWindowMode(); const adminWindowMode = launcherWindowMode === "admin"; const [launchState, setLaunchState] = useState("ready"); const [error, setError] = useState(""); const [worldId, setWorldId] = useState(DEFAULT_EDITOR_WORLD_ID_FALLBACK); const [activeBoardTab, setActiveBoardTab] = useState(adminWindowMode ? "requests" : "news"); const [requests, setRequests] = useState([]); const [requestsLoading, setRequestsLoading] = useState(true); const [requestsError, setRequestsError] = useState(""); const [requestDraftOpen, setRequestDraftOpen] = useState(false); const [requestDraft, setRequestDraft] = useState(""); const [requestSubmitting, setRequestSubmitting] = useState(false); const [requestMutatingId, setRequestMutatingId] = useState(""); 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); const [adminPassword, setAdminPassword] = useState(""); const [adminPasswordDraft, setAdminPasswordDraft] = useState(""); const [adminAuthSubmitting, setAdminAuthSubmitting] = useState(false); 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">(""); const [adminNotice, setAdminNotice] = useState(""); const adminPanelOpen = adminWindowMode; useEffect(() => { let cancelled = false; void resolveDefaultWorldId() .then((resolvedWorldId) => { if (cancelled) { return; } setWorldId(resolvedWorldId); }) .catch(() => { if (cancelled) { return; } setWorldId(DEFAULT_EDITOR_WORLD_ID_FALLBACK); }); return () => { cancelled = true; }; }, []); async function loadRequests(options?: { silent?: boolean }): Promise { const silent = options?.silent === true; if (!silent) { setRequestsLoading(true); } try { const payload = await fetchJsonOrThrow("/api/launcher-requests"); setRequests(Array.isArray(payload.requests) ? payload.requests.map(hydrateLauncherRequestForUi) : []); setRequestsError(""); } catch (nextError: unknown) { setRequestsError(String(nextError || "Failed to load requests.")); } finally { if (!silent) { setRequestsLoading(false); } } } async function loadRequestMeta(): Promise { try { const payload = await fetchJsonOrThrow("/api/launcher-request-meta"); setAllowedRequestTags(Array.isArray(payload.allowedTags) ? payload.allowedTags : []); } catch { setAllowedRequestTags([]); } } async function loadRecentSaveEvents(): Promise { setLogsLoading(true); try { const payload = await fetchJsonOrThrow("/api/debug/recent-saves", { headers: buildAdminHeaders(adminPassword), }); setRecentSaveEvents(Array.isArray(payload.saves) ? payload.saves : []); setLogsError(""); } catch (nextError: unknown) { if (isAdminAccessError(nextError)) { setAdminAccessGranted(false); } setLogsError(String(nextError || "Failed to load admin logs.")); } finally { setLogsLoading(false); } } async function verifyAdminPassword(password: string): Promise { const payload = await fetchJsonOrThrow("/api/admin/auth-check", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ password }), }); if (!payload.accessGranted) { throw new Error(String(payload.error || "Admin access denied.")); } } async function refreshAdminData(options?: { includeLogs?: boolean; silentRequests?: boolean }): Promise { await loadRequests({ silent: options?.silentRequests === true }); if (options?.includeLogs && adminAccessGranted && adminPassword) { await loadRecentSaveEvents(); } } useEffect(() => { void loadRequests(); void loadRequestMeta(); }, []); useEffect(() => { if (!adminPanelOpen || !adminAccessGranted || !adminPassword) { return; } void loadRecentSaveEvents(); }, [adminPanelOpen, adminAccessGranted, adminPassword]); useEffect(() => { if (activeBoardTab !== "requests") { return; } let cancelled = false; const refreshBoard = async (): Promise => { if (!adminPanelOpen) { try { const payload = await fetchJsonOrThrow("/api/launcher-requests"); if (!cancelled) { setRequests(Array.isArray(payload.requests) ? payload.requests.map(hydrateLauncherRequestForUi) : []); } } catch { // Keep the current list visible during background refresh failures. } } if (!adminPanelOpen || !adminAccessGranted || !adminPassword) { return; } try { const payload = await fetchJsonOrThrow("/api/debug/recent-saves", { headers: buildAdminHeaders(adminPassword), }); if (!cancelled) { setRecentSaveEvents(Array.isArray(payload.saves) ? payload.saves : []); } } catch { // Avoid surfacing noisy polling failures in the admin panel. } }; const intervalId = window.setInterval(() => { void refreshBoard(); }, 15000); return () => { cancelled = true; window.clearInterval(intervalId); }; }, [activeBoardTab, adminPanelOpen, adminAccessGranted, adminPassword]); useEffect(() => { if (!adminPanelOpen || !adminAccessGranted) { return; } if (requests.length === 0) { setSelectedAdminRequestId(""); setAdminEditorDraft(null); return; } const selectedRequest = requests.find((entry) => entry.id === selectedAdminRequestId); if (selectedRequest) { if (!adminEditorDraft || adminEditorDraft.id !== selectedRequest.id) { setAdminEditorDraft(cloneLauncherRequest(hydrateLauncherRequestForUi(selectedRequest))); } return; } setSelectedAdminRequestId(requests[0].id); 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"); try { const resolvedWorldId = nextWorldId || await resolveDefaultWorldId().catch(() => DEFAULT_EDITOR_WORLD_ID_FALLBACK); setWorldId(resolvedWorldId); if (openStudioPopup(resolvedWorldId)) { setLaunchState("opened"); return; } setLaunchState("blocked"); } catch (nextError: unknown) { const nextErrorText = String(nextError || "Failed to prepare Worldshaper Studio."); setLaunchState("error"); setError(nextErrorText); } } async function handleAddRequest(): Promise { const text = requestDraft.trim(); if (!text) { setRequestsError("Write a request before saving it."); return; } setRequestSubmitting(true); try { const payload = await fetchJsonOrThrow("/api/launcher-requests", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ text }), }); setRequests(Array.isArray(payload.requests) ? payload.requests.map(hydrateLauncherRequestForUi) : []); setRequestDraft(""); setRequestDraftOpen(false); setRequestsError(""); setAdminNotice("Request saved. The VPS queue worker will pick it up if analysis autorun is enabled."); if (adminPanelOpen && adminAccessGranted) { void loadRecentSaveEvents(); } window.setTimeout(() => { void refreshAdminData({ includeLogs: adminPanelOpen && adminAccessGranted, silentRequests: true }); }, 3500); } catch (nextError: unknown) { setRequestsError(String(nextError || "Failed to save request.")); } finally { setRequestSubmitting(false); } } async function handleAdminPanelToggle(): Promise { setRequestDraftOpen(false); setRequestsError(""); setLogsError(""); if (adminWindowMode) { return; } if (!openAdminPanelWindow()) { setAdminNotice("Allow popups to open the admin review window."); } } async function handleAdminUnlock(): Promise { const submittedPassword = adminPasswordDraft.trim(); if (!submittedPassword) { setAdminPasswordError("Enter the admin password to continue."); return; } setAdminAuthSubmitting(true); setAdminPasswordError(""); try { await verifyAdminPassword(submittedPassword); setAdminPassword(submittedPassword); setAdminAccessGranted(true); setAdminNotice("Admin access granted."); await refreshAdminData({ includeLogs: true, silentRequests: true }); } catch (nextError: unknown) { setAdminAccessGranted(false); setAdminPassword(""); setAdminPasswordError(String(nextError || "Failed to unlock the admin panel.")); } finally { setAdminAuthSubmitting(false); } } function handleSelectAdminRequest(requestId: string): void { const nextRequest = requests.find((entry) => entry.id === requestId); setSelectedAdminRequestId(requestId); setSelectedAdminAnalysisIndex(0); setAdminEditorDraft(nextRequest ? cloneLauncherRequest(hydrateLauncherRequestForUi(nextRequest)) : null); setAdminNotice(""); setAdminPasswordError(""); } function updateAdminDraft(updater: (current: LauncherRequest) => LauncherRequest): void { setAdminEditorDraft((current) => (current ? updater(current) : current)); } function updateAdminDraftItem( itemIndex: number, updater: (item: LauncherRequestAnalysisItem) => LauncherRequestAnalysisItem, ): void { updateAdminDraft((current) => { const next = cloneLauncherRequest(current); if (!next.analysis) { next.analysis = { state: "needs_review", items: [], }; } const items = Array.isArray(next.analysis.items) ? [...next.analysis.items] : []; const existingItem = items[itemIndex] || {}; items[itemIndex] = updater({ ...existingItem, tags: Array.isArray(existingItem.tags) ? [...existingItem.tags] : [], affectedSystems: Array.isArray(existingItem.affectedSystems) ? [...existingItem.affectedSystems] : [], affectedFiles: Array.isArray(existingItem.affectedFiles) ? [...existingItem.affectedFiles] : [], reviewOptions: Array.isArray(existingItem.reviewOptions) ? [...existingItem.reviewOptions] : [], }); next.analysis.items = items; next.analysis.itemCount = items.length; next.analysis.updatedAt = new Date().toISOString(); return next; }); } function buildAdminSavePayload(requestEntry: LauncherRequest): RequestInit { return { method: "PATCH", headers: buildAdminHeaders(adminPassword, { "Content-Type": "application/json", }), body: JSON.stringify({ title: requestEntry.title, status: requestEntry.status, category: requestEntry.category, tags: requestEntry.tags, sourceText: requestEntry.sourceText, summary: requestEntry.summary, implementationNotes: requestEntry.implementationNotes, analysis: requestEntry.analysis, }), }; } async function handleSaveAdminRequest(): Promise { if (!adminEditorDraft) { return; } setAdminSaving(true); try { const payload = await fetchJsonOrThrow<{ request?: LauncherRequest; requests?: LauncherRequest[] }>( `/api/launcher-requests/${encodeURIComponent(adminEditorDraft.id)}`, buildAdminSavePayload(adminEditorDraft), ); const nextRequests = Array.isArray(payload.requests) ? payload.requests.map(hydrateLauncherRequestForUi) : requests; setRequests(nextRequests); 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(); } } catch (nextError: unknown) { if (isAdminAccessError(nextError)) { setAdminAccessGranted(false); setAdminPassword(""); setAdminPasswordError("Admin access expired. Enter the password again."); } setLogsError(String(nextError || "Failed to save admin changes.")); } finally { setAdminSaving(false); } } async function handleApproveAdminRequest(): Promise { if (!adminEditorDraft) { return; } const nextDraft = cloneLauncherRequest(adminEditorDraft); if (!nextDraft.analysis) { setLogsError("This request has no analysis to approve yet."); 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", })); nextDraft.analysis.state = "processed"; nextDraft.analysis.updatedAt = new Date().toISOString(); setAdminEditorDraft(nextDraft); setAdminSaving(true); try { await fetchJsonOrThrow<{ request?: LauncherRequest; requests?: LauncherRequest[] }>( `/api/launcher-requests/${encodeURIComponent(nextDraft.id)}`, buildAdminSavePayload(nextDraft), ); const promotePayload = await fetchJsonOrThrow( `/api/launcher-requests/${encodeURIComponent(nextDraft.id)}/process-analysis`, { method: "POST", headers: buildAdminHeaders(adminPassword, { "Content-Type": "application/json", }), body: JSON.stringify({ action: "promote", analysis: nextDraft.analysis, }), }, ); const nextRequests = Array.isArray(promotePayload.requests) ? promotePayload.requests.map(hydrateLauncherRequestForUi) : []; setRequests(nextRequests); const fallbackSelection = nextRequests[0] || null; setSelectedAdminRequestId(fallbackSelection?.id || ""); 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(); } } catch (nextError: unknown) { if (isAdminAccessError(nextError)) { setAdminAccessGranted(false); setAdminPassword(""); setAdminPasswordError("Admin access expired. Enter the password again."); } setLogsError(String(nextError || "Failed to approve this request.")); } finally { setAdminSaving(false); } } async function handleRequeueAnalysis(mode: "saved" | "draft"): Promise { if (!adminEditorDraft) { return; } setRequeueingMode(mode); setLogsError(""); try { const payload = await fetchJsonOrThrow( `/api/launcher-requests/${encodeURIComponent(adminEditorDraft.id)}/requeue-analysis`, { method: "POST", headers: buildAdminHeaders(adminPassword, { "Content-Type": "application/json", }), body: JSON.stringify({ mode, request: mode === "draft" ? { title: adminEditorDraft.title, category: adminEditorDraft.category, tags: adminEditorDraft.tags, sourceText: adminEditorDraft.sourceText, summary: adminEditorDraft.summary, implementationNotes: adminEditorDraft.implementationNotes, } : undefined, }), }, ); const nextRequests = Array.isArray(payload.requests) ? payload.requests.map(hydrateLauncherRequestForUi) : requests; setRequests(nextRequests); 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." : "Saved request resubmitted to the analyzer."); } else { const reason = String(payload.reason || "no-op"); if (reason === "request-analysis-already-running") { setAdminNotice("The queue worker is already running. This request will be picked up on the next pass."); } else if (reason === "request-not-queued") { setAdminNotice("That request is not currently eligible for review reruns."); } else { setAdminNotice(`Review rerun returned: ${reason}.`); } } await refreshAdminData({ includeLogs: true, silentRequests: true }); window.setTimeout(() => { void refreshAdminData({ includeLogs: true, silentRequests: true }); }, 4200); } catch (nextError: unknown) { if (isAdminAccessError(nextError)) { setAdminAccessGranted(false); setAdminPassword(""); setAdminPasswordError("Admin access expired. Enter the password again."); } setLogsError(String(nextError || "Failed to requeue this request for review.")); } finally { setRequeueingMode(""); } } function handleToggleExpandedRequest(requestId: string): void { setExpandedRequestIds((current) => ( current.includes(requestId) ? current.filter((entry) => entry !== requestId) : [...current, requestId] )); } async function handleDeleteRequest(requestEntry: LauncherRequest): Promise { const confirmed = window.confirm(`Delete this request?\n\n${requestEntry.title}`); if (!confirmed) { return; } setRequestMutatingId(requestEntry.id); try { const payload = await fetchJsonOrThrow(`/api/launcher-requests/${encodeURIComponent(requestEntry.id)}`, { method: "DELETE", headers: buildAdminHeaders(adminPassword), }); setRequests(Array.isArray(payload.requests) ? payload.requests.map(hydrateLauncherRequestForUi) : []); setRequestsError(""); setExpandedRequestIds((current) => current.filter((entry) => entry !== requestEntry.id)); setAdminNotice(`Deleted request "${requestEntry.title}".`); if (adminPanelOpen) { void loadRecentSaveEvents(); } } catch (nextError: unknown) { if (isAdminAccessError(nextError)) { setAdminAccessGranted(false); setAdminPassword(""); setAdminPasswordError("Admin access expired. Enter the password again."); } setRequestsError(String(nextError || "Failed to delete request.")); } finally { setRequestMutatingId(""); } } async function handleProcessPendingQueue(): Promise { setQueueTriggering(true); try { const payload = await fetchJsonOrThrow("/api/launcher-requests/process-pending", { method: "POST", headers: buildAdminHeaders(adminPassword), }); if (payload.launched) { setAdminNotice(`Queue worker launched for ${payload.queuedPendingCount ?? 0} pending request${payload.queuedPendingCount === 1 ? "" : "s"}.`); } else { const reason = String(payload.reason || "no-op"); if (reason === "no-pending-requests") { setAdminNotice("No unprocessed pending requests are waiting in the queue."); } else if (reason === "request-analysis-already-running") { setAdminNotice("The request analysis worker is already running on the VPS."); } else if (reason === "request-analysis-not-configured") { setAdminNotice("Request analysis is not configured on the server."); } else { setAdminNotice(`Queue trigger returned: ${reason}.`); } } await refreshAdminData({ includeLogs: true, silentRequests: true }); if (payload.launched) { window.setTimeout(() => { void refreshAdminData({ includeLogs: true, silentRequests: true }); }, 4200); } } catch (nextError: unknown) { if (isAdminAccessError(nextError)) { setAdminAccessGranted(false); setAdminPassword(""); setAdminPasswordError("Admin access expired. Enter the password again."); } setLogsError(String(nextError || "Failed to trigger the queue worker.")); } finally { setQueueTriggering(false); } } const isBusy = launchState === "opening"; const requestCount = requests.length; const pendingRequestCount = requests.filter((entry) => entry.status === "pending").length; const activeRequestCount = requests.filter((entry) => entry.status === "active").length; const implementedRequestCount = requests.filter((entry) => entry.status === "implemented").length; const queuedPendingRequestCount = requests.filter(isQueuedPendingRequest).length; const needsReviewRequestCount = requests.filter(isNeedsReviewRequest).length; const requestTags = (allowedRequestTags.length > 0 ? allowedRequestTags : Array.from(new Set( requests .flatMap((entry) => Array.isArray(entry.tags) ? entry.tags : []) .map((entry) => String(entry || "").trim()) .filter(Boolean), ))).sort((a, b) => a.localeCompare(b)); const requestTagFilterOptions = requestTags .map((tag) => ({ tag, count: requests.filter((entry) => entry.tags.includes(tag)).length, })) .filter((entry) => entry.count > 0); 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`); return (
{!adminWindowMode ? (

Worldshaper Studio

{launchState === "blocked" ?

Popup blocked. Allow popups, then press Launch again.

: null} {error ?

{error}

: null}
) : null}
{boardTitle}
{boardHint}
{!adminWindowMode ? (
) : null} {activeBoardTab === "news" ? (
{CHANGELOG_SPLASH_KICKER}
{CHANGELOG_SPLASH_TITLE}
Release {CHANGELOG_SPLASH_VERSION}
{CHANGELOG_SECTIONS.map((section) => (

{section.title}

    {section.items.map((item, index) => { const key = `${section.title}-${index}`; const normalizedItem: ChangelogItem = item; if (typeof normalizedItem === "string") { return
  • {normalizedItem}
  • ; } return (
  • {normalizedItem.text}
    {normalizedItem.note ?
    {normalizedItem.note}
    : null}
  • ); })}
))}
{CHANGELOG_SPLASH_FOOTNOTE}
) : (
{!adminWindowMode ? (
Shared Request Board
Requests
{requestCount} saved request{requestCount === 1 ? "" : "s"}: {queuedPendingRequestCount} queued, {needsReviewRequestCount} in review, {activeRequestCount} active, and {implementedRequestCount} implemented.
{!adminPanelOpen ? ( ) : null}
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 ? (
Protected Tools

Admin Access Required

Enter the admin password to manage deletions, run the queue worker, and read request logs.

{adminPasswordError ?

{adminPasswordError}

: null}
) : ( <>
{queuedPendingRequestCount} queued {needsReviewRequestCount} review {pendingRequestCount} pending {activeRequestCount} active {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}
{!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}
{requestDisplayState}
{formatRequestTimestamp(requestEntry.updatedAt)}
{requestEntry.tags.length > 0 ? (
{requestEntry.tags.slice(0, 3).map((tag) => ( {tag} ))}
) : null}
); })}
{!adminEditorDraft ? (
Select a request from the list to review it.
) : (
Selected Request

{adminEditorDraft.title}

{(adminEditorDraft.analysis?.items?.length || 0) > 1 ? ( ) : null}
{adminDetailTab === "routing" ? ( <>
Routing Pass
KB Routing Summary
updateAdminDraft((current) => ({ ...current, tags: appendUniqueString(current.tags, value) }))} onRemove={(value) => updateAdminDraft((current) => ({ ...current, tags: removeStringValue(current.tags, value) }))} /> updateAdminDraft((current) => ({ ...current, analysis: { ...(current.analysis || {}), routing: { ...(current.analysis?.routing || {}), suggestedTags: appendUniqueString( Array.isArray(current.analysis?.routing?.suggestedTags) ? current.analysis.routing.suggestedTags : [], value, ), }, }, }))} onRemove={(value) => updateAdminDraft((current) => ({ ...current, analysis: { ...(current.analysis || {}), routing: { ...(current.analysis?.routing || {}), suggestedTags: removeStringValue( Array.isArray(current.analysis?.routing?.suggestedTags) ? current.analysis.routing.suggestedTags : [], value, ), }, }, }))} />