perf(studio): defer import history and Nitro scale tools until opened
CI / check (push) Successful in 3m7s
CI / deploy (push) Successful in 1m2s
CI / publish-container (push) Successful in 1m8s

This commit is contained in:
Simo committed 2026-09-13 18:29:24 +02:00
1 parent 13665e0c3c
commit afea80708c
5 files changed
+339 -199

No files matched your search

+43
View File
@@ -33,10 +33,28 @@ test("Studio opens and dismisses detail, Nitro editor and import review", async
const errors: string[] = [];
const mutations: string[] = [];
let metadataRequests = 0;
let qualityRequests = 0;
page.on("pageerror", (error) => errors.push(error.message));
await page.route("**/api/**", async (route) => {
const request = route.request();
const url = new URL(request.url());
if (url.pathname === "/api/admin/studio/nitro-quality") {
qualityRequests++;
return route.fulfill({
json: {
report: {
scales: [32, 64].map((size) => ({
size,
state: "missing",
assets: [],
animations: [],
directions: [],
issues: [],
})),
},
},
});
}
if (url.pathname === "/api/admin/studio/inspect") {
const { classnames } = request.postDataJSON() as { classnames: string[] };
return route.fulfill({
@@ -113,6 +131,17 @@ test("Studio opens and dismisses detail, Nitro editor and import review", async
await expect(
page.getByRole("button", { name: "View Fixture chair", exact: true }),
).toBeVisible();
const history = page.getByRole("button", {
name: "Refresh history",
includeHidden: true,
});
await expect(history).toHaveCount(0);
await page.locator("summary").filter({ hasText: "Import history" }).click();
await expect(history).toBeVisible();
await page.locator("summary").filter({ hasText: "Import history" }).click();
await expect(history).toBeHidden();
await page.locator("summary").filter({ hasText: "Import history" }).click();
await expect(history).toBeVisible();
expect(metadataRequests).toBe(0);
await page
.getByRole("button", { name: "View Fixture chair", exact: true })
@@ -138,6 +167,20 @@ test("Studio opens and dismisses detail, Nitro editor and import review", async
editor.getByRole("switch", { name: "Can Sit On", exact: true }),
).toBeVisible();
expect(metadataRequests).toBe(1);
expect(qualityRequests).toBe(0);
await editor.getByRole("tab", { name: "32 / 64", exact: true }).click();
await expect(
editor.getByRole("button", { name: "Preview generation", exact: true }),
).toBeVisible();
await expect(
editor.getByText("Scale 32 · Not included", { exact: true }),
).toBeVisible();
expect(qualityRequests).toBe(1);
await editor.getByRole("tab", { name: "Visual", exact: true }).click();
await expect(
editor.getByRole("switch", { name: "Can Sit On", exact: true }),
).toBeVisible();
await editor.getByRole("button", { name: "Cancel", exact: true }).click();
await expect(editor).toHaveCount(0);
await page
@@ -16,10 +16,10 @@ import {
Save,
Sparkles,
} from "lucide-react";
import { useEffect, useEffectEvent, useState } from "react";
import { useTranslations } from "next-intl";
import { lazy, Suspense, useEffect, useEffectEvent, useState } from "react";
import { toast } from "sonner";
import { NitroQualityPanel } from "@/components/admin/studio/nitro-quality-panel";
import { NitroScale32Generator } from "@/components/admin/studio/nitro-scale32-generator";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
@@ -41,6 +41,17 @@ import {
nitroAnimationStatesCount,
} from "@/lib/furni/auto-interaction";
const NitroQualityPanel = lazy(() =>
import("@/components/admin/studio/nitro-quality-panel").then((module) => ({
default: module.NitroQualityPanel,
})),
);
const NitroScale32Generator = lazy(() =>
import("@/components/admin/studio/nitro-scale32-generator").then(
(module) => ({ default: module.NitroScale32Generator }),
),
);
interface NitroEditorDialogProps {
classname: string;
name?: string;
@@ -135,6 +146,7 @@ export function NitroEditorDialog({
open,
onOpenChange,
}: NitroEditorDialogProps) {
const loadingT = useTranslations("pages.admin.housekeeping");
const [metadata, setMetadata] = useState<NitroMetadata | null>(null);
const [originalMetadata, setOriginalMetadata] =
useState<NitroMetadata | null>(null);
@@ -448,19 +460,21 @@ export function NitroEditorDialog({
value="quality"
className="min-h-0 overflow-y-auto flex-1 px-4 sm:px-6 py-4"
>
<NitroScale32Generator
classname={classname}
disabled={hasChanges || saving}
onChanged={(updated) => {
setBundleRevision((value) => value + 1);
setMetadata(updated as NitroMetadata);
setOriginalMetadata(
structuredClone(updated) as NitroMetadata,
);
setJsonText(JSON.stringify(updated, null, 2));
}}
/>
<NitroQualityPanel key={bundleRevision} classname={classname} />
<Suspense fallback={<p role="status">{loadingT("loading")}</p>}>
<NitroScale32Generator
classname={classname}
disabled={hasChanges || saving}
onChanged={(updated) => {
setBundleRevision((value) => value + 1);
setMetadata(updated as NitroMetadata);
setOriginalMetadata(
structuredClone(updated) as NitroMetadata,
);
setJsonText(JSON.stringify(updated, null, 2));
}}
/>
<NitroQualityPanel key={bundleRevision} classname={classname} />
</Suspense>
</TabsContent>
{/* ── Visual Tab ── */}
<TabsContent
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+48
View File
@@ -0,0 +1,48 @@
import path from "node:path";
import { build } from "esbuild";
import { expect, it } from "vitest";
it("keeps scale preview and generation outside the initial Nitro editor bundle", async () => {
const entry = "src/app/admin/import/furni/nitro-editor-dialog.tsx";
const result = await build({
entryPoints: [entry],
bundle: true,
splitting: true,
format: "esm",
write: false,
outdir: "storage/bundle-check",
metafile: true,
packages: "external",
alias: { "@": path.resolve("src") },
});
const outputs = result.metafile.outputs;
const start = Object.keys(outputs).find(
(key) => outputs[key].entryPoint === entry,
);
expect(start).toBeDefined();
const seen = new Set<string>();
function visit(key: string) {
if (seen.has(key)) return;
seen.add(key);
for (const dependency of outputs[key].imports) {
if (dependency.kind !== "dynamic-import" && !dependency.external)
visit(dependency.path);
}
}
if (!start) throw new Error("Missing editor entry bundle");
visit(start);
const initialInputs = new Set(
[...seen].flatMap((key) => Object.keys(outputs[key].inputs)),
);
for (const name of [
"nitro-quality-panel.tsx",
"nitro-scale32-generator.tsx",
]) {
expect(
Object.keys(result.metafile.inputs).some((input) => input.endsWith(name)),
).toBe(true);
expect([...initialInputs].some((input) => input.endsWith(name))).toBe(
false,
);
}
}, 30_000);