From 90777d5cbbc39d92fada3be3cd1ca77d6be6fa66 Mon Sep 17 00:00:00 2001 From: zurdi Date: Sat, 20 Jun 2026 09:21:43 +0000 Subject: [PATCH] refactor: address Copilot review on debug overlay - Lazy-load DebugOverlay via defineAsyncComponent so its chunk (and the vueuse perf hooks it pulls in) stays out of the default bundle until the developer toggle is enabled. - Add useDebugMode unit tests mirroring useCrtMode: default-off, persisted restore, write-through, and singleton sharing. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../v2/composables/useDebugMode/index.test.ts | 50 +++++++++++++++++++ frontend/src/v2/layouts/AppLayout.vue | 14 +++++- 2 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 frontend/src/v2/composables/useDebugMode/index.test.ts diff --git a/frontend/src/v2/composables/useDebugMode/index.test.ts b/frontend/src/v2/composables/useDebugMode/index.test.ts new file mode 100644 index 000000000..97bb35425 --- /dev/null +++ b/frontend/src/v2/composables/useDebugMode/index.test.ts @@ -0,0 +1,50 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { nextTick } from "vue"; + +const KEY = "settings.v2.debugMode"; + +// `enabled` is a module-level singleton created at import time, so it reads +// localStorage exactly once on load. Re-import the module fresh after seeding +// storage to exercise the default / persisted-read paths in isolation. +async function loadFresh() { + vi.resetModules(); + const { useDebugMode } = await import("./index"); + return useDebugMode(); +} + +beforeEach(() => { + localStorage.clear(); +}); + +afterEach(() => { + localStorage.clear(); +}); + +describe("useDebugMode", () => { + it("defaults to off when nothing is persisted", async () => { + const { enabled } = await loadFresh(); + expect(enabled.value).toBe(false); + }); + + it("restores a previously persisted value on load", async () => { + localStorage.setItem(KEY, "true"); + const { enabled } = await loadFresh(); + expect(enabled.value).toBe(true); + }); + + it("persists changes under the 'settings.v2.debugMode' key", async () => { + const { enabled } = await loadFresh(); + enabled.value = true; + await nextTick(); + expect(localStorage.getItem(KEY)).toBe("true"); + }); + + it("shares a single instance across calls within a load", async () => { + vi.resetModules(); + const { useDebugMode } = await import("./index"); + const a = useDebugMode(); + const b = useDebugMode(); + a.enabled.value = true; + expect(b.enabled.value).toBe(true); + }); +}); diff --git a/frontend/src/v2/layouts/AppLayout.vue b/frontend/src/v2/layouts/AppLayout.vue index 6e7aa3883..8249b71d3 100644 --- a/frontend/src/v2/layouts/AppLayout.vue +++ b/frontend/src/v2/layouts/AppLayout.vue @@ -9,7 +9,13 @@ // Per-ROM action menus are not app-wide: each GameCard owns its own // `MoreMenu` dropdown on the three-dots button. Right-click is left to // the browser so "Open in new tab" etc. keep working. -import { onBeforeUnmount, onMounted, provide, ref } from "vue"; +import { + defineAsyncComponent, + onBeforeUnmount, + onMounted, + provide, + ref, +} from "vue"; import { useRouter } from "vue-router"; import storeCollections from "@/stores/collections"; import storePlatforms from "@/stores/platforms"; @@ -17,7 +23,6 @@ import AppNav from "@/v2/components/AppShell/AppNav.vue"; import BackgroundArt from "@/v2/components/AppShell/BackgroundArt.vue"; import BottomNav from "@/v2/components/AppShell/BottomNav.vue"; import CrtOverlay from "@/v2/components/AppShell/CrtOverlay.vue"; -import DebugOverlay from "@/v2/components/AppShell/DebugOverlay.vue"; import GlobalDialogs from "@/v2/components/Dialogs/GlobalDialogs.vue"; import SoundtrackMiniPlayer from "@/v2/components/Soundtrack/MiniPlayer.vue"; import { BACKGROUND_ART_KEY } from "@/v2/composables/useBackgroundArt"; @@ -46,7 +51,12 @@ const collectionsStore = storeCollections(); const platformsStore = storePlatforms(); // Developer debug overlay — opt-in via Settings → Developer (per-device). +// Lazily loaded so its chunk (and the vueuse perf hooks it pulls in) is only +// fetched once the toggle is on, keeping it out of the default bundle. const { enabled: debugEnabled } = useDebugMode(); +const DebugOverlay = defineAsyncComponent( + () => import("@/v2/components/AppShell/DebugOverlay.vue"), +); // Shared reactive background art — views paint covers via the injected setter. const layerA = ref(null);