@vue-skuilder/db 0.2.7 → 0.2.9
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/{contentSource-Cplhv3bJ.d.ts → contentSource-C-0t0y0V.d.ts} +7 -0
- package/dist/{contentSource-kI9_jwTu.d.cts → contentSource-jSkcOt2s.d.cts} +7 -0
- package/dist/core/index.d.cts +67 -4
- package/dist/core/index.d.ts +67 -4
- package/dist/core/index.js +201 -39
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +198 -39
- package/dist/core/index.mjs.map +1 -1
- package/dist/{dataLayerProvider-DrBqOUa3.d.ts → dataLayerProvider-BB0oi9T0.d.ts} +1 -1
- package/dist/{dataLayerProvider-CiA2Rr0v.d.cts → dataLayerProvider-BDClIrFC.d.cts} +1 -1
- package/dist/impl/couch/index.d.cts +2 -2
- package/dist/impl/couch/index.d.ts +2 -2
- package/dist/impl/couch/index.js +195 -39
- package/dist/impl/couch/index.js.map +1 -1
- package/dist/impl/couch/index.mjs +195 -39
- package/dist/impl/couch/index.mjs.map +1 -1
- package/dist/impl/static/index.d.cts +2 -2
- package/dist/impl/static/index.d.ts +2 -2
- package/dist/impl/static/index.js +195 -39
- package/dist/impl/static/index.js.map +1 -1
- package/dist/impl/static/index.mjs +195 -39
- package/dist/impl/static/index.mjs.map +1 -1
- package/dist/index.d.cts +115 -81
- package/dist/index.d.ts +115 -81
- package/dist/index.js +440 -251
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +437 -251
- package/dist/index.mjs.map +1 -1
- package/docs/navigators-architecture.md +29 -13
- package/package.json +3 -3
- package/src/core/interfaces/contentSource.ts +7 -0
- package/src/core/navigators/Pipeline.ts +93 -1
- package/src/core/navigators/PipelineDebugger.ts +11 -1
- package/src/core/navigators/SrsDebugger.ts +53 -0
- package/src/core/navigators/generators/prescribed.ts +76 -9
- package/src/core/navigators/generators/srs.ts +81 -37
- package/src/core/navigators/index.ts +9 -0
- package/src/study/SessionController.ts +260 -249
- package/src/study/SessionDebugger.ts +15 -25
- package/src/study/SessionOverlay.ts +108 -13
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { logger } from '../util/logger';
|
|
2
2
|
import { clearRunHistory as clearPipelineRunHistory } from '../core/navigators/PipelineDebugger';
|
|
3
|
+
import { clearSrsBacklogDebug } from '../core/navigators/SrsDebugger';
|
|
3
4
|
import { toggleSessionOverlay } from './SessionOverlay';
|
|
4
5
|
|
|
5
6
|
// ============================================================================
|
|
@@ -23,11 +24,9 @@ import { toggleSessionOverlay } from './SessionOverlay';
|
|
|
23
24
|
*/
|
|
24
25
|
export interface QueueSnapshot {
|
|
25
26
|
timestamp: Date;
|
|
26
|
-
|
|
27
|
-
newQLength: number;
|
|
27
|
+
supplyQLength: number;
|
|
28
28
|
failedQLength: number;
|
|
29
|
-
|
|
30
|
-
newQNext3?: string[]; // cardIds of next 3 in newQ
|
|
29
|
+
supplyQNext3?: string[]; // cardIds of next 3 in supplyQ
|
|
31
30
|
}
|
|
32
31
|
|
|
33
32
|
/**
|
|
@@ -40,7 +39,7 @@ export interface CardPresentation {
|
|
|
40
39
|
courseId: string;
|
|
41
40
|
courseName?: string;
|
|
42
41
|
origin: 'review' | 'new' | 'failed';
|
|
43
|
-
queueSource: '
|
|
42
|
+
queueSource: 'supplyQ' | 'failedQ';
|
|
44
43
|
score?: number; // If available from weighted cards
|
|
45
44
|
}
|
|
46
45
|
|
|
@@ -73,8 +72,7 @@ const MAX_HISTORY = 5;
|
|
|
73
72
|
* Start tracking a new session.
|
|
74
73
|
*/
|
|
75
74
|
export function startSessionTracking(
|
|
76
|
-
|
|
77
|
-
newQLength: number,
|
|
75
|
+
supplyQLength: number,
|
|
78
76
|
failedQLength: number
|
|
79
77
|
): void {
|
|
80
78
|
// Release pipeline run-history memory before this session begins piling
|
|
@@ -83,6 +81,7 @@ export function startSessionTracking(
|
|
|
83
81
|
// dominant debugging workflow — fully functional: a finished session's
|
|
84
82
|
// run history sits intact until the user actually begins another one.
|
|
85
83
|
clearPipelineRunHistory();
|
|
84
|
+
clearSrsBacklogDebug();
|
|
86
85
|
|
|
87
86
|
const sessionId = `session-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
88
87
|
|
|
@@ -91,8 +90,7 @@ export function startSessionTracking(
|
|
|
91
90
|
startTime: new Date(),
|
|
92
91
|
initialQueues: {
|
|
93
92
|
timestamp: new Date(),
|
|
94
|
-
|
|
95
|
-
newQLength,
|
|
93
|
+
supplyQLength,
|
|
96
94
|
failedQLength,
|
|
97
95
|
},
|
|
98
96
|
presentations: [],
|
|
@@ -110,7 +108,7 @@ export function recordCardPresentation(
|
|
|
110
108
|
courseId: string,
|
|
111
109
|
courseName: string | undefined,
|
|
112
110
|
origin: 'review' | 'new' | 'failed',
|
|
113
|
-
queueSource: '
|
|
111
|
+
queueSource: 'supplyQ' | 'failedQ',
|
|
114
112
|
score?: number
|
|
115
113
|
): void {
|
|
116
114
|
if (!activeSession) {
|
|
@@ -134,11 +132,9 @@ export function recordCardPresentation(
|
|
|
134
132
|
* Take a snapshot of current queue state.
|
|
135
133
|
*/
|
|
136
134
|
export function snapshotQueues(
|
|
137
|
-
|
|
138
|
-
newQLength: number,
|
|
135
|
+
supplyQLength: number,
|
|
139
136
|
failedQLength: number,
|
|
140
|
-
|
|
141
|
-
newQNext3?: string[]
|
|
137
|
+
supplyQNext3?: string[]
|
|
142
138
|
): void {
|
|
143
139
|
if (!activeSession) {
|
|
144
140
|
return;
|
|
@@ -146,11 +142,9 @@ export function snapshotQueues(
|
|
|
146
142
|
|
|
147
143
|
activeSession.queueSnapshots.push({
|
|
148
144
|
timestamp: new Date(),
|
|
149
|
-
|
|
150
|
-
newQLength,
|
|
145
|
+
supplyQLength,
|
|
151
146
|
failedQLength,
|
|
152
|
-
|
|
153
|
-
newQNext3,
|
|
147
|
+
supplyQNext3,
|
|
154
148
|
});
|
|
155
149
|
}
|
|
156
150
|
|
|
@@ -191,13 +185,9 @@ function showCurrentQueue(): void {
|
|
|
191
185
|
|
|
192
186
|
// eslint-disable-next-line no-console
|
|
193
187
|
console.group('📊 Current Queue State');
|
|
194
|
-
logger.info(`
|
|
195
|
-
if (latest.
|
|
196
|
-
logger.info(` Next: ${latest.
|
|
197
|
-
}
|
|
198
|
-
logger.info(`New Queue: ${latest.newQLength} cards`);
|
|
199
|
-
if (latest.newQNext3 && latest.newQNext3.length > 0) {
|
|
200
|
-
logger.info(` Next: ${latest.newQNext3.join(', ')}`);
|
|
188
|
+
logger.info(`Supply Queue: ${latest.supplyQLength} cards`);
|
|
189
|
+
if (latest.supplyQNext3 && latest.supplyQNext3.length > 0) {
|
|
190
|
+
logger.info(` Next: ${latest.supplyQNext3.join(', ')}`);
|
|
201
191
|
}
|
|
202
192
|
logger.info(`Failed Queue: ${latest.failedQLength} cards`);
|
|
203
193
|
// eslint-disable-next-line no-console
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { logger } from '../util/logger';
|
|
2
2
|
import type { ReplanHints } from '@db/core/navigators/generators/types';
|
|
3
|
+
import type { SrsBacklogDebug } from '@db/core/navigators/SrsDebugger';
|
|
3
4
|
|
|
4
5
|
// ============================================================================
|
|
5
6
|
// SESSION OVERLAY
|
|
@@ -17,12 +18,23 @@ import type { ReplanHints } from '@db/core/navigators/generators/types';
|
|
|
17
18
|
//
|
|
18
19
|
// ============================================================================
|
|
19
20
|
|
|
20
|
-
/**
|
|
21
|
+
/** A single queued item, carrying the now-load-bearing rank score + origin. */
|
|
22
|
+
export interface SessionQueueItemDebug {
|
|
23
|
+
cardID: string;
|
|
24
|
+
/** Item status: 'new' | 'review' | 'failed-new' | 'failed-review'. */
|
|
25
|
+
status: string;
|
|
26
|
+
/** Card nature, collapsed from status: 'new' | 'review'. */
|
|
27
|
+
origin: 'new' | 'review';
|
|
28
|
+
/** Pipeline suitability score at queue-build time; `+INF` marks a required card. */
|
|
29
|
+
score?: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Per-queue debug view: total length, cumulative draws, and head-first items. */
|
|
21
33
|
export interface SessionQueueDebug {
|
|
22
34
|
length: number;
|
|
23
35
|
dequeueCount: number;
|
|
24
|
-
/**
|
|
25
|
-
cards:
|
|
36
|
+
/** Items in queue order, head (next draw) first. */
|
|
37
|
+
cards: SessionQueueItemDebug[];
|
|
26
38
|
}
|
|
27
39
|
|
|
28
40
|
/**
|
|
@@ -55,9 +67,11 @@ export interface SessionDebugSnapshot {
|
|
|
55
67
|
replanActive: boolean;
|
|
56
68
|
/** Reason for the in-flight replan (caller label, or '(auto)'); may be stale when idle. */
|
|
57
69
|
replanLabel: string | null;
|
|
58
|
-
|
|
59
|
-
|
|
70
|
+
/** The single rank-ordered supply (new + review interleaved), head first. */
|
|
71
|
+
supplyQ: SessionQueueDebug;
|
|
60
72
|
failedQ: SessionQueueDebug;
|
|
73
|
+
/** SRS backlog state per course (drives the "is review starvation permanent?" read). */
|
|
74
|
+
reviewBacklog: SrsBacklogDebug[];
|
|
61
75
|
/** Every card the learner has interacted with this session, draw order. */
|
|
62
76
|
drawnCards: SessionDrawnCardDebug[];
|
|
63
77
|
}
|
|
@@ -125,8 +139,7 @@ let minified = false;
|
|
|
125
139
|
|
|
126
140
|
/** Expansion state for collapsible (large) lists, preserved across re-renders. */
|
|
127
141
|
const expanded: Record<string, boolean> = {
|
|
128
|
-
|
|
129
|
-
newQ: false,
|
|
142
|
+
supplyQ: false,
|
|
130
143
|
failedQ: false,
|
|
131
144
|
drawn: false,
|
|
132
145
|
};
|
|
@@ -214,8 +227,8 @@ function render(): void {
|
|
|
214
227
|
replanHtml(s) +
|
|
215
228
|
metaHtml(s) +
|
|
216
229
|
hintsHtml(s.sessionHints) +
|
|
217
|
-
|
|
218
|
-
queueHtml('
|
|
230
|
+
backlogHtml(s.reviewBacklog) +
|
|
231
|
+
queueHtml('supplyQ', 'supplyQ', s.supplyQ) +
|
|
219
232
|
queueHtml('failedQ', 'failedQ', s.failedQ) +
|
|
220
233
|
drawnHtml('drawn', s.drawnCards);
|
|
221
234
|
|
|
@@ -350,6 +363,65 @@ function hintsHtml(h: ReplanHints | null): string {
|
|
|
350
363
|
);
|
|
351
364
|
}
|
|
352
365
|
|
|
366
|
+
/**
|
|
367
|
+
* SRS backlog panel — answers "is review starvation permanent?". Backlog
|
|
368
|
+
* pressure is a multiplier (×1.0 healthy → max) on review urgency; reviews are
|
|
369
|
+
* no longer clamped to 1.0, so under a heavy backlog they scale up to compete
|
|
370
|
+
* with (and exceed) new cards. So:
|
|
371
|
+
* - multiplier climbing, below max → reviews will rise as the backlog grows
|
|
372
|
+
* (temporary — they'll start winning slots);
|
|
373
|
+
* - multiplier maxed but still out-competed → the boosts in `sessionHints`
|
|
374
|
+
* simply sit higher; reviews only resurface once those relax (backoff), not
|
|
375
|
+
* from more backlog. Compare `top review` here against the supplyQ head score.
|
|
376
|
+
*/
|
|
377
|
+
function backlogHtml(backlog: SrsBacklogDebug[]): string {
|
|
378
|
+
if (!backlog.length) return '';
|
|
379
|
+
const rows = backlog
|
|
380
|
+
.map((b) => {
|
|
381
|
+
const maxed = b.backlogMultiplier >= b.maxBacklogMultiplier - 1e-9;
|
|
382
|
+
const multColor = b.backlogMultiplier <= 1 ? '#86efac' : maxed ? '#fca5a5' : '#fcd34d';
|
|
383
|
+
const headroom = maxed
|
|
384
|
+
? 'maxed — boosts decide order until they relax'
|
|
385
|
+
: b.backlogMultiplier > 1
|
|
386
|
+
? 'climbing as backlog grows'
|
|
387
|
+
: 'healthy — no pressure';
|
|
388
|
+
const top = b.topReviewScore !== null ? b.topReviewScore.toFixed(2) : '—';
|
|
389
|
+
const next = b.nextDueIn ? ` <span style="opacity:.6">· next due ${esc(b.nextDueIn)}</span>` : '';
|
|
390
|
+
return (
|
|
391
|
+
`<div style="margin-left:6px">` +
|
|
392
|
+
`<span style="opacity:.7">${esc(b.courseId.slice(0, 8))}</span> ` +
|
|
393
|
+
`due ${b.dueNow}/${b.scheduledTotal} <span style="opacity:.6">(healthy ${b.healthyBacklog})</span>${next}` +
|
|
394
|
+
`<div style="margin-left:6px">` +
|
|
395
|
+
`pressure <span style="color:${multColor}">×${b.backlogMultiplier.toFixed(2)}/${b.maxBacklogMultiplier.toFixed(2)}</span> ` +
|
|
396
|
+
`<span style="opacity:.6">${headroom} · top review ${top}</span></div>` +
|
|
397
|
+
`</div>`
|
|
398
|
+
);
|
|
399
|
+
})
|
|
400
|
+
.join('');
|
|
401
|
+
return (
|
|
402
|
+
`<div style="margin-bottom:6px">` +
|
|
403
|
+
`<div style="color:#93c5fd">review backpressure</div>${rows}</div>`
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/** Format a rank score for display: finite → 2dp, `+INF` → REQ (required). */
|
|
408
|
+
function fmtScore(score?: number): string {
|
|
409
|
+
if (score === undefined) return '';
|
|
410
|
+
if (!Number.isFinite(score)) return 'REQ';
|
|
411
|
+
return score.toFixed(2);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/** One supply/failed queue line: `cardID [r 0.82]` with origin-coloured tag. */
|
|
415
|
+
function queueItemHtml(item: SessionQueueItemDebug): string {
|
|
416
|
+
const tagColor = item.origin === 'review' ? '#93c5fd' : '#fcd34d';
|
|
417
|
+
const score = fmtScore(item.score);
|
|
418
|
+
const label = `${item.origin === 'review' ? 'r' : 'n'}${score ? ' ' + score : ''}`;
|
|
419
|
+
return (
|
|
420
|
+
`${esc(item.cardID)}` +
|
|
421
|
+
`<span style="color:${tagColor};opacity:.85"> [${label}]</span>`
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
|
|
353
425
|
function queueHtml(key: string, label: string, q: SessionQueueDebug): string {
|
|
354
426
|
const collapsible = q.length > INLINE_THRESHOLD;
|
|
355
427
|
const isOpen = collapsible && expanded[key];
|
|
@@ -371,7 +443,7 @@ function queueHtml(key: string, label: string, q: SessionQueueDebug): string {
|
|
|
371
443
|
|
|
372
444
|
let body =
|
|
373
445
|
`<ol style="margin:2px 0 ${listMarginBottom}px 0;padding-left:20px">` +
|
|
374
|
-
shown.map((c) => `<li style="white-space:nowrap">${
|
|
446
|
+
shown.map((c) => `<li style="white-space:nowrap">${queueItemHtml(c)}</li>`).join('') +
|
|
375
447
|
`</ol>`;
|
|
376
448
|
|
|
377
449
|
if (collapsible) {
|
|
@@ -465,13 +537,36 @@ function snapshotToText(s: SessionDebugSnapshot | null): string {
|
|
|
465
537
|
}
|
|
466
538
|
lines.push(hintParts.length ? hintParts.join('\n') : ' none');
|
|
467
539
|
|
|
540
|
+
if (s.reviewBacklog.length) {
|
|
541
|
+
lines.push('');
|
|
542
|
+
lines.push('review backpressure:');
|
|
543
|
+
for (const b of s.reviewBacklog) {
|
|
544
|
+
const maxed = b.backlogMultiplier >= b.maxBacklogMultiplier - 1e-9;
|
|
545
|
+
const headroom = maxed
|
|
546
|
+
? 'maxed (boosts decide order)'
|
|
547
|
+
: b.backlogMultiplier > 1
|
|
548
|
+
? 'climbing'
|
|
549
|
+
: 'healthy';
|
|
550
|
+
const top = b.topReviewScore !== null ? b.topReviewScore.toFixed(2) : '—';
|
|
551
|
+
const next = b.nextDueIn ? `, next due ${b.nextDueIn}` : '';
|
|
552
|
+
lines.push(
|
|
553
|
+
` ${b.courseId.slice(0, 8)}: due ${b.dueNow}/${b.scheduledTotal} ` +
|
|
554
|
+
`(healthy ${b.healthyBacklog})${next}; pressure ×${b.backlogMultiplier.toFixed(2)}/` +
|
|
555
|
+
`${b.maxBacklogMultiplier.toFixed(2)} ${headroom}; top review ${top}`
|
|
556
|
+
);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
468
560
|
const queueText = (label: string, q: SessionQueueDebug) => {
|
|
469
561
|
lines.push('');
|
|
470
562
|
lines.push(`${label}: ${q.length} (drawn ${q.dequeueCount})`);
|
|
471
|
-
q.cards.forEach((c, i) =>
|
|
563
|
+
q.cards.forEach((c, i) => {
|
|
564
|
+
const score = fmtScore(c.score);
|
|
565
|
+
const tag = `${c.origin === 'review' ? 'r' : 'n'}${score ? ' ' + score : ''}`;
|
|
566
|
+
lines.push(` ${i + 1}. ${c.cardID} [${tag}]`);
|
|
567
|
+
});
|
|
472
568
|
};
|
|
473
|
-
queueText('
|
|
474
|
-
queueText('newQ', s.newQ);
|
|
569
|
+
queueText('supplyQ', s.supplyQ);
|
|
475
570
|
queueText('failedQ', s.failedQ);
|
|
476
571
|
|
|
477
572
|
lines.push('');
|