feat: catalog audit can repair orphaned refs and duplicate classnames

- add repairOrphanedCatalog: remove catalog_items rows whose item_ids only
  reference missing items_base entries, strip orphaned ids from mixed rows
- add repairDuplicateClassnames: merge items_base duplicates into one
  canonical row per classname, remap references, delete duplicate rows
- add 'repair structural issues' checkbox + result display in audit UI
This commit is contained in:
openhands committed 2026-08-03 18:08:45 +02:00
1 parent bee55e1fd4
commit 6fc14b9b84
4 files changed
+384 -1

No files matched your search

+85 -1
View File
@@ -48,6 +48,11 @@ interface AuditSummary {
catalogSqlApplied: number;
furniDataAdded: number;
furniDataDuplicatesRemoved: number;
orphanedRemoved: number;
orphanedCleaned: number;
duplicatesMerged: number;
duplicateRowsRemoved: number;
remappedReferences: number;
}
interface RepairStats {
@@ -73,6 +78,13 @@ interface AuditEvent {
applied?: number;
sqlFailed?: number;
furniData?: { added: number; removedDuplicates: number };
structure?: {
orphanedRemoved: number;
orphanedCleaned: number;
duplicatesMerged: number;
duplicateRowsRemoved: number;
remappedReferences: number;
};
}
const TYPE_LABELS: Record<string, string> = {
@@ -131,6 +143,7 @@ export function AuditClient() {
const [generateSql, setGenerateSql] = useState(false);
const [applySql, setApplySql] = useState(false);
const [repairFurniData, setRepairFurniData] = useState(false);
const [repairStructure, setRepairStructure] = useState(false);
const [issues, setIssues] = useState<AuditIssue[]>([]);
const [missingFromSources, setMissingFromSources] = useState<
@@ -150,6 +163,13 @@ export function AuditClient() {
added: number;
removedDuplicates: number;
} | null>(null);
const [structureResult, setStructureResult] = useState<{
orphanedRemoved: number;
orphanedCleaned: number;
duplicatesMerged: number;
duplicateRowsRemoved: number;
remappedReferences: number;
} | null>(null);
const [checkProgress, setCheckProgress] = useState<{
section: string;
@@ -170,6 +190,7 @@ export function AuditClient() {
setSqlText(null);
setSqlApplied(null);
setFurniDataResult(null);
setStructureResult(null);
setCheckProgress(null);
setActiveTab("issues");
@@ -187,6 +208,7 @@ export function AuditClient() {
sql: generateSql || applySql,
applySql,
repairFurniData,
repairStructure,
}),
});
@@ -267,6 +289,10 @@ export function AuditClient() {
if (evt.furniData) setFurniDataResult(evt.furniData);
}
if (evt.type === "structure_repair_complete") {
if (evt.structure) setStructureResult(evt.structure);
}
if (evt.type === "batch_complete") {
setIssues(evt.issues ?? []);
setMissingFromSources(evt.missingFromSources ?? []);
@@ -309,7 +335,14 @@ export function AuditClient() {
setLoading(false);
abortRef.current = null;
}
}, [repairEnabled, repairNitros, generateSql, applySql, repairFurniData]);
}, [
repairEnabled,
repairNitros,
generateSql,
applySql,
repairFurniData,
repairStructure,
]);
const errors = issues.filter((i) => i.severity === "error");
const warnings = issues.filter((i) => i.severity === "warning");
@@ -372,6 +405,13 @@ export function AuditClient() {
>
Repair FurnitureData.json (add missing + dedupe)
</AuditCheckbox>
<AuditCheckbox
checked={repairStructure}
disabled={loading}
onChange={setRepairStructure}
>
Repair structural issues (orphaned refs + duplicates)
</AuditCheckbox>
{loading && (
<Button
variant="outline"
@@ -601,6 +641,49 @@ export function AuditClient() {
</span>
</div>
)}
{structureResult && (
<div className="flex items-center gap-2 col-span-full">
<span className="text-xs text-muted-foreground">
Structural repair:{" "}
{structureResult.orphanedRemoved > 0 && (
<>
<span className="text-[var(--admin-success)] font-medium">
{structureResult.orphanedRemoved} orphaned rows removed
</span>
{" · "}
</>
)}
{structureResult.orphanedCleaned > 0 && (
<>
<span className="font-medium">
{structureResult.orphanedCleaned} cleaned
</span>
{" · "}
</>
)}
{structureResult.duplicatesMerged > 0 && (
<>
<span className="text-[var(--admin-success)] font-medium">
{structureResult.duplicatesMerged} classname(s) merged
</span>
{" · "}
</>
)}
{structureResult.duplicateRowsRemoved > 0 && (
<>
<span className="text-[var(--admin-warning)] font-medium">
{structureResult.duplicateRowsRemoved} duplicate rows
removed
</span>
{" · "}
</>
)}
<span className="text-[var(--admin-text-muted)]">
{structureResult.remappedReferences} reference(s) remapped
</span>
</span>
</div>
)}
</div>
{/* Tabs */}
@@ -733,6 +816,7 @@ export function AuditClient() {
setSqlText(null);
setSqlApplied(null);
setFurniDataResult(null);
setStructureResult(null);
setProgress("");
setCheckProgress(null);
}}