@vue-skuilder/db 0.2.8 → 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.
Files changed (38) hide show
  1. package/dist/{contentSource-Cplhv3bJ.d.ts → contentSource-C-0t0y0V.d.ts} +7 -0
  2. package/dist/{contentSource-kI9_jwTu.d.cts → contentSource-jSkcOt2s.d.cts} +7 -0
  3. package/dist/core/index.d.cts +29 -4
  4. package/dist/core/index.d.ts +29 -4
  5. package/dist/core/index.js +132 -39
  6. package/dist/core/index.js.map +1 -1
  7. package/dist/core/index.mjs +130 -39
  8. package/dist/core/index.mjs.map +1 -1
  9. package/dist/{dataLayerProvider-DrBqOUa3.d.ts → dataLayerProvider-BB0oi9T0.d.ts} +1 -1
  10. package/dist/{dataLayerProvider-CiA2Rr0v.d.cts → dataLayerProvider-BDClIrFC.d.cts} +1 -1
  11. package/dist/impl/couch/index.d.cts +2 -2
  12. package/dist/impl/couch/index.d.ts +2 -2
  13. package/dist/impl/couch/index.js +128 -39
  14. package/dist/impl/couch/index.js.map +1 -1
  15. package/dist/impl/couch/index.mjs +128 -39
  16. package/dist/impl/couch/index.mjs.map +1 -1
  17. package/dist/impl/static/index.d.cts +2 -2
  18. package/dist/impl/static/index.d.ts +2 -2
  19. package/dist/impl/static/index.js +128 -39
  20. package/dist/impl/static/index.js.map +1 -1
  21. package/dist/impl/static/index.mjs +128 -39
  22. package/dist/impl/static/index.mjs.map +1 -1
  23. package/dist/index.d.cts +115 -81
  24. package/dist/index.d.ts +115 -81
  25. package/dist/index.js +371 -251
  26. package/dist/index.js.map +1 -1
  27. package/dist/index.mjs +369 -251
  28. package/dist/index.mjs.map +1 -1
  29. package/docs/navigators-architecture.md +29 -13
  30. package/package.json +3 -3
  31. package/src/core/interfaces/contentSource.ts +7 -0
  32. package/src/core/navigators/SrsDebugger.ts +53 -0
  33. package/src/core/navigators/generators/prescribed.ts +76 -9
  34. package/src/core/navigators/generators/srs.ts +81 -37
  35. package/src/core/navigators/index.ts +5 -0
  36. package/src/study/SessionController.ts +260 -249
  37. package/src/study/SessionDebugger.ts +15 -25
  38. 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
- reviewQLength: number;
27
- newQLength: number;
27
+ supplyQLength: number;
28
28
  failedQLength: number;
29
- reviewQNext3?: string[]; // cardIds of next 3 in reviewQ
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: 'reviewQ' | 'newQ' | 'failedQ';
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
- reviewQLength: number,
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
- reviewQLength,
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: 'reviewQ' | 'newQ' | 'failedQ',
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
- reviewQLength: number,
138
- newQLength: number,
135
+ supplyQLength: number,
139
136
  failedQLength: number,
140
- reviewQNext3?: string[],
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
- reviewQLength,
150
- newQLength,
145
+ supplyQLength,
151
146
  failedQLength,
152
- reviewQNext3,
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(`Review Queue: ${latest.reviewQLength} cards`);
195
- if (latest.reviewQNext3 && latest.reviewQNext3.length > 0) {
196
- logger.info(` Next: ${latest.reviewQNext3.join(', ')}`);
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
- /** Per-queue debug view: total length, cumulative draws, and head-first cardIDs. */
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
- /** cardIDs in queue order, head (next draw) first. */
25
- cards: string[];
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
- reviewQ: SessionQueueDebug;
59
- newQ: SessionQueueDebug;
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
- reviewQ: false,
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
- queueHtml('reviewQ', 'reviewQ', s.reviewQ) +
218
- queueHtml('newQ', 'newQ', s.newQ) +
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">${esc(c)}</li>`).join('') +
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) => lines.push(` ${i + 1}. ${c}`));
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('reviewQ', s.reviewQ);
474
- queueText('newQ', s.newQ);
569
+ queueText('supplyQ', s.supplyQ);
475
570
  queueText('failedQ', s.failedQ);
476
571
 
477
572
  lines.push('');