fix(catalog): remove suggestions that substitute different furniture
CI / check (push) Successful in 1m43s
CI / deploy (push) Successful in 1m27s
CI / e2e (push) Successful in 21s

This commit is contained in:
Simo committed 2026-09-05 15:26:46 +02:00
1 parent 33b6d1520e
commit 2d14e02a68
5 files changed
+15 -139

No files matched your search

File diff suppressed because it is too large. Load diff
@@ -2419,10 +2419,10 @@ export function StudioClient({
: "Off" : "Off"
} }
onCancel={() => setReview(null)} onCancel={() => setReview(null)}
onConfirm={(accepted, replaced) => { onConfirm={(accepted) => {
const single = review.single; const single = review.single;
setReview(null); setReview(null);
if (review.cloneAll && !replaced) if (review.cloneAll)
void cloneAllMissing(accepted.map((item) => item.classname)); void cloneAllMissing(accepted.map((item) => item.classname));
else if (single && accepted[0]) void importSingle(accepted[0]); else if (single && accepted[0]) void importSingle(accepted[0]);
else else
-24
View File
@@ -14,27 +14,3 @@ export interface SourceAssetCheck {
revision: number; revision: number;
alternatives: SourceAssetItem[]; alternatives: SourceAssetItem[];
} }
export function relatedFurniture(
item: SourceAssetItem,
candidates: SourceAssetItem[],
) {
const tail = (name: string) =>
name.toLowerCase().split("_").slice(-2).join("_");
const label = (name: string) => (name ?? "").trim().toLowerCase();
return candidates
.filter(
(candidate) =>
candidate.classname !== item.classname &&
candidate.type === item.type &&
((label(item.name) && label(candidate.name) === label(item.name)) ||
(item.classname.split("_").length >= 3 &&
tail(candidate.classname) === tail(item.classname))),
)
.sort(
(a, b) =>
Number(label(b.name) === label(item.name)) -
Number(label(a.name) === label(item.name)) ||
a.classname.localeCompare(b.classname),
)
.slice(0, 5);
}
@@ -1,9 +1,6 @@
// @vitest-environment node // @vitest-environment node
import { afterEach, describe, expect, it, vi } from "vitest"; import { afterEach, describe, expect, it, vi } from "vitest";
import { import type { SourceAssetItem } from "@/lib/furni/source-assets";
relatedFurniture,
type SourceAssetItem,
} from "@/lib/furni/source-assets";
import { inspectSourceAssets, probeAsset } from "./furniture-source-assets"; import { inspectSourceAssets, probeAsset } from "./furniture-source-assets";
const source: SourceAssetItem = { const source: SourceAssetItem = {
@@ -23,7 +20,7 @@ const alternative = {
}; };
afterEach(() => vi.unstubAllGlobals()); afterEach(() => vi.unstubAllGlobals());
describe("source asset preflight", () => { describe("source asset preflight", () => {
it("suggests reachable alternatives for unavailable source files", async () => { it("keeps missing furniture unchanged even when a similar asset is available", async () => {
const fetchMock = vi.fn( const fetchMock = vi.fn(
async (url: string) => async (url: string) =>
new Response(null, { status: url.includes("/73257/") ? 200 : 404 }), new Response(null, { status: url.includes("/73257/") ? 200 : 404 }),
@@ -31,7 +28,9 @@ describe("source asset preflight", () => {
vi.stubGlobal("fetch", fetchMock); vi.stubGlobal("fetch", fetchMock);
const result = await inspectSourceAssets(source, [source, alternative]); const result = await inspectSourceAssets(source, [source, alternative]);
expect(result.state).toBe("missing"); expect(result.state).toBe("missing");
expect(result.alternatives).toEqual([alternative]); expect(result.classname).toBe(source.classname);
expect(result.alternatives).toEqual([]);
expect(fetchMock).toHaveBeenCalledTimes(1);
}); });
it("does not describe blocked or unreachable servers as missing files", async () => { it("does not describe blocked or unreachable servers as missing files", async () => {
vi.stubGlobal( vi.stubGlobal(
@@ -67,28 +66,6 @@ describe("source asset preflight", () => {
); );
expect(result.state).toBe("available"); expect(result.state).toBe("available");
}); });
it("bounds suggestions and excludes different furniture types", () => {
expect(
relatedFurniture(source, [source, { ...alternative, type: "wallitem" }]),
).toEqual([]);
expect(
relatedFurniture(
source,
Array.from({ length: 20 }, (_, id) => ({
...alternative,
id,
classname: `f${id}_monkeybzn_pole`,
})),
),
).toHaveLength(5);
});
it("handles unnamed source entries while finding related assets", () => {
expect(
relatedFurniture(source, [
{ ...alternative, name: null as unknown as string },
]),
).toHaveLength(1);
});
it("caches a probe to avoid repeating CDN requests", async () => { it("caches a probe to avoid repeating CDN requests", async () => {
const fetchMock = vi.fn(async () => new Response(null, { status: 200 })); const fetchMock = vi.fn(async () => new Response(null, { status: 200 }));
vi.stubGlobal("fetch", fetchMock); vi.stubGlobal("fetch", fetchMock);
+2 -13
View File
@@ -3,7 +3,6 @@ import type {
SourceAssetCheck, SourceAssetCheck,
SourceAssetItem, SourceAssetItem,
} from "@/lib/furni/source-assets"; } from "@/lib/furni/source-assets";
import { relatedFurniture } from "@/lib/furni/source-assets";
import type { CloneSource } from "./clone-sources"; import type { CloneSource } from "./clone-sources";
import { browserHeaders } from "./import/core/browser-headers"; import { browserHeaders } from "./import/core/browser-headers";
@@ -78,21 +77,11 @@ export async function inspectSourceAssets(
revision: item.revision || canonical?.revision || 0, revision: item.revision || canonical?.revision || 0,
}; };
const state = await available(effective, source); const state = await available(effective, source);
const alternatives: SourceAssetItem[] = [];
if (state === "missing") {
for (const candidate of relatedFurniture(
{ ...effective, name: canonical?.name || item.name },
official,
)) {
if ((await available(candidate)) === "available")
alternatives.push(candidate);
if (alternatives.length === 3) break;
}
}
return { return {
classname: item.classname, classname: item.classname,
revision: effective.revision, revision: effective.revision,
state, state,
alternatives, alternatives: [],
}; };
} }