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

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,36 @@
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
+ getSession?: () => 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
+ subscribeItemEvents?: (args: {
69
+ sectionId: string;
70
+ attemptId?: string;
71
+ listener: (event: { itemId?: string; timestamp?: number }) => void;
72
+ }) => () => void;
73
+ subscribeSectionLifecycleEvents?: (args: {
74
+ sectionId: string;
75
+ attemptId?: string;
76
+ listener: (event: { itemId?: string; timestamp?: number }) => void;
77
+ }) => () => void;
51
78
  onSectionControllerLifecycle?: (
52
79
  listener: (event: { type: 'ready' | 'disposed'; key?: { sectionId?: string; attemptId?: string } }) => void
53
80
  ) => () => void;
@@ -73,15 +100,29 @@
73
100
  currentItemIndex: null,
74
101
  currentItemId: null,
75
102
  visitedItemIdentifiers: [],
103
+ loadingComplete: false,
104
+ totalRegistered: 0,
105
+ totalLoaded: 0,
106
+ itemsComplete: false,
107
+ completedCount: 0,
108
+ totalItems: 0,
76
109
  updatedAt: null,
77
110
  lastChangedItemId: null,
78
111
  itemSessions: {}
79
112
  });
80
- let activeController: SectionControllerLike | null = null;
81
113
  let unsubscribeController: (() => void) | null = null;
82
114
  let unsubscribeLifecycle: (() => void) | null = null;
83
115
  let controllerAvailable = $state(false);
84
- let refreshQueued = false;
116
+ let resubscribeQueued = false;
117
+ const subscriptionTarget: {
118
+ controller: SectionControllerLike | null;
119
+ sectionId: string;
120
+ attemptId?: string;
121
+ } = {
122
+ controller: null,
123
+ sectionId: '',
124
+ attemptId: undefined
125
+ };
85
126
 
86
127
  function cloneSessionSnapshot<T>(value: T): T {
87
128
  try {
@@ -111,7 +152,8 @@
111
152
  controllerOverride?: SectionControllerLike | null
112
153
  ) {
113
154
  const controller = controllerOverride || getController();
114
- const sectionSlice = controller?.getCurrentSectionAttemptSlice?.() || null;
155
+ const sectionSlice = controller?.getRuntimeState?.() || null;
156
+ const persistedSlice = controller?.getSession?.() || null;
115
157
  controllerAvailable = Boolean(controller);
116
158
  sessionPanelSnapshot = {
117
159
  currentItemIndex:
@@ -123,30 +165,26 @@
123
165
  ? sectionSlice.currentItemId
124
166
  : null,
125
167
  visitedItemIdentifiers: cloneSessionSnapshot(sectionSlice?.visitedItemIdentifiers || []),
168
+ loadingComplete: sectionSlice?.loadingComplete === true,
169
+ totalRegistered: typeof sectionSlice?.totalRegistered === 'number' ? sectionSlice.totalRegistered : 0,
170
+ totalLoaded: typeof sectionSlice?.totalLoaded === 'number' ? sectionSlice.totalLoaded : 0,
171
+ itemsComplete: sectionSlice?.itemsComplete === true,
172
+ completedCount: typeof sectionSlice?.completedCount === 'number' ? sectionSlice.completedCount : 0,
173
+ totalItems: typeof sectionSlice?.totalItems === 'number' ? sectionSlice.totalItems : 0,
126
174
  updatedAt: meta?.updatedAt || Date.now(),
127
175
  lastChangedItemId: meta?.itemId || null,
128
- itemSessions: cloneSessionSnapshot(sectionSlice?.itemSessions || {})
176
+ itemSessions: cloneSessionSnapshot(
177
+ sectionSlice?.itemSessions || persistedSlice?.itemSessions || {}
178
+ )
129
179
  };
130
180
  }
131
181
 
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
182
  function detachControllerSubscription() {
147
183
  unsubscribeController?.();
148
184
  unsubscribeController = null;
149
- activeController = null;
185
+ subscriptionTarget.controller = null;
186
+ subscriptionTarget.sectionId = '';
187
+ subscriptionTarget.attemptId = undefined;
150
188
  }
151
189
 
152
190
  function detachLifecycleSubscription() {
@@ -154,6 +192,13 @@
154
192
  unsubscribeLifecycle = null;
155
193
  }
156
194
 
195
+ function handleControllerEvent(detail: { itemId?: string; timestamp?: number }): void {
196
+ refreshFromController({
197
+ itemId: detail?.itemId,
198
+ updatedAt: detail?.timestamp || Date.now()
199
+ });
200
+ }
201
+
157
202
  function ensureControllerSubscription() {
158
203
  const controller = getController() || null;
159
204
  if (!controller) {
@@ -163,32 +208,54 @@
163
208
  currentItemIndex: null,
164
209
  currentItemId: null,
165
210
  visitedItemIdentifiers: [],
211
+ loadingComplete: false,
212
+ totalRegistered: 0,
213
+ totalLoaded: 0,
214
+ itemsComplete: false,
215
+ completedCount: 0,
216
+ totalItems: 0,
166
217
  updatedAt: Date.now(),
167
218
  lastChangedItemId: null,
168
219
  itemSessions: {}
169
220
  };
170
221
  return;
171
222
  }
172
- if (controller === activeController) return;
223
+ const nextAttemptId = attemptId || undefined;
224
+ const isSameTarget =
225
+ subscriptionTarget.controller === controller &&
226
+ subscriptionTarget.sectionId === sectionId &&
227
+ subscriptionTarget.attemptId === nextAttemptId;
228
+ if (isSameTarget && unsubscribeController) {
229
+ refreshFromController(undefined, controller);
230
+ return;
231
+ }
173
232
  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;
233
+ const unsubscribeItem = toolkitCoordinator?.subscribeItemEvents?.({
234
+ sectionId,
235
+ attemptId,
236
+ listener: handleControllerEvent
237
+ }) || null;
238
+ const unsubscribeSection = toolkitCoordinator?.subscribeSectionLifecycleEvents?.({
239
+ sectionId,
240
+ attemptId,
241
+ listener: handleControllerEvent
242
+ }) || null;
243
+ unsubscribeController = () => {
244
+ unsubscribeItem?.();
245
+ unsubscribeSection?.();
246
+ };
247
+ subscriptionTarget.controller = controller;
248
+ subscriptionTarget.sectionId = sectionId;
249
+ subscriptionTarget.attemptId = nextAttemptId;
186
250
  refreshFromController(undefined, controller);
187
251
  }
188
252
 
189
- export function refreshFromHost(): void {
190
- queueRefresh({
191
- updatedAt: Date.now()
253
+ function queueEnsureControllerSubscription(): void {
254
+ if (resubscribeQueued) return;
255
+ resubscribeQueued = true;
256
+ queueMicrotask(() => {
257
+ resubscribeQueued = false;
258
+ ensureControllerSubscription();
192
259
  });
193
260
  }
194
261
 
@@ -198,7 +265,7 @@
198
265
  detachLifecycleSubscription();
199
266
  unsubscribeLifecycle = toolkitCoordinator.onSectionControllerLifecycle?.((event) => {
200
267
  if (!isMatchingSectionControllerLifecycleEvent(event, sectionId, attemptId)) return;
201
- ensureControllerSubscription();
268
+ queueEnsureControllerSubscription();
202
269
  refreshFromController({
203
270
  updatedAt: Date.now()
204
271
  });
@@ -229,20 +296,6 @@
229
296
  sessionWindowY = initial.y;
230
297
  sessionWindowWidth = initial.width;
231
298
  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
299
  });
247
300
 
248
301
  const pointerController = createFloatingPanelPointerController({
@@ -320,19 +373,20 @@
320
373
  </svg>
321
374
  <span class="pie-section-player-tools-session-debugger__text-xs">Section controller not available for this section yet.</span>
322
375
  </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
376
  {:else}
377
+ {#if Object.keys(sessionPanelSnapshot.itemSessions || {}).length === 0}
378
+ <div class="pie-section-player-tools-session-debugger__alert pie-section-player-tools-session-debugger__alert--info">
379
+ <svg
380
+ xmlns="http://www.w3.org/2000/svg"
381
+ class="pie-section-player-tools-session-debugger__icon-md"
382
+ fill="none"
383
+ viewBox="0 0 24 24"
384
+ >
385
+ <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" />
386
+ </svg>
387
+ <span class="pie-section-player-tools-session-debugger__text-xs">No section session data yet. Interact with the questions to see updates.</span>
388
+ </div>
389
+ {/if}
336
390
  <div class="pie-section-player-tools-session-debugger__card">
337
391
  <div class="pie-section-player-tools-session-debugger__card-title">
338
392
  Item Sessions Snapshot
@@ -401,8 +455,4 @@
401
455
  min-height: 0;
402
456
  }
403
457
 
404
- .pie-section-player-tools-session-debugger__resize-handle {
405
- user-select: none;
406
- }
407
-
408
458
  </style>