@pie-players/pie-section-player-tools-session-debugger 0.3.4 → 0.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -29,6 +29,12 @@
29
29
  currentItemIndex: number | null;
30
30
  currentItemId: string | null;
31
31
  visitedItemIdentifiers: string[];
32
+ loadingComplete: boolean;
33
+ totalRegistered: number;
34
+ totalLoaded: number;
35
+ itemsComplete: boolean;
36
+ completedCount: number;
37
+ totalItems: number;
32
38
  updatedAt: number | null;
33
39
  lastChangedItemId: string | null;
34
40
  itemSessions: Record<string, unknown>;
@@ -39,15 +45,31 @@
39
45
  currentItemId?: string;
40
46
  visitedItemIdentifiers?: string[];
41
47
  itemSessions?: Record<string, unknown>;
48
+ loadingComplete?: boolean;
49
+ totalRegistered?: number;
50
+ totalLoaded?: number;
51
+ itemsComplete?: boolean;
52
+ completedCount?: number;
53
+ totalItems?: number;
54
+ };
55
+
56
+ type SectionSessionStateLike = {
57
+ itemSessions?: Record<string, unknown>;
42
58
  };
43
59
 
44
60
  type SectionControllerLike = {
45
- getCurrentSectionAttemptSlice?: () => SectionAttemptSliceLike | null;
61
+ getRuntimeState?: () => SectionAttemptSliceLike | null;
62
+ getSessionState?: () => SectionSessionStateLike | null;
46
63
  subscribe?: (listener: (event: { itemId?: string; timestamp?: number }) => void) => () => void;
47
64
  };
48
65
 
49
66
  type ToolkitCoordinatorLike = {
50
67
  getSectionController?: (args: { sectionId: string; attemptId?: string }) => SectionControllerLike | undefined;
68
+ subscribeSectionEvents: (args: {
69
+ sectionId: string;
70
+ attemptId?: string;
71
+ listener: (event: { itemId?: string; timestamp?: number }) => void;
72
+ }) => () => void;
51
73
  onSectionControllerLifecycle?: (
52
74
  listener: (event: { type: 'ready' | 'disposed'; key?: { sectionId?: string; attemptId?: string } }) => void
53
75
  ) => () => void;
@@ -73,15 +95,29 @@
73
95
  currentItemIndex: null,
74
96
  currentItemId: null,
75
97
  visitedItemIdentifiers: [],
98
+ loadingComplete: false,
99
+ totalRegistered: 0,
100
+ totalLoaded: 0,
101
+ itemsComplete: false,
102
+ completedCount: 0,
103
+ totalItems: 0,
76
104
  updatedAt: null,
77
105
  lastChangedItemId: null,
78
106
  itemSessions: {}
79
107
  });
80
- let activeController: SectionControllerLike | null = null;
81
108
  let unsubscribeController: (() => void) | null = null;
82
109
  let unsubscribeLifecycle: (() => void) | null = null;
83
110
  let controllerAvailable = $state(false);
84
- let refreshQueued = false;
111
+ let resubscribeQueued = false;
112
+ const subscriptionTarget: {
113
+ controller: SectionControllerLike | null;
114
+ sectionId: string;
115
+ attemptId?: string;
116
+ } = {
117
+ controller: null,
118
+ sectionId: '',
119
+ attemptId: undefined
120
+ };
85
121
 
86
122
  function cloneSessionSnapshot<T>(value: T): T {
87
123
  try {
@@ -111,7 +147,8 @@
111
147
  controllerOverride?: SectionControllerLike | null
112
148
  ) {
113
149
  const controller = controllerOverride || getController();
114
- const sectionSlice = controller?.getCurrentSectionAttemptSlice?.() || null;
150
+ const sectionSlice = controller?.getRuntimeState?.() || null;
151
+ const persistedSlice = controller?.getSessionState?.() || null;
115
152
  controllerAvailable = Boolean(controller);
116
153
  sessionPanelSnapshot = {
117
154
  currentItemIndex:
@@ -123,30 +160,26 @@
123
160
  ? sectionSlice.currentItemId
124
161
  : null,
125
162
  visitedItemIdentifiers: cloneSessionSnapshot(sectionSlice?.visitedItemIdentifiers || []),
163
+ loadingComplete: sectionSlice?.loadingComplete === true,
164
+ totalRegistered: typeof sectionSlice?.totalRegistered === 'number' ? sectionSlice.totalRegistered : 0,
165
+ totalLoaded: typeof sectionSlice?.totalLoaded === 'number' ? sectionSlice.totalLoaded : 0,
166
+ itemsComplete: sectionSlice?.itemsComplete === true,
167
+ completedCount: typeof sectionSlice?.completedCount === 'number' ? sectionSlice.completedCount : 0,
168
+ totalItems: typeof sectionSlice?.totalItems === 'number' ? sectionSlice.totalItems : 0,
126
169
  updatedAt: meta?.updatedAt || Date.now(),
127
170
  lastChangedItemId: meta?.itemId || null,
128
- itemSessions: cloneSessionSnapshot(sectionSlice?.itemSessions || {})
171
+ itemSessions: cloneSessionSnapshot(
172
+ sectionSlice?.itemSessions || persistedSlice?.itemSessions || {}
173
+ )
129
174
  };
130
175
  }
131
176
 
132
- function queueRefresh(meta?: { itemId?: string; updatedAt?: number }) {
133
- if (refreshQueued) return;
134
- refreshQueued = true;
135
- queueMicrotask(() => {
136
- refreshQueued = false;
137
- ensureControllerSubscription();
138
- refreshFromController(
139
- meta || {
140
- updatedAt: Date.now()
141
- }
142
- );
143
- });
144
- }
145
-
146
177
  function detachControllerSubscription() {
147
178
  unsubscribeController?.();
148
179
  unsubscribeController = null;
149
- activeController = null;
180
+ subscriptionTarget.controller = null;
181
+ subscriptionTarget.sectionId = '';
182
+ subscriptionTarget.attemptId = undefined;
150
183
  }
151
184
 
152
185
  function detachLifecycleSubscription() {
@@ -154,6 +187,13 @@
154
187
  unsubscribeLifecycle = null;
155
188
  }
156
189
 
190
+ function handleControllerEvent(detail: { itemId?: string; timestamp?: number }): void {
191
+ refreshFromController({
192
+ itemId: detail?.itemId,
193
+ updatedAt: detail?.timestamp || Date.now()
194
+ });
195
+ }
196
+
157
197
  function ensureControllerSubscription() {
158
198
  const controller = getController() || null;
159
199
  if (!controller) {
@@ -163,32 +203,45 @@
163
203
  currentItemIndex: null,
164
204
  currentItemId: null,
165
205
  visitedItemIdentifiers: [],
206
+ loadingComplete: false,
207
+ totalRegistered: 0,
208
+ totalLoaded: 0,
209
+ itemsComplete: false,
210
+ completedCount: 0,
211
+ totalItems: 0,
166
212
  updatedAt: Date.now(),
167
213
  lastChangedItemId: null,
168
214
  itemSessions: {}
169
215
  };
170
216
  return;
171
217
  }
172
- if (controller === activeController) return;
218
+ const nextAttemptId = attemptId || undefined;
219
+ const isSameTarget =
220
+ subscriptionTarget.controller === controller &&
221
+ subscriptionTarget.sectionId === sectionId &&
222
+ subscriptionTarget.attemptId === nextAttemptId;
223
+ if (isSameTarget && unsubscribeController) {
224
+ refreshFromController(undefined, controller);
225
+ return;
226
+ }
173
227
  detachControllerSubscription();
174
- activeController = controller;
175
- const subscribe = typeof controller.subscribe === 'function' ? controller.subscribe.bind(controller) : null;
176
- unsubscribeController =
177
- subscribe?.((detail) => {
178
- refreshFromController(
179
- {
180
- itemId: detail?.itemId,
181
- updatedAt: detail?.timestamp || Date.now()
182
- },
183
- controller
184
- );
185
- }) || null;
228
+ unsubscribeController = toolkitCoordinator?.subscribeSectionEvents({
229
+ sectionId,
230
+ attemptId,
231
+ listener: handleControllerEvent
232
+ }) || null;
233
+ subscriptionTarget.controller = controller;
234
+ subscriptionTarget.sectionId = sectionId;
235
+ subscriptionTarget.attemptId = nextAttemptId;
186
236
  refreshFromController(undefined, controller);
187
237
  }
188
238
 
189
- export function refreshFromHost(): void {
190
- queueRefresh({
191
- updatedAt: Date.now()
239
+ function queueEnsureControllerSubscription(): void {
240
+ if (resubscribeQueued) return;
241
+ resubscribeQueued = true;
242
+ queueMicrotask(() => {
243
+ resubscribeQueued = false;
244
+ ensureControllerSubscription();
192
245
  });
193
246
  }
194
247
 
@@ -198,7 +251,7 @@
198
251
  detachLifecycleSubscription();
199
252
  unsubscribeLifecycle = toolkitCoordinator.onSectionControllerLifecycle?.((event) => {
200
253
  if (!isMatchingSectionControllerLifecycleEvent(event, sectionId, attemptId)) return;
201
- ensureControllerSubscription();
254
+ queueEnsureControllerSubscription();
202
255
  refreshFromController({
203
256
  updatedAt: Date.now()
204
257
  });
@@ -229,20 +282,6 @@
229
282
  sessionWindowY = initial.y;
230
283
  sessionWindowWidth = initial.width;
231
284
  sessionWindowHeight = initial.height;
232
-
233
- const handleRuntimeSessionEvent = () => {
234
- queueRefresh({
235
- updatedAt: Date.now()
236
- });
237
- };
238
- document.addEventListener('session-changed', handleRuntimeSessionEvent as EventListener, true);
239
- document.addEventListener('item-session-changed', handleRuntimeSessionEvent as EventListener, true);
240
- document.addEventListener('composition-changed', handleRuntimeSessionEvent as EventListener, true);
241
- return () => {
242
- document.removeEventListener('session-changed', handleRuntimeSessionEvent as EventListener, true);
243
- document.removeEventListener('item-session-changed', handleRuntimeSessionEvent as EventListener, true);
244
- document.removeEventListener('composition-changed', handleRuntimeSessionEvent as EventListener, true);
245
- };
246
285
  });
247
286
 
248
287
  const pointerController = createFloatingPanelPointerController({
@@ -320,19 +359,20 @@
320
359
  </svg>
321
360
  <span class="pie-section-player-tools-session-debugger__text-xs">Section controller not available for this section yet.</span>
322
361
  </div>
323
- {:else if Object.keys(sessionPanelSnapshot.itemSessions || {}).length === 0}
324
- <div class="pie-section-player-tools-session-debugger__alert pie-section-player-tools-session-debugger__alert--info">
325
- <svg
326
- xmlns="http://www.w3.org/2000/svg"
327
- class="pie-section-player-tools-session-debugger__icon-md"
328
- fill="none"
329
- viewBox="0 0 24 24"
330
- >
331
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
332
- </svg>
333
- <span class="pie-section-player-tools-session-debugger__text-xs">No section session data yet. Interact with the questions to see updates.</span>
334
- </div>
335
362
  {:else}
363
+ {#if Object.keys(sessionPanelSnapshot.itemSessions || {}).length === 0}
364
+ <div class="pie-section-player-tools-session-debugger__alert pie-section-player-tools-session-debugger__alert--info">
365
+ <svg
366
+ xmlns="http://www.w3.org/2000/svg"
367
+ class="pie-section-player-tools-session-debugger__icon-md"
368
+ fill="none"
369
+ viewBox="0 0 24 24"
370
+ >
371
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
372
+ </svg>
373
+ <span class="pie-section-player-tools-session-debugger__text-xs">No section session data yet. Interact with the questions to see updates.</span>
374
+ </div>
375
+ {/if}
336
376
  <div class="pie-section-player-tools-session-debugger__card">
337
377
  <div class="pie-section-player-tools-session-debugger__card-title">
338
378
  Item Sessions Snapshot
@@ -401,8 +441,4 @@
401
441
  min-height: 0;
402
442
  }
403
443
 
404
- .pie-section-player-tools-session-debugger__resize-handle {
405
- user-select: none;
406
- }
407
-
408
444
  </style>