From 2c58f2c0cd6ca1ce85af28d49f62cd0264b86d88 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 28 Jun 2026 00:27:22 +0000 Subject: [PATCH] fix(dashboard): reliably reopen after close (recreate stale singleton) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Sync Dashboard is a single ApplicationV2 instance created at init and re-rendered on every open. After a close — especially one that interrupts an in-flight render — that instance can be left in a non-re-renderable state, so reopening it intermittently did nothing ("can't reopen the dashboard"). Adopt the proven SyncCalendar singleton pattern: a module-level openDashboard() brings an already-open window to the front, otherwise builds, (re)binds, and renders a FRESH instance. bind() only sets a reference, so recreating leaks nothing. All four open sites (scene-control tool, sidebar click + right-click, module.api.openDashboard) route through it; module.api.dashboard becomes a live getter so it never goes stale. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01LaDDiXB5GTVurEzJwYYopf --- scripts/module.mjs | 39 +++++++++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/scripts/module.mjs b/scripts/module.mjs index 0f83460..911b149 100644 --- a/scripts/module.mjs +++ b/scripts/module.mjs @@ -30,6 +30,27 @@ let dashboard = null; /** @type {HTMLElement|null} Sidebar status indicator, re-attached on re-render. */ let _statusIndicatorEl = null; +/** + * Open the Sync Dashboard, recreating the singleton if it isn't currently + * rendered. An ApplicationV2 instance can be left in a non-re-renderable state + * after a close (notably when the close interrupts an in-flight render), which + * made the dashboard intermittently fail to reopen. Mirrors the SyncCalendar + * singleton helper: bring an already-open window to the front; otherwise build, + * (re)bind, and render a fresh instance. `bind()` only sets a reference, so + * recreating leaks nothing. + * @returns {SyncDashboard|null} + */ +function openDashboard() { + if (dashboard && dashboard.rendered) { + dashboard.bringToFront?.(); + return dashboard; + } + dashboard = new SyncDashboard(); + if (syncManager) dashboard.bind(syncManager); + dashboard.render({ force: true }); + return dashboard; +} + /** * Module initialization — register settings. * This runs before the game is fully ready. @@ -207,9 +228,9 @@ async function _runtimeValidateDescriptor() { Hooks.on('getSceneControlButtons', (controls) => { if (!game.user.isGM) return; - const openDashboard = () => { - if (dashboard) dashboard.render({ force: true }); - }; + // openDashboard() is the module-level helper above: it recreates the + // dashboard when a prior close left the instance non-re-renderable, fixing + // the intermittent "can't reopen the dashboard" bug. // FM-CAL-DASHBOARD-LINK: surface SyncCalendarApplication as a second // tool in the Chronicle Sync scene-control group. The singleton helper // is GM-only by contract; the outer GM guard above is also enforced. @@ -342,15 +363,15 @@ function _addStatusIndicator() { if (syncManager.api.state === 'disconnected') { syncManager.api.connect(); ui.notifications.info('Chronicle: Reconnecting...'); - } else if (dashboard) { - dashboard.render({ force: true }); + } else { + openDashboard(); } }); // Right-click always opens the dashboard (even when disconnected). indicator.addEventListener('contextmenu', (e) => { e.preventDefault(); - if (dashboard) dashboard.render({ force: true }); + openDashboard(); }); // Flash the dot briefly when a WS message arrives (activity indicator). @@ -431,9 +452,11 @@ Hooks.once('ready', () => { if (module) { module.api = { syncManager, - dashboard, + // Getter (not a snapshot): openDashboard() may recreate the instance, so + // expose the live reference rather than the one captured at ready time. + get dashboard() { return dashboard; }, getAPI: () => syncManager?.api, - openDashboard: () => dashboard?.render({ force: true }), + openDashboard: () => openDashboard(), // Version-independent diagnostics escape hatch. Lets an operator always // produce the System Debug snapshot from the console even if the toolbar // button or sidebar indicator regress on a Foundry upgrade. DOM-