From e7744dfc7ce239f6b3eacd9b3acffb79c2a7294f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Sat, 7 Nov 2026 13:23:13 +0500 Subject: [PATCH] preview modules: SQL file list, install with SQL waits for confirmation, link panel closes after install - The SQL review listed '( statements)': the page read file_path and statement_count, the backend sends file, target_schema, line_count and what each file does. - After a build from a link, a module with SQL files was installed without them (its NPCs missing) and the panel kept offering a second install; now the SQL confirmation opens instead, and the link panel closes once the module is installed. - The catalogue says when a filter hides modules, with Show all. Co-Authored-By: Claude Opus 5.5 --- src/lib/api.ts | 11 ++++- src/screens/ModulesPage.tsx | 92 ++++++++++++++++++++++++++++--------- 2 files changed, 81 insertions(+), 22 deletions(-) diff --git a/src/lib/api.ts b/src/lib/api.ts index 2494f0b..b0a922b 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -342,7 +342,16 @@ export interface CompatibilityReport { has_duplicate_registration: boolean; }; databases: Array<{ database_type: string; operation: string; file: string; line: number }>; - sql_files: Array<{ file_path: string; statement_count: number; tables_referenced: string[]; has_schema_mutation: boolean }>; + /** As the backend sends it (analyzer::SqlFileAudit). */ + sql_files: Array<{ + file: string; + target_schema: string; + has_create_table: boolean; + has_alter_table: boolean; + has_insert_replace: boolean; + has_delete_update: boolean; + line_count: number; + }>; core_patches: string[]; has_cmakelists: boolean; has_entrypoint_adapter: boolean; diff --git a/src/screens/ModulesPage.tsx b/src/screens/ModulesPage.tsx index eb17604..9aa1551 100644 --- a/src/screens/ModulesPage.tsx +++ b/src/screens/ModulesPage.tsx @@ -650,7 +650,13 @@ export function ModulesPage({ }, [menuOpen, serverId]); const [analyzing, setAnalyzing] = useState(false); const [analysisError, setAnalysisError] = useState(null); - const [report, setReport] = useState(null); + const [report, setReportState] = useState(null); + // The build callbacks run after an await, when `report` in their closure may be older: they read this. + const reportRef = useRef(null); + const setReport = (r: CompatibilityReport | null) => { + reportRef.current = r; + setReportState(r); + }; // Toolchain state const [toolchain, setToolchain] = useState(null); @@ -912,26 +918,28 @@ export function ModulesPage({ elapsedSeconds: (Date.now() - startTime) / 1000, }); - // Automatically install if module doesn't require schema changes + // A module without SQL is installed right away; one with SQL waits for the owner to confirm it (installing + // it without its SQL would leave out what the SQL adds, such as the module's NPCs). let autoInstalled = false; - try { - await api.previewInstallModule( - serverId, - res.module_id, - res.version, - false - ); - setNotification( - locale === "ru" - ? `Модуль '${res.display_name || res.module_id}' успешно собран и установлен!` - : `Module '${res.display_name || res.module_id}' successfully built and installed!` - ); - await load(); - setActiveTab("installed"); - onChanged?.(); - autoInstalled = true; - } catch { - // If auto-install requires manual SQL confirmation, keep build results visible + if ((reportRef.current?.sql_files.length ?? 0) > 0) { + setShowSqlDialog(true); + autoInstalled = true; // the confirmation dialog takes over; no "build succeeded" banner + } else { + try { + await api.previewInstallModule(serverId, res.module_id, res.version, false); + setNotification( + locale === "ru" + ? `Модуль '${res.display_name || res.module_id}' собран и установлен.` + : `Module '${res.display_name || res.module_id}' built and installed.` + ); + closeLinkPanel(); + await load(); + setActiveTab("installed"); + onChanged?.(); + autoInstalled = true; + } catch { + // keep the build result visible: "Install" stays available + } } if (!autoInstalled) { @@ -999,6 +1007,15 @@ export function ModulesPage({ } } + /** After a module from a link is installed, the link panel and its analysis are done with. */ + function closeLinkPanel() { + setReport(null); + setBuildResult(null); + setGithubUrl(""); + setAnalysisError(null); + setShowUrlBar(false); + } + async function performInstall(applySql: boolean) { if (!buildResult) return; setShowSqlDialog(false); @@ -1012,6 +1029,7 @@ export function ModulesPage({ applySql ); setNotification(res.message); + closeLinkPanel(); await load(); setActiveTab("installed"); onChanged?.(); @@ -2094,6 +2112,25 @@ export function ModulesPage({ })} + {(catalogFilter !== "all" || catalogCategory !== "all") && ( +

+ {locale === "ru" + ? `Показано ${filteredVerifiedCatalog.length + filteredCommunityCatalog.length} из ${(searchedVerifiedCatalog.length + searchedCommunityCatalog.length)}: включён фильтр.` + : `Showing ${filteredVerifiedCatalog.length + filteredCommunityCatalog.length} of ${searchedVerifiedCatalog.length + searchedCommunityCatalog.length}: a filter is on.`} + +

+ )} + {filteredCommunityCatalog.length > communityDisplayLimit && (