@vue-skuilder/db 0.2.3 → 0.2.4
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.
- package/dist/index.d.cts +45 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.js +220 -9
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +220 -9
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/study/SessionController.ts +85 -9
- package/src/study/SessionDebugger.ts +10 -0
- package/src/study/SessionOverlay.ts +276 -0
package/dist/index.d.cts
CHANGED
|
@@ -308,6 +308,32 @@ declare class QuotaRoundRobinMixer implements SourceMixer {
|
|
|
308
308
|
mix(batches: SourceBatch[], limit: number): WeightedCard[];
|
|
309
309
|
}
|
|
310
310
|
|
|
311
|
+
/** Per-queue debug view: total length, cumulative draws, and head-first cardIDs. */
|
|
312
|
+
interface SessionQueueDebug {
|
|
313
|
+
length: number;
|
|
314
|
+
dequeueCount: number;
|
|
315
|
+
/** cardIDs in queue order, head (next draw) first. */
|
|
316
|
+
cards: string[];
|
|
317
|
+
}
|
|
318
|
+
/** Live snapshot of the controller, read fresh on each overlay tick. */
|
|
319
|
+
interface SessionDebugSnapshot {
|
|
320
|
+
secondsRemaining: number;
|
|
321
|
+
hasCardGuarantee: boolean;
|
|
322
|
+
minCardsGuarantee: number;
|
|
323
|
+
wellIndicatedRemaining: number;
|
|
324
|
+
/** cardID of the card currently in front of the learner, if any. */
|
|
325
|
+
currentCard: string | null;
|
|
326
|
+
/** Session-durable hints re-merged into every pipeline run this session. */
|
|
327
|
+
sessionHints: ReplanHints | null;
|
|
328
|
+
/** True while a replan is executing (in-flight). */
|
|
329
|
+
replanActive: boolean;
|
|
330
|
+
/** Reason for the in-flight replan (caller label, or '(auto)'); may be stale when idle. */
|
|
331
|
+
replanLabel: string | null;
|
|
332
|
+
reviewQ: SessionQueueDebug;
|
|
333
|
+
newQ: SessionQueueDebug;
|
|
334
|
+
failedQ: SessionQueueDebug;
|
|
335
|
+
}
|
|
336
|
+
|
|
311
337
|
/**
|
|
312
338
|
* Options for requesting a mid-session replan.
|
|
313
339
|
*
|
|
@@ -482,6 +508,13 @@ declare class SessionController<TView = unknown> extends Loggable {
|
|
|
482
508
|
* Used by nextCard() to await completion before drawing from queues.
|
|
483
509
|
*/
|
|
484
510
|
private _replanPromise;
|
|
511
|
+
/**
|
|
512
|
+
* Reason for the replan currently executing in `_runReplan`, surfaced by the
|
|
513
|
+
* debug overlay's spinner. The caller's `opts.label` when present, else
|
|
514
|
+
* `'(auto)'`. Only meaningful while `_replanPromise` is non-null; cleared
|
|
515
|
+
* when the in-flight chain settles.
|
|
516
|
+
*/
|
|
517
|
+
private _activeReplanLabel;
|
|
485
518
|
/**
|
|
486
519
|
* Number of well-indicated new cards remaining before the queue
|
|
487
520
|
* degrades to poorly-indicated content. Decremented on each newQ
|
|
@@ -637,6 +670,13 @@ declare class SessionController<TView = unknown> extends Loggable {
|
|
|
637
670
|
* e.g. an outcome observer that clamps a compounding boost).
|
|
638
671
|
*/
|
|
639
672
|
getSessionHints(): ReplanHints | null;
|
|
673
|
+
/**
|
|
674
|
+
* Live state snapshot for the debug overlay (window.skuilder.session
|
|
675
|
+
* .dbgOverlay()). Reads directly from the private queues and hints, so it
|
|
676
|
+
* always reflects the current moment — unlike the passive SessionDebugger
|
|
677
|
+
* snapshots, which only capture what was explicitly pushed to them.
|
|
678
|
+
*/
|
|
679
|
+
getDebugSnapshot(): SessionDebugSnapshot;
|
|
640
680
|
/**
|
|
641
681
|
* Merge `hints` into the durable session hints via the pipeline's
|
|
642
682
|
* `mergeHints` (boosts multiply, require/exclude lists concat-dedup).
|
|
@@ -1060,6 +1100,11 @@ declare const sessionDebugAPI: {
|
|
|
1060
1100
|
* Show current queue state.
|
|
1061
1101
|
*/
|
|
1062
1102
|
showQueue(): void;
|
|
1103
|
+
/**
|
|
1104
|
+
* Toggle the pinned, live-updating DOM overlay for the active controller
|
|
1105
|
+
* (queues, session hints, timer). No-ops in non-browser hosts.
|
|
1106
|
+
*/
|
|
1107
|
+
dbgOverlay(): void;
|
|
1063
1108
|
/**
|
|
1064
1109
|
* Show presentation history for current or past session.
|
|
1065
1110
|
*/
|
package/dist/index.d.ts
CHANGED
|
@@ -308,6 +308,32 @@ declare class QuotaRoundRobinMixer implements SourceMixer {
|
|
|
308
308
|
mix(batches: SourceBatch[], limit: number): WeightedCard[];
|
|
309
309
|
}
|
|
310
310
|
|
|
311
|
+
/** Per-queue debug view: total length, cumulative draws, and head-first cardIDs. */
|
|
312
|
+
interface SessionQueueDebug {
|
|
313
|
+
length: number;
|
|
314
|
+
dequeueCount: number;
|
|
315
|
+
/** cardIDs in queue order, head (next draw) first. */
|
|
316
|
+
cards: string[];
|
|
317
|
+
}
|
|
318
|
+
/** Live snapshot of the controller, read fresh on each overlay tick. */
|
|
319
|
+
interface SessionDebugSnapshot {
|
|
320
|
+
secondsRemaining: number;
|
|
321
|
+
hasCardGuarantee: boolean;
|
|
322
|
+
minCardsGuarantee: number;
|
|
323
|
+
wellIndicatedRemaining: number;
|
|
324
|
+
/** cardID of the card currently in front of the learner, if any. */
|
|
325
|
+
currentCard: string | null;
|
|
326
|
+
/** Session-durable hints re-merged into every pipeline run this session. */
|
|
327
|
+
sessionHints: ReplanHints | null;
|
|
328
|
+
/** True while a replan is executing (in-flight). */
|
|
329
|
+
replanActive: boolean;
|
|
330
|
+
/** Reason for the in-flight replan (caller label, or '(auto)'); may be stale when idle. */
|
|
331
|
+
replanLabel: string | null;
|
|
332
|
+
reviewQ: SessionQueueDebug;
|
|
333
|
+
newQ: SessionQueueDebug;
|
|
334
|
+
failedQ: SessionQueueDebug;
|
|
335
|
+
}
|
|
336
|
+
|
|
311
337
|
/**
|
|
312
338
|
* Options for requesting a mid-session replan.
|
|
313
339
|
*
|
|
@@ -482,6 +508,13 @@ declare class SessionController<TView = unknown> extends Loggable {
|
|
|
482
508
|
* Used by nextCard() to await completion before drawing from queues.
|
|
483
509
|
*/
|
|
484
510
|
private _replanPromise;
|
|
511
|
+
/**
|
|
512
|
+
* Reason for the replan currently executing in `_runReplan`, surfaced by the
|
|
513
|
+
* debug overlay's spinner. The caller's `opts.label` when present, else
|
|
514
|
+
* `'(auto)'`. Only meaningful while `_replanPromise` is non-null; cleared
|
|
515
|
+
* when the in-flight chain settles.
|
|
516
|
+
*/
|
|
517
|
+
private _activeReplanLabel;
|
|
485
518
|
/**
|
|
486
519
|
* Number of well-indicated new cards remaining before the queue
|
|
487
520
|
* degrades to poorly-indicated content. Decremented on each newQ
|
|
@@ -637,6 +670,13 @@ declare class SessionController<TView = unknown> extends Loggable {
|
|
|
637
670
|
* e.g. an outcome observer that clamps a compounding boost).
|
|
638
671
|
*/
|
|
639
672
|
getSessionHints(): ReplanHints | null;
|
|
673
|
+
/**
|
|
674
|
+
* Live state snapshot for the debug overlay (window.skuilder.session
|
|
675
|
+
* .dbgOverlay()). Reads directly from the private queues and hints, so it
|
|
676
|
+
* always reflects the current moment — unlike the passive SessionDebugger
|
|
677
|
+
* snapshots, which only capture what was explicitly pushed to them.
|
|
678
|
+
*/
|
|
679
|
+
getDebugSnapshot(): SessionDebugSnapshot;
|
|
640
680
|
/**
|
|
641
681
|
* Merge `hints` into the durable session hints via the pipeline's
|
|
642
682
|
* `mergeHints` (boosts multiply, require/exclude lists concat-dedup).
|
|
@@ -1060,6 +1100,11 @@ declare const sessionDebugAPI: {
|
|
|
1060
1100
|
* Show current queue state.
|
|
1061
1101
|
*/
|
|
1062
1102
|
showQueue(): void;
|
|
1103
|
+
/**
|
|
1104
|
+
* Toggle the pinned, live-updating DOM overlay for the active controller
|
|
1105
|
+
* (queues, session hints, timer). No-ops in non-browser hosts.
|
|
1106
|
+
*/
|
|
1107
|
+
dbgOverlay(): void;
|
|
1063
1108
|
/**
|
|
1064
1109
|
* Show presentation history for current or past session.
|
|
1065
1110
|
*/
|
package/dist/index.js
CHANGED
|
@@ -13156,6 +13156,161 @@ mountMixerDebugger();
|
|
|
13156
13156
|
// src/study/SessionDebugger.ts
|
|
13157
13157
|
init_logger();
|
|
13158
13158
|
init_PipelineDebugger();
|
|
13159
|
+
|
|
13160
|
+
// src/study/SessionOverlay.ts
|
|
13161
|
+
init_logger();
|
|
13162
|
+
var activeController = null;
|
|
13163
|
+
function registerActiveController(controller) {
|
|
13164
|
+
activeController = controller;
|
|
13165
|
+
}
|
|
13166
|
+
function getActiveController() {
|
|
13167
|
+
return activeController;
|
|
13168
|
+
}
|
|
13169
|
+
var OVERLAY_ID = "skuilder-session-overlay";
|
|
13170
|
+
var POLL_MS = 300;
|
|
13171
|
+
var INLINE_THRESHOLD = 5;
|
|
13172
|
+
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
13173
|
+
var spinnerFrame = 0;
|
|
13174
|
+
var overlayEl = null;
|
|
13175
|
+
var pollHandle = null;
|
|
13176
|
+
var expanded = { reviewQ: false, newQ: false, failedQ: false };
|
|
13177
|
+
function toggleSessionOverlay() {
|
|
13178
|
+
if (typeof document === "undefined") {
|
|
13179
|
+
logger.info("[Session Overlay] No DOM available (non-browser host); overlay unavailable.");
|
|
13180
|
+
return;
|
|
13181
|
+
}
|
|
13182
|
+
if (overlayEl) {
|
|
13183
|
+
teardown();
|
|
13184
|
+
logger.info("[Session Overlay] Hidden.");
|
|
13185
|
+
} else {
|
|
13186
|
+
mount();
|
|
13187
|
+
logger.info("[Session Overlay] Shown. Toggle off with window.skuilder.session.dbgOverlay().");
|
|
13188
|
+
}
|
|
13189
|
+
}
|
|
13190
|
+
function mount() {
|
|
13191
|
+
overlayEl = document.createElement("div");
|
|
13192
|
+
overlayEl.id = OVERLAY_ID;
|
|
13193
|
+
Object.assign(overlayEl.style, {
|
|
13194
|
+
position: "fixed",
|
|
13195
|
+
top: "8px",
|
|
13196
|
+
left: "8px",
|
|
13197
|
+
zIndex: "2147483647",
|
|
13198
|
+
maxWidth: "320px",
|
|
13199
|
+
maxHeight: "90vh",
|
|
13200
|
+
overflowY: "auto",
|
|
13201
|
+
padding: "8px 10px",
|
|
13202
|
+
background: "rgba(17, 24, 39, 0.92)",
|
|
13203
|
+
color: "#e5e7eb",
|
|
13204
|
+
font: "11px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace",
|
|
13205
|
+
borderRadius: "6px",
|
|
13206
|
+
boxShadow: "0 4px 16px rgba(0,0,0,0.4)",
|
|
13207
|
+
pointerEvents: "auto",
|
|
13208
|
+
userSelect: "none"
|
|
13209
|
+
});
|
|
13210
|
+
document.body.appendChild(overlayEl);
|
|
13211
|
+
render();
|
|
13212
|
+
pollHandle = setInterval(render, POLL_MS);
|
|
13213
|
+
}
|
|
13214
|
+
function teardown() {
|
|
13215
|
+
if (pollHandle !== null) {
|
|
13216
|
+
clearInterval(pollHandle);
|
|
13217
|
+
pollHandle = null;
|
|
13218
|
+
}
|
|
13219
|
+
if (overlayEl?.parentNode) {
|
|
13220
|
+
overlayEl.parentNode.removeChild(overlayEl);
|
|
13221
|
+
}
|
|
13222
|
+
overlayEl = null;
|
|
13223
|
+
}
|
|
13224
|
+
function render() {
|
|
13225
|
+
if (!overlayEl) return;
|
|
13226
|
+
spinnerFrame++;
|
|
13227
|
+
const ctrl = getActiveController();
|
|
13228
|
+
if (!ctrl) {
|
|
13229
|
+
overlayEl.innerHTML = headerHtml() + `<div style="opacity:.65">No active session.</div>`;
|
|
13230
|
+
return;
|
|
13231
|
+
}
|
|
13232
|
+
const s = ctrl.getDebugSnapshot();
|
|
13233
|
+
overlayEl.innerHTML = headerHtml() + replanHtml(s) + metaHtml(s) + hintsHtml(s.sessionHints) + queueHtml("reviewQ", "reviewQ", s.reviewQ) + queueHtml("newQ", "newQ", s.newQ) + queueHtml("failedQ", "failedQ", s.failedQ);
|
|
13234
|
+
overlayEl.querySelectorAll("[data-q]").forEach((el) => {
|
|
13235
|
+
el.onclick = () => {
|
|
13236
|
+
const key = el.dataset.q;
|
|
13237
|
+
if (!key) return;
|
|
13238
|
+
expanded[key] = !expanded[key];
|
|
13239
|
+
render();
|
|
13240
|
+
};
|
|
13241
|
+
});
|
|
13242
|
+
}
|
|
13243
|
+
function headerHtml() {
|
|
13244
|
+
return `<div style="font-weight:600;color:#93c5fd;margin-bottom:4px">\u2699 SessionController</div>`;
|
|
13245
|
+
}
|
|
13246
|
+
function replanHtml(s) {
|
|
13247
|
+
if (!s.replanActive) {
|
|
13248
|
+
return `<div style="margin-bottom:6px;opacity:.45">\u25CB idle</div>`;
|
|
13249
|
+
}
|
|
13250
|
+
const frame = SPINNER_FRAMES[spinnerFrame % SPINNER_FRAMES.length];
|
|
13251
|
+
const reason = esc(s.replanLabel ?? "(auto)");
|
|
13252
|
+
return `<div style="margin-bottom:6px;color:#fde047">${frame} replanning <span style="opacity:.85">[${reason}]</span></div>`;
|
|
13253
|
+
}
|
|
13254
|
+
function metaHtml(s) {
|
|
13255
|
+
const mmss = formatTime(s.secondsRemaining);
|
|
13256
|
+
const guarantee = s.hasCardGuarantee ? ` \xB7 <span style="color:#fbbf24">guarantee ${s.minCardsGuarantee}</span>` : "";
|
|
13257
|
+
const rows = [
|
|
13258
|
+
`time ${mmss}${guarantee}`,
|
|
13259
|
+
`well-indicated left: ${s.wellIndicatedRemaining}`,
|
|
13260
|
+
`current: ${s.currentCard ? esc(s.currentCard) : '<span style="opacity:.6">\u2014</span>'}`
|
|
13261
|
+
];
|
|
13262
|
+
return `<div style="margin-bottom:6px">${rows.map((r) => `<div>${r}</div>`).join("")}</div>`;
|
|
13263
|
+
}
|
|
13264
|
+
function hintsHtml(h) {
|
|
13265
|
+
const parts = [];
|
|
13266
|
+
if (h) {
|
|
13267
|
+
if (h.boostTags && Object.keys(h.boostTags).length) {
|
|
13268
|
+
parts.push(
|
|
13269
|
+
`boost: ` + Object.entries(h.boostTags).map(([k, v]) => `${esc(k)}<span style="opacity:.6">\xD7${v}</span>`).join(", ")
|
|
13270
|
+
);
|
|
13271
|
+
}
|
|
13272
|
+
if (h.boostCards && Object.keys(h.boostCards).length) {
|
|
13273
|
+
parts.push(
|
|
13274
|
+
`boostCards: ` + Object.entries(h.boostCards).map(([k, v]) => `${esc(k)}<span style="opacity:.6">\xD7${v}</span>`).join(", ")
|
|
13275
|
+
);
|
|
13276
|
+
}
|
|
13277
|
+
if (h.requireCards?.length) parts.push(`require: ${h.requireCards.map(esc).join(", ")}`);
|
|
13278
|
+
if (h.requireTags?.length) parts.push(`requireTags: ${h.requireTags.map(esc).join(", ")}`);
|
|
13279
|
+
if (h.excludeTags?.length) parts.push(`exclude: ${h.excludeTags.map(esc).join(", ")}`);
|
|
13280
|
+
if (h.excludeCards?.length) parts.push(`excludeCards: ${h.excludeCards.map(esc).join(", ")}`);
|
|
13281
|
+
}
|
|
13282
|
+
const body = parts.length ? parts.map((p) => `<div style="margin-left:6px">${p}</div>`).join("") : `<div style="margin-left:6px;opacity:.6">none</div>`;
|
|
13283
|
+
return `<div style="margin-bottom:6px"><div style="color:#86efac">sessionHints</div>${body}</div>`;
|
|
13284
|
+
}
|
|
13285
|
+
function queueHtml(key, label, q) {
|
|
13286
|
+
const collapsible = q.length > INLINE_THRESHOLD;
|
|
13287
|
+
const isOpen = !collapsible || expanded[key];
|
|
13288
|
+
const caret = collapsible ? expanded[key] ? "\u25BE " : "\u25B8 " : "";
|
|
13289
|
+
const drawn = q.dequeueCount ? ` <span style="opacity:.5">drawn ${q.dequeueCount}</span>` : "";
|
|
13290
|
+
const titleStyle = collapsible ? "cursor:pointer;color:#f9a8d4" : "color:#f9a8d4";
|
|
13291
|
+
const titleAttr = collapsible ? ` data-q="${key}"` : "";
|
|
13292
|
+
const title = `<div${titleAttr} style="${titleStyle}">${caret}${label}: ${q.length}${drawn}</div>`;
|
|
13293
|
+
let body = "";
|
|
13294
|
+
if (isOpen && q.cards.length) {
|
|
13295
|
+
body = `<ol style="margin:2px 0 6px 0;padding-left:20px">` + q.cards.map((c) => `<li style="white-space:nowrap">${esc(c)}</li>`).join("") + `</ol>`;
|
|
13296
|
+
} else if (!q.cards.length) {
|
|
13297
|
+
body = `<div style="margin:1px 0 6px 6px;opacity:.5">empty</div>`;
|
|
13298
|
+
} else {
|
|
13299
|
+
body = `<div style="margin:1px 0 6px 6px;opacity:.55">(${q.length} cards \u2014 click to expand)</div>`;
|
|
13300
|
+
}
|
|
13301
|
+
return title + body;
|
|
13302
|
+
}
|
|
13303
|
+
function formatTime(totalSeconds) {
|
|
13304
|
+
const s = Math.max(0, Math.round(totalSeconds));
|
|
13305
|
+
const m = Math.floor(s / 60);
|
|
13306
|
+
const r = s % 60;
|
|
13307
|
+
return `${m}:${r.toString().padStart(2, "0")}`;
|
|
13308
|
+
}
|
|
13309
|
+
function esc(value) {
|
|
13310
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
13311
|
+
}
|
|
13312
|
+
|
|
13313
|
+
// src/study/SessionDebugger.ts
|
|
13159
13314
|
var activeSession = null;
|
|
13160
13315
|
var sessionHistory = [];
|
|
13161
13316
|
var MAX_HISTORY = 5;
|
|
@@ -13336,6 +13491,13 @@ var sessionDebugAPI = {
|
|
|
13336
13491
|
showQueue() {
|
|
13337
13492
|
showCurrentQueue();
|
|
13338
13493
|
},
|
|
13494
|
+
/**
|
|
13495
|
+
* Toggle the pinned, live-updating DOM overlay for the active controller
|
|
13496
|
+
* (queues, session hints, timer). No-ops in non-browser hosts.
|
|
13497
|
+
*/
|
|
13498
|
+
dbgOverlay() {
|
|
13499
|
+
toggleSessionOverlay();
|
|
13500
|
+
},
|
|
13339
13501
|
/**
|
|
13340
13502
|
* Show presentation history for current or past session.
|
|
13341
13503
|
*/
|
|
@@ -13397,6 +13559,7 @@ var sessionDebugAPI = {
|
|
|
13397
13559
|
\u{1F3AF} Session Debug API
|
|
13398
13560
|
|
|
13399
13561
|
Commands:
|
|
13562
|
+
.dbgOverlay() Toggle the pinned live overlay (queues, hints, timer)
|
|
13400
13563
|
.showQueue() Show current queue state (active session only)
|
|
13401
13564
|
.showHistory(index?) Show presentation history (0=current/last, 1=previous, etc)
|
|
13402
13565
|
.showInterleaving(index?) Analyze course interleaving pattern
|
|
@@ -13465,6 +13628,13 @@ var SessionController = class _SessionController extends Loggable {
|
|
|
13465
13628
|
* Used by nextCard() to await completion before drawing from queues.
|
|
13466
13629
|
*/
|
|
13467
13630
|
_replanPromise = null;
|
|
13631
|
+
/**
|
|
13632
|
+
* Reason for the replan currently executing in `_runReplan`, surfaced by the
|
|
13633
|
+
* debug overlay's spinner. The caller's `opts.label` when present, else
|
|
13634
|
+
* `'(auto)'`. Only meaningful while `_replanPromise` is non-null; cleared
|
|
13635
|
+
* when the in-flight chain settles.
|
|
13636
|
+
*/
|
|
13637
|
+
_activeReplanLabel = null;
|
|
13468
13638
|
/**
|
|
13469
13639
|
* Number of well-indicated new cards remaining before the queue
|
|
13470
13640
|
* degrades to poorly-indicated content. Decremented on each newQ
|
|
@@ -13577,6 +13747,7 @@ var SessionController = class _SessionController extends Loggable {
|
|
|
13577
13747
|
endTime: ${this.endTime}
|
|
13578
13748
|
defaultBatchLimit: ${this._defaultBatchLimit}
|
|
13579
13749
|
initialReviewCap: ${this._initialReviewCap}`);
|
|
13750
|
+
registerActiveController(this);
|
|
13580
13751
|
}
|
|
13581
13752
|
tick() {
|
|
13582
13753
|
this._secondsRemaining = Math.floor((this.endTime.valueOf() - Date.now()) / 1e3);
|
|
@@ -13677,15 +13848,23 @@ var SessionController = class _SessionController extends Loggable {
|
|
|
13677
13848
|
);
|
|
13678
13849
|
const inflight = this._replanPromise;
|
|
13679
13850
|
const queued = inflight.catch(() => void 0).then(() => this._runReplan(opts));
|
|
13680
|
-
|
|
13681
|
-
if (this._replanPromise ===
|
|
13851
|
+
const tracked2 = queued.finally(() => {
|
|
13852
|
+
if (this._replanPromise === tracked2) {
|
|
13853
|
+
this._replanPromise = null;
|
|
13854
|
+
this._activeReplanLabel = null;
|
|
13855
|
+
}
|
|
13682
13856
|
});
|
|
13857
|
+
this._replanPromise = tracked2;
|
|
13683
13858
|
return queued;
|
|
13684
13859
|
}
|
|
13685
13860
|
const run = this._runReplan(opts);
|
|
13686
|
-
|
|
13687
|
-
if (this._replanPromise ===
|
|
13861
|
+
const tracked = run.finally(() => {
|
|
13862
|
+
if (this._replanPromise === tracked) {
|
|
13863
|
+
this._replanPromise = null;
|
|
13864
|
+
this._activeReplanLabel = null;
|
|
13865
|
+
}
|
|
13688
13866
|
});
|
|
13867
|
+
this._replanPromise = tracked;
|
|
13689
13868
|
await run;
|
|
13690
13869
|
}
|
|
13691
13870
|
/**
|
|
@@ -13694,7 +13873,6 @@ var SessionController = class _SessionController extends Loggable {
|
|
|
13694
13873
|
* triggers in nextCard) return false and may coalesce.
|
|
13695
13874
|
*/
|
|
13696
13875
|
_replanHasIntent(opts) {
|
|
13697
|
-
if (opts.label) return true;
|
|
13698
13876
|
if (opts.limit !== void 0) return true;
|
|
13699
13877
|
if (opts.minFollowUpCards !== void 0) return true;
|
|
13700
13878
|
if (opts.mode && opts.mode !== "replace") return true;
|
|
@@ -13715,6 +13893,7 @@ var SessionController = class _SessionController extends Loggable {
|
|
|
13715
13893
|
* newQ.peek(0) is the imminent draw we need to exclude.
|
|
13716
13894
|
*/
|
|
13717
13895
|
async _runReplan(opts) {
|
|
13896
|
+
this._activeReplanLabel = opts.label ?? "(auto)";
|
|
13718
13897
|
if (!opts.hints) opts.hints = {};
|
|
13719
13898
|
const hints = opts.hints;
|
|
13720
13899
|
const excludeSet = new Set(hints.excludeCards ?? []);
|
|
@@ -13772,6 +13951,34 @@ var SessionController = class _SessionController extends Loggable {
|
|
|
13772
13951
|
getSessionHints() {
|
|
13773
13952
|
return this._sessionHints;
|
|
13774
13953
|
}
|
|
13954
|
+
/**
|
|
13955
|
+
* Live state snapshot for the debug overlay (window.skuilder.session
|
|
13956
|
+
* .dbgOverlay()). Reads directly from the private queues and hints, so it
|
|
13957
|
+
* always reflects the current moment — unlike the passive SessionDebugger
|
|
13958
|
+
* snapshots, which only capture what was explicitly pushed to them.
|
|
13959
|
+
*/
|
|
13960
|
+
getDebugSnapshot() {
|
|
13961
|
+
const describe = (q) => {
|
|
13962
|
+
const cards = [];
|
|
13963
|
+
for (let i = 0; i < q.length; i++) {
|
|
13964
|
+
cards.push(q.peek(i).cardID);
|
|
13965
|
+
}
|
|
13966
|
+
return { length: q.length, dequeueCount: q.dequeueCount, cards };
|
|
13967
|
+
};
|
|
13968
|
+
return {
|
|
13969
|
+
secondsRemaining: this.secondsRemaining,
|
|
13970
|
+
hasCardGuarantee: this.hasCardGuarantee,
|
|
13971
|
+
minCardsGuarantee: this._minCardsGuarantee,
|
|
13972
|
+
wellIndicatedRemaining: this._wellIndicatedRemaining,
|
|
13973
|
+
currentCard: this._currentCard?.item.cardID ?? null,
|
|
13974
|
+
sessionHints: this._sessionHints,
|
|
13975
|
+
replanActive: this._replanPromise !== null,
|
|
13976
|
+
replanLabel: this._activeReplanLabel,
|
|
13977
|
+
reviewQ: describe(this.reviewQ),
|
|
13978
|
+
newQ: describe(this.newQ),
|
|
13979
|
+
failedQ: describe(this.failedQ)
|
|
13980
|
+
};
|
|
13981
|
+
}
|
|
13775
13982
|
/**
|
|
13776
13983
|
* Merge `hints` into the durable session hints via the pipeline's
|
|
13777
13984
|
* `mergeHints` (boosts multiply, require/exclude lists concat-dedup).
|
|
@@ -13853,9 +14060,13 @@ var SessionController = class _SessionController extends Loggable {
|
|
|
13853
14060
|
*/
|
|
13854
14061
|
async _replanUncoalesced(opts) {
|
|
13855
14062
|
const run = this._runReplan(opts);
|
|
13856
|
-
|
|
13857
|
-
if (this._replanPromise ===
|
|
14063
|
+
const tracked = run.finally(() => {
|
|
14064
|
+
if (this._replanPromise === tracked) {
|
|
14065
|
+
this._replanPromise = null;
|
|
14066
|
+
this._activeReplanLabel = null;
|
|
14067
|
+
}
|
|
13858
14068
|
});
|
|
14069
|
+
this._replanPromise = tracked;
|
|
13859
14070
|
await run;
|
|
13860
14071
|
}
|
|
13861
14072
|
/**
|
|
@@ -14211,14 +14422,14 @@ var SessionController = class _SessionController extends Loggable {
|
|
|
14211
14422
|
this.log(
|
|
14212
14423
|
`[AutoReplan:depletion] newQ has ${this.newQ.length} card(s) (${otherContent} in other queues) with ${this._secondsRemaining}s remaining. Triggering background replan.`
|
|
14213
14424
|
);
|
|
14214
|
-
void this.requestReplan();
|
|
14425
|
+
void this.requestReplan({ label: "auto:depletion" });
|
|
14215
14426
|
}
|
|
14216
14427
|
const REPLAN_BUFFER = 3;
|
|
14217
14428
|
if (!this._suppressQualityReplan && this._wellIndicatedRemaining <= REPLAN_BUFFER && this.newQ.length > 0 && !this._replanPromise) {
|
|
14218
14429
|
this.log(
|
|
14219
14430
|
`[AutoReplan:quality] ${this._wellIndicatedRemaining} well-indicated cards remaining (newQ: ${this.newQ.length}). Triggering background replan.`
|
|
14220
14431
|
);
|
|
14221
|
-
void this.requestReplan();
|
|
14432
|
+
void this.requestReplan({ label: "auto:quality" });
|
|
14222
14433
|
}
|
|
14223
14434
|
if (this._secondsRemaining <= 0 && this.failedQ.length === 0 && this._minCardsGuarantee <= 0) {
|
|
14224
14435
|
this._currentCard = null;
|