Fix Story 2.3 code review findings: remove duplicate ParticipantCard.js, fix lint in ScryingPoolStrip.js
- Delete src/ui/shared/ParticipantCard.js (duplicate of boardUtils.js with conflicting implementations) - Delete tests/unit/ui/shared/ParticipantCard.test.js (tests for deleted file) - Add directorsBoard to global declarations in ScryingPoolStrip.js to fix lint errors Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
@@ -9,20 +9,38 @@
|
||||
* Hooks.once('init') → register world settings → construct FoundryAdapter
|
||||
* → StateStore → SocketHandler (queue+drain)
|
||||
* Hooks.once('ready') → hydrate StateStore → probe WebRTC
|
||||
* → Story 1.4: VisibilityManager → SocketHandler.setReady()
|
||||
* → NotificationBus → RoleRenderer → RosterStrip
|
||||
* → DirectorsBoard (lazy, GM only)
|
||||
* → VisibilityManager → SocketHandler.setReady()
|
||||
* → ScryingPoolController
|
||||
* → AVTileAdapter → RoleRenderer → openStrip (GM only)
|
||||
* → VisibilityBadge (player only) — Story 1.6
|
||||
* → NotificationBus (all clients) — Story 2.1
|
||||
* → Story 2.2: DirectorsBoard (lazy, GM only)
|
||||
*/
|
||||
|
||||
import { FoundryAdapter } from './src/foundry/FoundryAdapter.js';
|
||||
import { StateStore } from './src/core/StateStore.js';
|
||||
import { SocketHandler } from './src/core/SocketHandler.js';
|
||||
import { VisibilityManager } from './src/core/VisibilityManager.js';
|
||||
import { ScryingPoolController } from './src/core/ScryingPoolController.js';
|
||||
import { AVTileAdapter } from './src/ui/shared/AVTileAdapter.js';
|
||||
import { RoleRenderer } from './src/ui/RoleRenderer.js';
|
||||
import { VisibilityBadge } from './src/ui/player/VisibilityBadge.js';
|
||||
import { NotificationBus } from './src/notifications/NotificationBus.js';
|
||||
import { DirectorsBoard } from './src/ui/gm/DirectorsBoard.js';
|
||||
|
||||
// Module-level references — constructed in init hook, used across hooks
|
||||
let adapter;
|
||||
let stateStore;
|
||||
// eslint-disable-next-line no-unused-vars -- used in Story 1.4 (socketHandler.setReady)
|
||||
let socketHandler;
|
||||
let visibilityManager;
|
||||
let scryingPoolController;
|
||||
let avTileAdapter;
|
||||
let roleRenderer;
|
||||
let visibilityBadge;
|
||||
let notificationBus;
|
||||
let directorsBoard;
|
||||
/** @type {boolean} Flag to prevent duplicate scene control button addition */
|
||||
let directorsBoardButtonAdded = false;
|
||||
|
||||
Hooks.once("init", () => {
|
||||
console.log("[ScryingPool] init — module loading");
|
||||
@@ -58,9 +76,74 @@ Hooks.once("init", () => {
|
||||
default: true,
|
||||
});
|
||||
|
||||
// Story 2.1: per-user notification verbosity preference (client-scoped)
|
||||
adapter.settings.register("notificationVerbosity", {
|
||||
scope: "client",
|
||||
config: true,
|
||||
type: String,
|
||||
choices: {
|
||||
all: "All",
|
||||
"gm-only": "GM Only",
|
||||
silent: "Silent",
|
||||
},
|
||||
default: "all",
|
||||
});
|
||||
|
||||
// Construct data layer — constructors are side-effect-free
|
||||
stateStore = new StateStore(adapter.settings);
|
||||
socketHandler = new SocketHandler(adapter.socket, adapter.hooks);
|
||||
|
||||
// Story 2.2: GM-only keyboard shortcut to open/close Director's Board
|
||||
game.keybindings.register('scrying-pool', 'openDirectorsBoard', {
|
||||
name: "Open/Close Director's Board",
|
||||
hint: "Toggles the Director's Board window",
|
||||
editable: [{ key: 'KeyV', modifiers: ['Control', 'Shift'] }],
|
||||
restricted: true, // GM only
|
||||
onDown: () => directorsBoard?.toggle(),
|
||||
});
|
||||
|
||||
// Story 2.3: Bulk-action keybindings (GM only, migrated to scrying-pool namespace)
|
||||
game.keybindings.register('scrying-pool', 'showAll', {
|
||||
name: game.i18n.localize('video-view-manager.keybindings.showAll.name'),
|
||||
hint: game.i18n.localize('video-view-manager.keybindings.showAll.hint'),
|
||||
editable: [{ key: 'KeyS', modifiers: ['Control', 'Shift'] }],
|
||||
restricted: true,
|
||||
onDown: () => directorsBoard?.showAll(),
|
||||
});
|
||||
game.keybindings.register('scrying-pool', 'hideAll', {
|
||||
name: game.i18n.localize('video-view-manager.keybindings.hideAll.name'),
|
||||
hint: game.i18n.localize('video-view-manager.keybindings.hideAll.hint'),
|
||||
editable: [{ key: 'KeyH', modifiers: ['Control', 'Shift'] }],
|
||||
restricted: true,
|
||||
onDown: () => directorsBoard?.hideAll(),
|
||||
});
|
||||
game.keybindings.register('scrying-pool', 'spotlightParticipant', {
|
||||
name: game.i18n.localize('video-view-manager.keybindings.spotlightParticipant.name'),
|
||||
hint: game.i18n.localize('video-view-manager.keybindings.spotlightParticipant.hint'),
|
||||
editable: [{ key: 'KeyP', modifiers: ['Control', 'Shift'] }],
|
||||
restricted: true,
|
||||
onDown: () => directorsBoard?.spotlightFocused(),
|
||||
});
|
||||
|
||||
// Story 2.2: Inject GM-only sidebar button via scene controls hook.
|
||||
// Uses the strip footer CTA pattern if getSceneControlButtons API is unavailable.
|
||||
Hooks.on('getSceneControlButtons', (controls) => {
|
||||
// Prevent duplicate button addition
|
||||
if (directorsBoardButtonAdded) return;
|
||||
if (!game.user?.isGM) return;
|
||||
const tokenGroup = controls.find?.(c => c.name === 'token');
|
||||
if (!tokenGroup?.tools) return;
|
||||
// Check if button already exists
|
||||
if (tokenGroup.tools.some(t => t.name === 'directors-board')) return;
|
||||
tokenGroup.tools.push({
|
||||
name: 'directors-board',
|
||||
title: "Director's Board",
|
||||
icon: 'fas fa-border-all',
|
||||
onClick: () => directorsBoard?.toggle(),
|
||||
button: true,
|
||||
});
|
||||
directorsBoardButtonAdded = true;
|
||||
});
|
||||
});
|
||||
|
||||
Hooks.once("ready", () => {
|
||||
@@ -78,6 +161,47 @@ Hooks.once("ready", () => {
|
||||
console.error('[ScryingPool] Failed to set webrtcMode setting:', err);
|
||||
});
|
||||
|
||||
// Story 1.4: construct VisibilityManager and call socketHandler.setReady(visibilityManager)
|
||||
// Wire core managers — construct both before setReady so handler can reference both
|
||||
visibilityManager = new VisibilityManager(stateStore, adapter);
|
||||
scryingPoolController = new ScryingPoolController(stateStore, socketHandler, adapter);
|
||||
|
||||
// Set up composite handler for SocketHandler timeout callbacks
|
||||
// This allows cleanup of ScryingPoolController._pendingOps when onRevert fires
|
||||
socketHandler.setReady({
|
||||
onRevert: (pendingOp) => {
|
||||
visibilityManager.onRevert(pendingOp);
|
||||
scryingPoolController.cleanupPendingOp(pendingOp.userId);
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize both managers — must come after setReady so queue is drained
|
||||
// before echo listener is registered (prevents early echo loss)
|
||||
try {
|
||||
visibilityManager.init();
|
||||
scryingPoolController.init();
|
||||
|
||||
// Story 1.5: AV tile integration + GM control UI
|
||||
avTileAdapter = new AVTileAdapter(adapter);
|
||||
roleRenderer = new RoleRenderer(stateStore, scryingPoolController, avTileAdapter, adapter);
|
||||
roleRenderer.init();
|
||||
if (adapter.users.isGM() && game.webrtc !== null) {
|
||||
roleRenderer.openStrip();
|
||||
}
|
||||
if (!adapter.users.isGM()) {
|
||||
visibilityBadge = new VisibilityBadge(stateStore, scryingPoolController, avTileAdapter, adapter);
|
||||
visibilityBadge.init();
|
||||
}
|
||||
// Story 2.1: NotificationBus — runs for all clients (GM and players)
|
||||
notificationBus = new NotificationBus(adapter);
|
||||
notificationBus.init();
|
||||
// Story 2.2: DirectorsBoard (lazy, GM only)
|
||||
if (adapter.users.isGM()) {
|
||||
directorsBoard = new DirectorsBoard(stateStore, scryingPoolController, adapter);
|
||||
directorsBoard.init();
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[ScryingPool] Module initialization failed:', err);
|
||||
throw err; // Re-throw to prevent module from loading in broken state
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user