@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.
Files changed (40) 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 +67 -4
  4. package/dist/core/index.d.ts +67 -4
  5. package/dist/core/index.js +201 -39
  6. package/dist/core/index.js.map +1 -1
  7. package/dist/core/index.mjs +198 -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 +195 -39
  14. package/dist/impl/couch/index.js.map +1 -1
  15. package/dist/impl/couch/index.mjs +195 -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 +195 -39
  20. package/dist/impl/static/index.js.map +1 -1
  21. package/dist/impl/static/index.mjs +195 -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 +440 -251
  26. package/dist/index.js.map +1 -1
  27. package/dist/index.mjs +437 -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/Pipeline.ts +93 -1
  33. package/src/core/navigators/PipelineDebugger.ts +11 -1
  34. package/src/core/navigators/SrsDebugger.ts +53 -0
  35. package/src/core/navigators/generators/prescribed.ts +76 -9
  36. package/src/core/navigators/generators/srs.ts +81 -37
  37. package/src/core/navigators/index.ts +9 -0
  38. package/src/study/SessionController.ts +260 -249
  39. package/src/study/SessionDebugger.ts +15 -25
  40. package/src/study/SessionOverlay.ts +108 -13
package/dist/index.d.ts CHANGED
@@ -1,10 +1,10 @@
1
- import { U as UserDBInterface, s as CourseRegistrationDoc, S as StudySessionItem, W as WeightedCard, R as ReplanHints, h as StudyContentSource, G as GeneratorResult, C as CourseDBInterface } from './contentSource-Cplhv3bJ.js';
2
- export { K as ActivityRecord, A as AdminDBInterface, v as AssignedCard, g as AssignedContent, u as AssignedCourse, t as AssignedTag, a4 as CardGenerator, a6 as CardGeneratorFactory, c as ClassroomDBInterface, F as ClassroomRegistration, E as ClassroomRegistrationDesignation, H as ClassroomRegistrationDoc, e as ContentNavigationStrategyData, f as ContentNavigator, q as ContentSourceID, d as CourseInfo, L as CourseRegistration, b as CoursesDBInterface, ad as DocumentUpdater, a5 as GeneratorContext, a7 as LearnableWeight, N as NavigatorConstructor, a0 as NavigatorRole, a1 as NavigatorRoles, $ as Navigators, a8 as OrchestrationContext, j as ScheduledCard, I as SessionTrackingData, Z as StrategyContribution, i as StudentClassroomDBInterface, k as StudySessionFailedItem, l as StudySessionFailedNewItem, m as StudySessionFailedReviewItem, n as StudySessionNewItem, o as StudySessionReviewItem, T as TeacherClassroomDBInterface, J as UserConfig, z as UserCourseSetting, y as UserCourseSettings, x as UserDBAuthenticator, a as UserDBReader, w as UserDBWriter, M as UserOutcomeRecord, B as UsrCrsDataInterface, a9 as computeDeviation, ab as computeEffectiveWeight, aa as computeSpread, ac as createOrchestrationContext, _ as getCardOrigin, P as getRegisteredNavigator, X as getRegisteredNavigatorNames, V as getRegisteredNavigatorRole, r as getStudySource, Q as hasRegisteredNavigator, Y as initializeNavigatorRegistry, a3 as isFilter, a2 as isGenerator, p as isReview, ae as newInterval, O as registerNavigator } from './contentSource-Cplhv3bJ.js';
3
- import { D as DataLayerProvider } from './dataLayerProvider-DrBqOUa3.js';
1
+ import { U as UserDBInterface, s as CourseRegistrationDoc, S as StudySessionItem, W as WeightedCard, R as ReplanHints, h as StudyContentSource, G as GeneratorResult, C as CourseDBInterface } from './contentSource-C-0t0y0V.js';
2
+ export { K as ActivityRecord, A as AdminDBInterface, v as AssignedCard, g as AssignedContent, u as AssignedCourse, t as AssignedTag, a4 as CardGenerator, a6 as CardGeneratorFactory, c as ClassroomDBInterface, F as ClassroomRegistration, E as ClassroomRegistrationDesignation, H as ClassroomRegistrationDoc, e as ContentNavigationStrategyData, f as ContentNavigator, q as ContentSourceID, d as CourseInfo, L as CourseRegistration, b as CoursesDBInterface, ad as DocumentUpdater, a5 as GeneratorContext, a7 as LearnableWeight, N as NavigatorConstructor, a0 as NavigatorRole, a1 as NavigatorRoles, $ as Navigators, a8 as OrchestrationContext, j as ScheduledCard, I as SessionTrackingData, Z as StrategyContribution, i as StudentClassroomDBInterface, k as StudySessionFailedItem, l as StudySessionFailedNewItem, m as StudySessionFailedReviewItem, n as StudySessionNewItem, o as StudySessionReviewItem, T as TeacherClassroomDBInterface, J as UserConfig, z as UserCourseSetting, y as UserCourseSettings, x as UserDBAuthenticator, a as UserDBReader, w as UserDBWriter, M as UserOutcomeRecord, B as UsrCrsDataInterface, a9 as computeDeviation, ab as computeEffectiveWeight, aa as computeSpread, ac as createOrchestrationContext, _ as getCardOrigin, P as getRegisteredNavigator, X as getRegisteredNavigatorNames, V as getRegisteredNavigatorRole, r as getStudySource, Q as hasRegisteredNavigator, Y as initializeNavigatorRegistry, a3 as isFilter, a2 as isGenerator, p as isReview, ae as newInterval, O as registerNavigator } from './contentSource-C-0t0y0V.js';
3
+ import { D as DataLayerProvider } from './dataLayerProvider-BB0oi9T0.js';
4
4
  import { C as CardHistory, c as CardRecord, d as QuestionRecord } from './types-legacy-4tlwHnXo.js';
5
5
  export { e as CardData, f as CourseListData, h as DataShapeData, g as DisplayableData, D as DocType, b as DocTypePrefixes, F as Field, G as GuestUsername, Q as QualifiedCardID, i as QuestionData, S as SkuilderCourseData, a as Tag, T as TagStub, l as log } from './types-legacy-4tlwHnXo.js';
6
- import { Loggable } from './core/index.js';
7
- export { BulkCardProcessorConfig, CardFilter, CardFilterFactory, DIVERSITY_FLOOR, DIVERSITY_STRENGTH, DiversityRerankOptions, FilterContext, FilterImpact, GeneratorSummary, GradientObservation, GradientResult, ImportResult, PeriodUpdateInput, PeriodUpdateResult, PipelineRunReport, SignalConfig, StrategyLearningState, StrategyStateDoc, StrategyStateId, aggregateOutcomesForGradient, areQuestionRecords, buildStrategyStateId, computeOutcomeSignal, computeStrategyGradient, diversityRerank, docIsDeleted, getCardHistoryID, getDefaultLearnableWeight, importParsedCards, isQuestionRecord, mountPipelineDebugger, mountUserDBDebugger, parseCardHistoryID, pipelineDebugAPI, recordUserOutcome, runPeriodUpdate, scoreAccuracyInZone, updateLearningState, updateStrategyWeight, userDBDebugAPI, validateProcessorConfig } from './core/index.js';
6
+ import { SrsBacklogDebug, Loggable } from './core/index.js';
7
+ export { BulkCardProcessorConfig, CardFilter, CardFilterFactory, DIVERSITY_FLOOR, DIVERSITY_STRENGTH, DiversityRerankOptions, FilterContext, FilterImpact, GeneratorSummary, GradientObservation, GradientResult, ImportResult, PeriodUpdateInput, PeriodUpdateResult, PipelineForecaster, PipelineRunReport, SignalConfig, StrategyLearningState, StrategyStateDoc, StrategyStateId, aggregateOutcomesForGradient, areQuestionRecords, buildStrategyStateId, clearSrsBacklogDebug, computeOutcomeSignal, computeStrategyGradient, diversityRerank, docIsDeleted, getActivePipeline, getCardHistoryID, getDefaultLearnableWeight, getSrsBacklogDebug, importParsedCards, isQuestionRecord, mountPipelineDebugger, mountUserDBDebugger, parseCardHistoryID, pipelineDebugAPI, recordUserOutcome, runPeriodUpdate, scoreAccuracyInZone, updateLearningState, updateStrategyWeight, userDBDebugAPI, validateProcessorConfig } from './core/index.js';
8
8
  import { TaggedPerformance, TagFilter, DataShape, CourseConfig } from '@vue-skuilder/common';
9
9
  import { S as StaticCourseManifest } from './types-CHgpWQAY.js';
10
10
  export { A as AttachmentData, C as ChunkMetadata, D as DesignDocument, I as IndexMetadata, a as PackedCourseData, P as PackerConfig } from './types-CHgpWQAY.js';
@@ -308,12 +308,22 @@ 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. */
311
+ /** A single queued item, carrying the now-load-bearing rank score + origin. */
312
+ interface SessionQueueItemDebug {
313
+ cardID: string;
314
+ /** Item status: 'new' | 'review' | 'failed-new' | 'failed-review'. */
315
+ status: string;
316
+ /** Card nature, collapsed from status: 'new' | 'review'. */
317
+ origin: 'new' | 'review';
318
+ /** Pipeline suitability score at queue-build time; `+INF` marks a required card. */
319
+ score?: number;
320
+ }
321
+ /** Per-queue debug view: total length, cumulative draws, and head-first items. */
312
322
  interface SessionQueueDebug {
313
323
  length: number;
314
324
  dequeueCount: number;
315
- /** cardIDs in queue order, head (next draw) first. */
316
- cards: string[];
325
+ /** Items in queue order, head (next draw) first. */
326
+ cards: SessionQueueItemDebug[];
317
327
  }
318
328
  /**
319
329
  * A card the learner has interacted with this session (one entry per card in
@@ -344,9 +354,11 @@ interface SessionDebugSnapshot {
344
354
  replanActive: boolean;
345
355
  /** Reason for the in-flight replan (caller label, or '(auto)'); may be stale when idle. */
346
356
  replanLabel: string | null;
347
- reviewQ: SessionQueueDebug;
348
- newQ: SessionQueueDebug;
357
+ /** The single rank-ordered supply (new + review interleaved), head first. */
358
+ supplyQ: SessionQueueDebug;
349
359
  failedQ: SessionQueueDebug;
360
+ /** SRS backlog state per course (drives the "is review starvation permanent?" read). */
361
+ reviewBacklog: SrsBacklogDebug[];
350
362
  /** Every card the learner has interacted with this session, draw order. */
351
363
  drawnCards: SessionDrawnCardDebug[];
352
364
  }
@@ -404,8 +416,8 @@ interface ReplanOptions {
404
416
  */
405
417
  limit?: number;
406
418
  /**
407
- * How to integrate the new cards into the existing newQ.
408
- * - `'replace'` (default): atomically swap the entire newQ.
419
+ * How to integrate the new cards into the existing supplyQ.
420
+ * - `'replace'` (default): atomically swap the entire supplyQ.
409
421
  * - `'merge'`: insert new cards at the front, keeping existing cards.
410
422
  */
411
423
  mode?: 'replace' | 'merge';
@@ -519,22 +531,31 @@ declare class SessionController<TView = unknown> extends Loggable {
519
531
  * Individual replans can override via `ReplanOptions.limit`.
520
532
  */
521
533
  private _defaultBatchLimit;
522
- /**
523
- * Maximum number of reviews enqueued at session start. Reviews live
524
- * outside the replan flow — the queue drains via consumption and is
525
- * not refilled mid-session. The session timer caps total review
526
- * exposure, so overfilling here is intentional. Default is generous
527
- * to accommodate Anki-style power users with hundreds of due reviews;
528
- * apps targeting nimbler sessions should override via constructor.
529
- */
530
- private _initialReviewCap;
531
534
  private sources;
532
535
  private _sessionRecord;
533
536
  set sessionRecord(r: StudySessionRecord[]);
534
537
  private _currentCard;
535
- private reviewQ;
536
- private newQ;
538
+ /**
539
+ * The single supply queue: `new` + `review` items interleaved in pipeline
540
+ * rank order (the mixer's score-ordered, source-interleaved output, with
541
+ * `+INF` required cards floated to the front). Drawn front-to-back; reviews
542
+ * and new compete on one cross-comparable scale rather than being re-mixed
543
+ * by a probability gate. Replaced/re-ranked wholesale on replan. See
544
+ * `docs/decision-single-supply-queue.md`.
545
+ */
546
+ private supplyQ;
537
547
  private failedQ;
548
+ /**
549
+ * Supply draws since the last failed-queue *event* (a failed draw, or a card
550
+ * entering failedQ on failure). Drives the light steady failed-interleave
551
+ * (§7): after this many consecutive supply draws, a pending failed card is
552
+ * drawn so remediation doesn't starve mid-session. Incremented on each supply
553
+ * draw; reset to 0 both when a failed card is drawn AND when one is added to
554
+ * failedQ — the latter gives a just-failed card spacing instead of an instant
555
+ * retry (the counter would otherwise already be ≥ threshold from the preceding
556
+ * supply run).
557
+ */
558
+ private _supplyDrawsSinceFailed;
538
559
  /**
539
560
  * Promise tracking a currently in-progress replan, or null if idle.
540
561
  * Used by nextCard() to await completion before drawing from queues.
@@ -548,8 +569,8 @@ declare class SessionController<TView = unknown> extends Loggable {
548
569
  */
549
570
  private _activeReplanLabel;
550
571
  /**
551
- * Number of well-indicated new cards remaining before the queue
552
- * degrades to poorly-indicated content. Decremented on each newQ
572
+ * Number of well-indicated supply cards remaining before the queue
573
+ * degrades to poorly-indicated content. Decremented on each supplyQ
553
574
  * draw; when it hits 0, a replan is triggered automatically
554
575
  * (user state has changed from completing good cards).
555
576
  */
@@ -558,7 +579,7 @@ declare class SessionController<TView = unknown> extends Loggable {
558
579
  * When true, suppresses the quality-based auto-replan trigger in
559
580
  * nextCard(). Set after a burst replan (small limit) to prevent the
560
581
  * auto-replan from clobbering the burst cards before they're consumed.
561
- * Cleared when the depletion-triggered replan fires (newQ exhausted).
582
+ * Cleared when the depletion-triggered replan fires (supplyQ exhausted).
562
583
  */
563
584
  private _suppressQualityReplan;
564
585
  /**
@@ -587,13 +608,15 @@ declare class SessionController<TView = unknown> extends Loggable {
587
608
  * a draw the instant it happens — earlier than `_sessionRecord`, which only
588
609
  * lands once the card is *responded to*.
589
610
  *
590
- * Used to keep already-served cards out of newQ on every (re)plan: a `new`
591
- * card shown once must never re-enter newQ this session. This is the general
592
- * guard against re-presentation including the case where a replan in flight
593
- * captured a now-drawn card (e.g. a +INF require-injected follow-up the
594
- * depletion prefetch grabbed just before it was drawn). Reviews/failed cards
595
- * legitimately recur and are tracked by their own queues, so this only gates
596
- * `new`-origin candidates.
611
+ * Used to keep already-served cards out of supplyQ on every (re)plan, across
612
+ * ALL origins: a `new` card shown once must never re-enter, and once replans
613
+ * re-pull reviews, an answered/in-flight review must not re-enter the supply
614
+ * before its SRS reschedule clears the due-window (the review-loop guard,
615
+ * decision doc §4). This is the general guard against re-presentation —
616
+ * including the case where a replan in flight captured a now-drawn card (e.g.
617
+ * a +INF require-injected follow-up the depletion prefetch grabbed just before
618
+ * it was drawn). failedQ is separate and controller-owned, so failed cards
619
+ * legitimately recur there without being gated here.
597
620
  */
598
621
  private _servedCardIds;
599
622
  /**
@@ -623,15 +646,12 @@ declare class SessionController<TView = unknown> extends Loggable {
623
646
  * @param getViewComponent - Function to resolve view components
624
647
  * @param mixer - Optional source mixer strategy (defaults to QuotaRoundRobinMixer)
625
648
  * @param options - Optional session-level configuration
626
- * @param options.defaultBatchLimit - Default pipeline batch size (default: 20).
649
+ * @param options.defaultBatchLimit - Default supply working-set size (default: 20).
627
650
  * Smaller values for newer users cause more frequent replans, keeping plans
628
651
  * aligned with rapidly-changing user state.
629
- * @param options.initialReviewCap - Max reviews loaded at session start (default: 200).
630
- * Applied only on initial planning; replans do not refill the review queue.
631
652
  */
632
653
  constructor(sources: StudyContentSource[], time: number, dataLayer: DataLayerProvider, getViewComponent: (viewId: string) => TView, mixer?: SourceMixer, options?: {
633
654
  defaultBatchLimit?: number;
634
- initialReviewCap?: number;
635
655
  outcomeObservers?: OutcomeObserver[];
636
656
  });
637
657
  private tick;
@@ -643,16 +663,11 @@ declare class SessionController<TView = unknown> extends Loggable {
643
663
  * (seconds)
644
664
  */
645
665
  private estimateCleanupTime;
646
- /**
647
- * Extremely rough, conservative, estimate of amound of time to complete
648
- * all scheduled reviews
649
- */
650
- private estimateReviewTime;
651
666
  prepareSession(): Promise<void>;
652
667
  /**
653
668
  * Request a mid-session replan. Re-runs the pipeline with current user state
654
- * and atomically replaces the newQ contents. Safe to call at any time during
655
- * a session.
669
+ * and atomically replaces (or merges into) the supplyQ contents. Safe to call
670
+ * at any time during a session.
656
671
  *
657
672
  * Concurrency policy:
658
673
  * - Two unhinted auto-replans never run in parallel; the second coalesces
@@ -666,7 +681,8 @@ declare class SessionController<TView = unknown> extends Loggable {
666
681
  * results (e.g. surfacing another gpc-intro card right after one
667
682
  * completed, skipping the prescribed `c-wst-*` follow-up).
668
683
  *
669
- * Does NOT affect reviewQ or failedQ.
684
+ * Re-pulls and re-ranks the whole supply (including reviews); does NOT affect
685
+ * failedQ (controller-owned remediation).
670
686
  *
671
687
  * If nextCard() is called while a replan is in flight, it will automatically
672
688
  * await the replan before drawing from queues, ensuring the user always sees
@@ -692,7 +708,7 @@ declare class SessionController<TView = unknown> extends Loggable {
692
708
  * excludeCards happen at *invocation* time, not at *queue* time. For a
693
709
  * queued replan that means excludes reflect the state after the prior
694
710
  * replan landed — which is what we want, since the prior replan's
695
- * newQ.peek(0) is the imminent draw we need to exclude.
711
+ * supplyQ.peek(0) is the imminent draw we need to exclude.
696
712
  */
697
713
  private _runReplan;
698
714
  /**
@@ -786,7 +802,7 @@ declare class SessionController<TView = unknown> extends Loggable {
786
802
  */
787
803
  private static readonly WELL_INDICATED_SCORE;
788
804
  /**
789
- * newQ length at or below which the opportunistic depletion-prefetch
805
+ * supplyQ length at or below which the opportunistic depletion-prefetch
790
806
  * fires. Sets the lead time available for the background replan to land
791
807
  * before the user actually empties the queue and falls into the
792
808
  * (synchronous) wedge-breaker path.
@@ -799,7 +815,7 @@ declare class SessionController<TView = unknown> extends Loggable {
799
815
  */
800
816
  private static readonly DEPLETION_PREFETCH_THRESHOLD;
801
817
  /**
802
- * Internal replan execution. Runs the pipeline, builds a new newQ,
818
+ * Internal replan execution. Runs the pipeline, rebuilds the supplyQ,
803
819
  * atomically swaps it in, and triggers hydration for the new contents.
804
820
  *
805
821
  * If the initial replan produces fewer than MIN_WELL_INDICATED cards that
@@ -820,22 +836,14 @@ declare class SessionController<TView = unknown> extends Loggable {
820
836
  mode: string;
821
837
  description: string;
822
838
  };
823
- reviewQueue: {
824
- length: number;
825
- dequeueCount: number;
826
- items: {
827
- courseID: any;
828
- cardID: any;
829
- status: any;
830
- }[];
831
- };
832
- newQueue: {
839
+ supplyQueue: {
833
840
  length: number;
834
841
  dequeueCount: number;
835
842
  items: {
836
843
  courseID: any;
837
844
  cardID: any;
838
845
  status: any;
846
+ score: any;
839
847
  }[];
840
848
  };
841
849
  failedQueue: {
@@ -845,6 +853,7 @@ declare class SessionController<TView = unknown> extends Loggable {
845
853
  courseID: any;
846
854
  cardID: any;
847
855
  status: any;
856
+ score: any;
848
857
  }[];
849
858
  };
850
859
  hydratedCache: {
@@ -859,26 +868,31 @@ declare class SessionController<TView = unknown> extends Loggable {
859
868
  };
860
869
  };
861
870
  /**
862
- * Fetch content using the getWeightedCards API and mix across sources.
871
+ * Fetch weighted content from all sources, mix across sources, and populate
872
+ * the single supply queue in pipeline rank order.
863
873
  *
864
- * This method:
865
- * 1. Fetches weighted cards from each source
866
- * 2. Fetches full review data (we need ScheduledCard fields for queue)
867
- * 3. Uses SourceMixer to balance content across sources
868
- * 4. Populates review and new card queues with mixed results
869
- */
870
- /**
871
- * Fetch weighted content from all sources and populate session queues.
874
+ * Reviews and new cards compete on one cross-comparable scale (SRS 0.5–1.0
875
+ * w/ backlog pressure vs ELO 0.0–1.0) there is no origin split and no
876
+ * second mixer. The working set is `supplyLimit` cards (the top of the mixed
877
+ * ranking, plus any `+INF` required cards floated to the front); replans
878
+ * re-pull and re-rank the whole supply, so a heavy review backlog surfaces as
879
+ * a refreshed top-ranked working set rather than a frozen 200-card snapshot.
872
880
  *
873
881
  * @param options.replan - If true, this is a mid-session replan rather than
874
- * initial session setup. Skips review queue population (avoiding duplicates),
875
- * atomically replaces newQ contents, and treats empty results as non-fatal.
876
- * @param options.additive - If true (replan only), merge new high-quality
877
- * candidates into the front of the existing newQ instead of replacing it.
882
+ * initial session setup. Atomically replaces supplyQ contents and treats
883
+ * empty results as non-fatal.
884
+ * @param options.additive - If true (replan only), merge high-quality
885
+ * candidates into the front of the existing supplyQ instead of replacing it.
878
886
  * @returns Number of "well-indicated" cards (passed all hierarchy filters)
879
887
  * in the new content. Returns -1 if no content was loaded.
880
888
  */
881
889
  private getWeightedContent;
890
+ /**
891
+ * Build a supply item from a weighted candidate. Review-origin cards carry
892
+ * their `reviewID` so SRS outcome tracking and re-presentation work; new
893
+ * cards do not. `score` is carried on both for the debug overlay.
894
+ */
895
+ private _buildSupplyItem;
882
896
  /**
883
897
  * Returns items that should be pre-hydrated.
884
898
  * Deterministic: top N items from each queue to ensure coverage.
@@ -887,9 +901,31 @@ declare class SessionController<TView = unknown> extends Loggable {
887
901
  private _getItemsToHydrate;
888
902
  /**
889
903
  * Selects the next item to present to the user.
890
- * Nondeterministic: uses probability to balance between queues based on session state.
904
+ *
905
+ * The supplyQ is already rank-ordered (the pipeline + mixer did the mixing,
906
+ * with `+INF` required cards floated to the front), so the primary path is a
907
+ * deterministic front-to-back draw — no second new-vs-review mixer. The only
908
+ * remaining decisions are (a) when the session ends and (b) when to interleave
909
+ * a remediation card from failedQ. See decision doc §2/§3/§7.
891
910
  */
892
911
  private _selectNextItemToHydrate;
912
+ /** Supply draws between forced failed-queue interleaves (light steady cadence). */
913
+ private static readonly FAILED_INTERLEAVE_EVERY;
914
+ /**
915
+ * Slack (seconds) below which the endgame failed-pressure kicks in: when the
916
+ * time left after clearing remediation drops under this, bias hard to failed
917
+ * so the session doesn't end with un-cleared remediation. Mirrors the old
918
+ * `availableTime > 20` ladder thresholds.
919
+ */
920
+ private static readonly FAILED_ENDGAME_SLACK_SECONDS;
921
+ /**
922
+ * Whether to interleave a failed (remediation) card now instead of drawing
923
+ * the supply head. Replaces the old `newBound`/`reviewBound` probability
924
+ * ladder's failed path (decision doc §7).
925
+ *
926
+ * @param supplyAvailable - whether supplyQ has a card to draw instead.
927
+ */
928
+ private _shouldInterleaveFailed;
893
929
  nextCard(action?: SessionAction): Promise<HydratedCard<TView> | null>;
894
930
  /**
895
931
  * Public API for processing user responses to cards.
@@ -1096,11 +1132,9 @@ declare function mountMixerDebugger(): void;
1096
1132
  */
1097
1133
  interface QueueSnapshot {
1098
1134
  timestamp: Date;
1099
- reviewQLength: number;
1100
- newQLength: number;
1135
+ supplyQLength: number;
1101
1136
  failedQLength: number;
1102
- reviewQNext3?: string[];
1103
- newQNext3?: string[];
1137
+ supplyQNext3?: string[];
1104
1138
  }
1105
1139
  /**
1106
1140
  * Record of a single card presentation.
@@ -1112,7 +1146,7 @@ interface CardPresentation {
1112
1146
  courseId: string;
1113
1147
  courseName?: string;
1114
1148
  origin: 'review' | 'new' | 'failed';
1115
- queueSource: 'reviewQ' | 'newQ' | 'failedQ';
1149
+ queueSource: 'supplyQ' | 'failedQ';
1116
1150
  score?: number;
1117
1151
  }
1118
1152
  /**
@@ -1129,15 +1163,15 @@ interface SessionRunReport {
1129
1163
  /**
1130
1164
  * Start tracking a new session.
1131
1165
  */
1132
- declare function startSessionTracking(reviewQLength: number, newQLength: number, failedQLength: number): void;
1166
+ declare function startSessionTracking(supplyQLength: number, failedQLength: number): void;
1133
1167
  /**
1134
1168
  * Record a card presentation.
1135
1169
  */
1136
- declare function recordCardPresentation(cardId: string, courseId: string, courseName: string | undefined, origin: 'review' | 'new' | 'failed', queueSource: 'reviewQ' | 'newQ' | 'failedQ', score?: number): void;
1170
+ declare function recordCardPresentation(cardId: string, courseId: string, courseName: string | undefined, origin: 'review' | 'new' | 'failed', queueSource: 'supplyQ' | 'failedQ', score?: number): void;
1137
1171
  /**
1138
1172
  * Take a snapshot of current queue state.
1139
1173
  */
1140
- declare function snapshotQueues(reviewQLength: number, newQLength: number, failedQLength: number, reviewQNext3?: string[], newQNext3?: string[]): void;
1174
+ declare function snapshotQueues(supplyQLength: number, failedQLength: number, supplyQNext3?: string[]): void;
1141
1175
  /**
1142
1176
  * End the current session tracking.
1143
1177
  */
@@ -1433,4 +1467,4 @@ interface CouchDbUserDoc extends PouchDB.Authentication.User {
1433
1467
  entitlements: UserEntitlements;
1434
1468
  }
1435
1469
 
1436
- export { type AggregatedDocument, type AttachmentUploadResult, CardHistory, type CardPresentation, CardRecord, type CouchDbUserDoc, CourseDBInterface, CourseLookup, CourseRegistrationDoc, type CustomQuestionsData, DEFAULT_MIGRATION_OPTIONS, type DataLayerConfig, DataLayerProvider, type DocumentCounts, ENV, type Entitlement, FileSystemAdapter, GeneratorResult, Loggable, type MigrationOptions, type MigrationResult, type MixerCardInfo, type MixerRunReport, NOT_SET, type OutcomeObserver, type ProcessedDataShape, type ProcessedQuestionData, QuestionRecord, type QueueSnapshot, QuotaRoundRobinMixer, ReplanHints, type ReplanOptions, type ResponseResult, type RestoreProgress, type SessionAction, SessionController, type SessionControls, type SessionOutcome, type SessionRunReport, type SourceBatch, type SourceMixer, type SourceSelectionBreakdown, type SourceSummary, StaticCourseManifest, type StaticCourseValidation, StaticToCouchDBMigrator, StudyContentSource, StudySessionItem, type StudySessionRecord, TagFilteredContentSource, type UserAccountStatus, UserDBInterface, type UserEntitlements, type ValidationIssue, type ValidationResult, WeightedCard, _resetDataLayer, captureMixerRun, endSessionTracking, ensureAppDataDirectory, getAppDataDirectory, getDataLayer, getDbPath, initializeDataDirectory, initializeDataLayer, isDataShapeRegistered, isDataShapeSchemaAvailable, isQuestionTypeRegistered, mixerDebugAPI, mountMixerDebugger, mountSessionDebugger, processCustomQuestionsData, recordCardPresentation, registerBlanksCard, registerCustomQuestionTypes, registerDataShape, registerQuestionType, registerSeedData, removeCustomQuestionTypes, removeDataShape, removeQuestionType, sessionDebugAPI, snapshotQueues, startSessionTracking, validateMigration, validateStaticCourse };
1470
+ export { type AggregatedDocument, type AttachmentUploadResult, CardHistory, type CardPresentation, CardRecord, type CouchDbUserDoc, CourseDBInterface, CourseLookup, CourseRegistrationDoc, type CustomQuestionsData, DEFAULT_MIGRATION_OPTIONS, type DataLayerConfig, DataLayerProvider, type DocumentCounts, ENV, type Entitlement, FileSystemAdapter, GeneratorResult, Loggable, type MigrationOptions, type MigrationResult, type MixerCardInfo, type MixerRunReport, NOT_SET, type OutcomeObserver, type ProcessedDataShape, type ProcessedQuestionData, QuestionRecord, type QueueSnapshot, QuotaRoundRobinMixer, ReplanHints, type ReplanOptions, type ResponseResult, type RestoreProgress, type SessionAction, SessionController, type SessionControls, type SessionOutcome, type SessionRunReport, type SourceBatch, type SourceMixer, type SourceSelectionBreakdown, type SourceSummary, SrsBacklogDebug, StaticCourseManifest, type StaticCourseValidation, StaticToCouchDBMigrator, StudyContentSource, StudySessionItem, type StudySessionRecord, TagFilteredContentSource, type UserAccountStatus, UserDBInterface, type UserEntitlements, type ValidationIssue, type ValidationResult, WeightedCard, _resetDataLayer, captureMixerRun, endSessionTracking, ensureAppDataDirectory, getAppDataDirectory, getDataLayer, getDbPath, initializeDataDirectory, initializeDataLayer, isDataShapeRegistered, isDataShapeSchemaAvailable, isQuestionTypeRegistered, mixerDebugAPI, mountMixerDebugger, mountSessionDebugger, processCustomQuestionsData, recordCardPresentation, registerBlanksCard, registerCustomQuestionTypes, registerDataShape, registerQuestionType, registerSeedData, removeCustomQuestionTypes, removeDataShape, removeQuestionType, sessionDebugAPI, snapshotQueues, startSessionTracking, validateMigration, validateStaticCourse };