@vue-skuilder/db 0.1.18 → 0.1.21

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 (87) hide show
  1. package/CLAUDE.md +2 -2
  2. package/dist/{classroomDB-BgfrVb8d.d.ts → contentSource-BP9hznNV.d.ts} +220 -197
  3. package/dist/{classroomDB-CTOenngH.d.cts → contentSource-DsJadoBU.d.cts} +220 -197
  4. package/dist/core/index.d.cts +80 -6
  5. package/dist/core/index.d.ts +80 -6
  6. package/dist/core/index.js +735 -1560
  7. package/dist/core/index.js.map +1 -1
  8. package/dist/core/index.mjs +708 -1539
  9. package/dist/core/index.mjs.map +1 -1
  10. package/dist/{dataLayerProvider-D6PoCwS6.d.cts → dataLayerProvider-CHYrQ5pB.d.cts} +1 -1
  11. package/dist/{dataLayerProvider-CZxC9GtB.d.ts → dataLayerProvider-MDTxXq2l.d.ts} +1 -1
  12. package/dist/impl/couch/index.d.cts +8 -23
  13. package/dist/impl/couch/index.d.ts +8 -23
  14. package/dist/impl/couch/index.js +723 -1578
  15. package/dist/impl/couch/index.js.map +1 -1
  16. package/dist/impl/couch/index.mjs +692 -1552
  17. package/dist/impl/couch/index.mjs.map +1 -1
  18. package/dist/impl/static/index.d.cts +25 -8
  19. package/dist/impl/static/index.d.ts +25 -8
  20. package/dist/impl/static/index.js +700 -1400
  21. package/dist/impl/static/index.js.map +1 -1
  22. package/dist/impl/static/index.mjs +688 -1393
  23. package/dist/impl/static/index.mjs.map +1 -1
  24. package/dist/{index-D-Fa4Smt.d.cts → index-B_j6u5E4.d.cts} +1 -1
  25. package/dist/{index-CD8BZz2k.d.ts → index-Dj0SEgk3.d.ts} +1 -1
  26. package/dist/index.d.cts +71 -63
  27. package/dist/index.d.ts +71 -63
  28. package/dist/index.js +1162 -1996
  29. package/dist/index.js.map +1 -1
  30. package/dist/index.mjs +1124 -1955
  31. package/dist/index.mjs.map +1 -1
  32. package/dist/pouch/index.js +3 -0
  33. package/dist/pouch/index.js.map +1 -1
  34. package/dist/pouch/index.mjs +3 -0
  35. package/dist/pouch/index.mjs.map +1 -1
  36. package/dist/{types-CzPDLAK6.d.cts → types-Bn0itutr.d.cts} +1 -1
  37. package/dist/{types-CewsN87z.d.ts → types-DQaXnuoc.d.ts} +1 -1
  38. package/dist/{types-legacy-6ettoclI.d.cts → types-legacy-DDY4N-Uq.d.cts} +3 -1
  39. package/dist/{types-legacy-6ettoclI.d.ts → types-legacy-DDY4N-Uq.d.ts} +3 -1
  40. package/dist/util/packer/index.d.cts +3 -3
  41. package/dist/util/packer/index.d.ts +3 -3
  42. package/docs/navigators-architecture.md +115 -17
  43. package/package.json +4 -4
  44. package/src/core/index.ts +1 -0
  45. package/src/core/interfaces/classroomDB.ts +5 -13
  46. package/src/core/interfaces/contentSource.ts +6 -66
  47. package/src/core/interfaces/courseDB.ts +15 -7
  48. package/src/core/interfaces/userDB.ts +32 -0
  49. package/src/core/navigators/Pipeline.ts +136 -52
  50. package/src/core/navigators/PipelineAssembler.ts +1 -1
  51. package/src/core/navigators/defaults.ts +84 -0
  52. package/src/core/navigators/{hierarchyDefinition.ts → filters/hierarchyDefinition.ts} +15 -29
  53. package/src/core/navigators/filters/index.ts +3 -0
  54. package/src/core/navigators/filters/inferredPreferenceStub.ts +107 -0
  55. package/src/core/navigators/{interferenceMitigator.ts → filters/interferenceMitigator.ts} +11 -37
  56. package/src/core/navigators/{relativePriority.ts → filters/relativePriority.ts} +12 -38
  57. package/src/core/navigators/filters/userGoalStub.ts +136 -0
  58. package/src/core/navigators/filters/userTagPreference.ts +217 -0
  59. package/src/core/navigators/{CompositeGenerator.ts → generators/CompositeGenerator.ts} +15 -64
  60. package/src/core/navigators/{elo.ts → generators/elo.ts} +13 -63
  61. package/src/core/navigators/{srs.ts → generators/srs.ts} +11 -40
  62. package/src/core/navigators/generators/types.ts +1 -1
  63. package/src/core/navigators/index.ts +95 -91
  64. package/src/core/types/strategyState.ts +84 -0
  65. package/src/core/types/types-legacy.ts +2 -0
  66. package/src/impl/common/BaseUserDB.ts +74 -7
  67. package/src/impl/couch/adminDB.ts +1 -2
  68. package/src/impl/couch/classroomDB.ts +100 -103
  69. package/src/impl/couch/courseDB.ts +35 -91
  70. package/src/impl/couch/pouchdb-setup.ts +7 -0
  71. package/src/impl/static/StaticDataUnpacker.ts +50 -1
  72. package/src/impl/static/courseDB.ts +87 -37
  73. package/src/study/SessionController.ts +122 -202
  74. package/src/study/SourceMixer.ts +65 -0
  75. package/src/study/TagFilteredContentSource.ts +49 -92
  76. package/src/study/index.ts +1 -0
  77. package/src/study/services/CardHydrationService.ts +165 -81
  78. package/src/util/dataDirectory.ts +1 -1
  79. package/src/util/index.ts +0 -1
  80. package/tests/core/navigators/CompositeGenerator.test.ts +44 -168
  81. package/tests/core/navigators/Pipeline.test.ts +6 -72
  82. package/tests/core/navigators/PipelineAssembler.test.ts +8 -58
  83. package/tests/core/navigators/navigators.test.ts +118 -151
  84. package/docs/todo-pipeline-optimization.md +0 -117
  85. package/docs/todo-strategy-state-storage.md +0 -278
  86. package/src/core/navigators/hardcodedOrder.ts +0 -163
  87. package/src/util/tuiLogger.ts +0 -139
@@ -1,4 +1,4 @@
1
- import { P as PackerConfig, a as PackedCourseData, S as StaticCourseManifest } from './types-CzPDLAK6.cjs';
1
+ import { P as PackerConfig, a as PackedCourseData, S as StaticCourseManifest } from './types-Bn0itutr.cjs';
2
2
 
3
3
  /**
4
4
  * Abstraction for file system operations needed by the migrator.
@@ -1,4 +1,4 @@
1
- import { P as PackerConfig, a as PackedCourseData, S as StaticCourseManifest } from './types-CewsN87z.js';
1
+ import { P as PackerConfig, a as PackedCourseData, S as StaticCourseManifest } from './types-DQaXnuoc.js';
2
2
 
3
3
  /**
4
4
  * Abstraction for file system operations needed by the migrator.
package/dist/index.d.cts CHANGED
@@ -1,15 +1,15 @@
1
- import { U as UserDBInterface, s as CourseRegistrationDoc, l as StudySessionItem, i as StudyContentSource, S as StudySessionNewItem, f as StudySessionReviewItem, g as ScheduledCard, W as WeightedCard } from './classroomDB-CTOenngH.cjs';
2
- export { J as ActivityRecord, A as AdminDBInterface, v as AssignedCard, h as AssignedContent, u as AssignedCourse, t as AssignedTag, c as ClassroomDBInterface, F as ClassroomRegistration, E as ClassroomRegistrationDesignation, G as ClassroomRegistrationDoc, k as ContentNavigator, q as ContentSourceID, C as CourseDBInterface, d as CourseInfo, K as CourseRegistration, b as CoursesDBInterface, V as DocumentUpdater, O as NavigatorRole, P as NavigatorRoles, N as Navigators, H as SessionTrackingData, L as StrategyContribution, j as StudentClassroomDBInterface, m as StudySessionFailedItem, n as StudySessionFailedNewItem, o as StudySessionFailedReviewItem, T as TeacherClassroomDBInterface, I as UserConfig, z as UserCourseSetting, y as UserCourseSettings, x as UserDBAuthenticator, a as UserDBReader, w as UserDBWriter, B as UsrCrsDataInterface, M as getCardOrigin, r as getStudySource, R as isFilter, Q as isGenerator, p as isReview, X as newInterval } from './classroomDB-CTOenngH.cjs';
3
- import { D as DataLayerProvider } from './dataLayerProvider-D6PoCwS6.cjs';
4
- import { C as CardHistory, c as CardRecord } from './types-legacy-6ettoclI.cjs';
5
- export { d as CardData, e as CourseListData, g as DataShapeData, f as DisplayableData, D as DocType, b as DocTypePrefixes, F as Field, G as GuestUsername, Q as QualifiedCardID, h as QuestionData, i as QuestionRecord, S as SkuilderCourseData, a as Tag, T as TagStub, l as log } from './types-legacy-6ettoclI.cjs';
1
+ import { U as UserDBInterface, s as CourseRegistrationDoc, S as StudySessionItem, W as WeightedCard, h as StudyContentSource } from './contentSource-DsJadoBU.cjs';
2
+ export { J as ActivityRecord, A as AdminDBInterface, v as AssignedCard, g as AssignedContent, u as AssignedCourse, t as AssignedTag, c as ClassroomDBInterface, F as ClassroomRegistration, E as ClassroomRegistrationDesignation, G as ClassroomRegistrationDoc, f as ContentNavigator, q as ContentSourceID, C as CourseDBInterface, d as CourseInfo, K as CourseRegistration, b as CoursesDBInterface, V as DocumentUpdater, O as NavigatorRole, P as NavigatorRoles, N as Navigators, j as ScheduledCard, H as SessionTrackingData, L as StrategyContribution, i as StudentClassroomDBInterface, k as StudySessionFailedItem, l as StudySessionFailedNewItem, m as StudySessionFailedReviewItem, n as StudySessionNewItem, o as StudySessionReviewItem, T as TeacherClassroomDBInterface, I as UserConfig, z as UserCourseSetting, y as UserCourseSettings, x as UserDBAuthenticator, a as UserDBReader, w as UserDBWriter, B as UsrCrsDataInterface, M as getCardOrigin, r as getStudySource, R as isFilter, Q as isGenerator, p as isReview, X as newInterval } from './contentSource-DsJadoBU.cjs';
3
+ import { D as DataLayerProvider } from './dataLayerProvider-CHYrQ5pB.cjs';
4
+ import { C as CardHistory, c as CardRecord } from './types-legacy-DDY4N-Uq.cjs';
5
+ export { d as CardData, e as CourseListData, g as DataShapeData, f as DisplayableData, D as DocType, b as DocTypePrefixes, F as Field, G as GuestUsername, Q as QualifiedCardID, h as QuestionData, i as QuestionRecord, S as SkuilderCourseData, a as Tag, T as TagStub, l as log } from './types-legacy-DDY4N-Uq.cjs';
6
6
  import { Loggable } from './core/index.cjs';
7
- export { BulkCardProcessorConfig, CardFilter, CardFilterFactory, CardGenerator, CardGeneratorFactory, FilterContext, GeneratorContext, ImportResult, areQuestionRecords, docIsDeleted, getCardHistoryID, importParsedCards, isQuestionRecord, parseCardHistoryID, validateProcessorConfig } from './core/index.cjs';
7
+ export { BulkCardProcessorConfig, CardFilter, CardFilterFactory, CardGenerator, CardGeneratorFactory, FilterContext, GeneratorContext, ImportResult, StrategyStateDoc, StrategyStateId, areQuestionRecords, buildStrategyStateId, docIsDeleted, getCardHistoryID, importParsedCards, isQuestionRecord, parseCardHistoryID, validateProcessorConfig } from './core/index.cjs';
8
8
  import { TagFilter } from '@vue-skuilder/common';
9
- import { S as StaticCourseManifest } from './types-CzPDLAK6.cjs';
10
- export { A as AttachmentData, C as ChunkMetadata, D as DesignDocument, I as IndexMetadata, a as PackedCourseData, P as PackerConfig } from './types-CzPDLAK6.cjs';
11
- import { F as FileSystemAdapter } from './index-D-Fa4Smt.cjs';
12
- export { C as CouchDBToStaticPacker, a as FileStats, b as FileSystemError } from './index-D-Fa4Smt.cjs';
9
+ import { S as StaticCourseManifest } from './types-Bn0itutr.cjs';
10
+ export { A as AttachmentData, C as ChunkMetadata, D as DesignDocument, I as IndexMetadata, a as PackedCourseData, P as PackerConfig } from './types-Bn0itutr.cjs';
11
+ import { F as FileSystemAdapter } from './index-B_j6u5E4.cjs';
12
+ export { C as CouchDBToStaticPacker, a as FileStats, b as FileSystemError } from './index-B_j6u5E4.cjs';
13
13
  import 'moment';
14
14
 
15
15
  /**
@@ -242,30 +242,47 @@ declare function getDbPath(dbName: string): string;
242
242
  declare function initializeDataDirectory(): Promise<void>;
243
243
 
244
244
  /**
245
- * Initialize TUI logging - redirect console logs to file in Node.js
245
+ * Represents a batch of content fetched from a single StudyContentSource.
246
246
  */
247
- declare function initializeTuiLogging(): void;
248
- /**
249
- * Get the current log file path (for debugging)
250
- */
251
- declare function getLogFilePath(): string | null;
252
- /**
253
- * Show user-facing message (always visible in TUI)
254
- */
255
- declare function showUserMessage(message: string): void;
247
+ interface SourceBatch {
248
+ sourceIndex: number;
249
+ weighted: WeightedCard[];
250
+ }
256
251
  /**
257
- * Show user-facing error (always visible in TUI)
252
+ * Strategy interface for mixing content from multiple sources into a unified
253
+ * set of weighted candidates.
254
+ *
255
+ * Different implementations can provide different balancing strategies:
256
+ * - QuotaRoundRobinMixer: Equal representation per source
257
+ * - MinMaxNormalizingMixer: Score normalization then global sort
258
+ * - PercentileBucketMixer: Bucketed round-robin
259
+ * etc.
258
260
  */
259
- declare function showUserError(message: string): void;
261
+ interface SourceMixer {
262
+ /**
263
+ * Mix weighted cards from multiple sources into a unified, ordered list.
264
+ *
265
+ * @param batches - Content batches from each source
266
+ * @param limit - Target number of cards to return
267
+ * @returns Mixed and ordered weighted cards
268
+ */
269
+ mix(batches: SourceBatch[], limit: number): WeightedCard[];
270
+ }
260
271
  /**
261
- * Logger object with standard log levels
272
+ * Simple quota-based mixer: allocates equal representation to each source,
273
+ * taking the top-N cards by score from each.
274
+ *
275
+ * Guarantees balanced representation across sources regardless of absolute
276
+ * score differences. A low-scoring source gets the same quota as a high-scoring
277
+ * source.
278
+ *
279
+ * This is the KISS approach - simple, predictable, and fair in terms of
280
+ * source representation (though not necessarily optimal in terms of absolute
281
+ * card quality).
262
282
  */
263
- declare const logger: {
264
- debug: (message: string, ...args: any[]) => void;
265
- info: (message: string, ...args: any[]) => void;
266
- warn: (message: string, ...args: any[]) => void;
267
- error: (message: string, ...args: any[]) => void;
268
- };
283
+ declare class QuotaRoundRobinMixer implements SourceMixer {
284
+ mix(batches: SourceBatch[], limit: number): WeightedCard[];
285
+ }
269
286
 
270
287
  interface StudySessionRecord {
271
288
  card: {
@@ -294,6 +311,7 @@ declare class SessionController<TView = unknown> extends Loggable {
294
311
  private srsService;
295
312
  private eloService;
296
313
  private hydrationService;
314
+ private mixer;
297
315
  private sources;
298
316
  private _sessionRecord;
299
317
  set sessionRecord(r: StudySessionRecord[]);
@@ -309,9 +327,13 @@ declare class SessionController<TView = unknown> extends Loggable {
309
327
  get detailedReport(): string;
310
328
  private _intervalHandle;
311
329
  /**
312
- *
330
+ * @param sources - Array of content sources to mix for the session
331
+ * @param time - Session duration in seconds
332
+ * @param dataLayer - Data layer provider
333
+ * @param getViewComponent - Function to resolve view components
334
+ * @param mixer - Optional source mixer strategy (defaults to QuotaRoundRobinMixer)
313
335
  */
314
- constructor(sources: StudyContentSource[], time: number, dataLayer: DataLayerProvider, getViewComponent: (viewId: string) => TView);
336
+ constructor(sources: StudyContentSource[], time: number, dataLayer: DataLayerProvider, getViewComponent: (viewId: string) => TView, mixer?: SourceMixer);
315
337
  private tick;
316
338
  /**
317
339
  * Returns a rough, erring toward conservative, guess at
@@ -369,34 +391,29 @@ declare class SessionController<TView = unknown> extends Loggable {
369
391
  };
370
392
  hydratedCache: {
371
393
  count: number;
372
- failedCacheSize: number;
373
- items: any[];
394
+ cardIds: string[];
374
395
  };
375
396
  };
376
397
  /**
377
- * Fetch content using the new getWeightedCards API.
378
- *
379
- * This method uses getWeightedCards() to get scored candidates, then uses the
380
- * scores to determine ordering. For reviews, we still need the full ScheduledCard
381
- * data from getPendingReviews(), so we fetch both and use scores for ordering.
398
+ * Fetch content using the getWeightedCards API and mix across sources.
382
399
  *
383
- * The hybrid approach:
384
- * 1. Fetch weighted cards to get scoring/ordering information
385
- * 2. Fetch full review data via legacy getPendingReviews()
386
- * 3. Order reviews by their weighted scores
387
- * 4. Add new cards ordered by their weighted scores
400
+ * This method:
401
+ * 1. Fetches weighted cards from each source
402
+ * 2. Fetches full review data (we need ScheduledCard fields for queue)
403
+ * 3. Uses SourceMixer to balance content across sources
404
+ * 4. Populates review and new card queues with mixed results
388
405
  */
389
406
  private getWeightedContent;
390
407
  /**
391
- * @deprecated Use getWeightedContent() instead. This method is kept for backward
392
- * compatibility with sources that don't support getWeightedCards().
408
+ * Returns items that should be pre-hydrated.
409
+ * Deterministic: top N items from each queue to ensure coverage.
410
+ * Failed queue items will typically already be hydrated (from initial render).
393
411
  */
394
- private getScheduledReviews;
412
+ private _getItemsToHydrate;
395
413
  /**
396
- * @deprecated Use getWeightedContent() instead. This method is kept for backward
397
- * compatibility with sources that don't support getWeightedCards().
414
+ * Selects the next item to present to the user.
415
+ * Nondeterministic: uses probability to balance between queues based on session state.
398
416
  */
399
- private getNewCards;
400
417
  private _selectNextItemToHydrate;
401
418
  nextCard(action?: SessionAction): Promise<HydratedCard<TView> | null>;
402
419
  /**
@@ -414,9 +431,8 @@ declare class SessionController<TView = unknown> extends Loggable {
414
431
  */
415
432
  submitResponse(cardRecord: CardRecord, cardHistory: Promise<CardHistory<CardRecord>>, courseRegistrationDoc: CourseRegistrationDoc, currentCard: StudySessionRecord, courseId: string, cardId: string, maxAttemptsPerView: number, maxSessionViews: number, sessionViews: number): Promise<ResponseResult>;
416
433
  private dismissCurrentCard;
417
- private hasAvailableCards;
418
434
  /**
419
- * Helper method for CardHydrationService to remove items from appropriate queue.
435
+ * Remove an item from its source queue after consumption by nextCard().
420
436
  */
421
437
  private removeItemFromQueue;
422
438
  }
@@ -445,20 +461,12 @@ declare class TagFilteredContentSource implements StudyContentSource {
445
461
  * - Cards in `exclude` tags are removed from the result
446
462
  */
447
463
  private resolveFilteredCardIds;
448
- /**
449
- * Gets new cards that match the tag filter and are not already active for the user.
450
- */
451
- getNewCards(limit?: number): Promise<StudySessionNewItem[]>;
452
- /**
453
- * Gets pending reviews, filtered to only include cards that match the tag filter.
454
- */
455
- getPendingReviews(): Promise<(StudySessionReviewItem & ScheduledCard)[]>;
456
464
  /**
457
465
  * Get cards with suitability scores for presentation.
458
466
  *
459
- * This implementation wraps the legacy getNewCards/getPendingReviews methods,
460
- * assigning score=1.0 to all cards. TagFilteredContentSource does not currently
461
- * support pluggable navigation strategies - it returns flat-scored candidates.
467
+ * Filters cards by tag inclusion/exclusion and assigns score=1.0 to all.
468
+ * TagFilteredContentSource does not currently support pluggable navigation
469
+ * strategies - it returns flat-scored candidates.
462
470
  *
463
471
  * @param limit - Maximum number of cards to return
464
472
  * @returns Cards sorted by score descending (all scores = 1.0)
@@ -577,4 +585,4 @@ interface CouchDbUserDoc extends PouchDB.Authentication.User {
577
585
  entitlements: UserEntitlements;
578
586
  }
579
587
 
580
- export { type AggregatedDocument, type AttachmentUploadResult, CardHistory, CardRecord, type CouchDbUserDoc, CourseLookup, CourseRegistrationDoc, DEFAULT_MIGRATION_OPTIONS, type DataLayerConfig, DataLayerProvider, type DocumentCounts, ENV, type Entitlement, FileSystemAdapter, Loggable, type MigrationOptions, type MigrationResult, NOT_SET, type ResponseResult, type RestoreProgress, ScheduledCard, type SessionAction, SessionController, StaticCourseManifest, type StaticCourseValidation, StaticToCouchDBMigrator, StudyContentSource, StudySessionItem, StudySessionNewItem, type StudySessionRecord, StudySessionReviewItem, TagFilteredContentSource, type UserAccountStatus, UserDBInterface, type UserEntitlements, type ValidationIssue, type ValidationResult, WeightedCard, _resetDataLayer, ensureAppDataDirectory, getAppDataDirectory, getDataLayer, getDbPath, getLogFilePath, initializeDataDirectory, initializeDataLayer, initializeTuiLogging, logger, showUserError, showUserMessage, validateMigration, validateStaticCourse };
588
+ export { type AggregatedDocument, type AttachmentUploadResult, CardHistory, CardRecord, type CouchDbUserDoc, CourseLookup, CourseRegistrationDoc, DEFAULT_MIGRATION_OPTIONS, type DataLayerConfig, DataLayerProvider, type DocumentCounts, ENV, type Entitlement, FileSystemAdapter, Loggable, type MigrationOptions, type MigrationResult, NOT_SET, QuotaRoundRobinMixer, type ResponseResult, type RestoreProgress, type SessionAction, SessionController, type SourceBatch, type SourceMixer, StaticCourseManifest, type StaticCourseValidation, StaticToCouchDBMigrator, StudyContentSource, StudySessionItem, type StudySessionRecord, TagFilteredContentSource, type UserAccountStatus, UserDBInterface, type UserEntitlements, type ValidationIssue, type ValidationResult, WeightedCard, _resetDataLayer, ensureAppDataDirectory, getAppDataDirectory, getDataLayer, getDbPath, initializeDataDirectory, initializeDataLayer, validateMigration, validateStaticCourse };
package/dist/index.d.ts CHANGED
@@ -1,15 +1,15 @@
1
- import { U as UserDBInterface, s as CourseRegistrationDoc, l as StudySessionItem, i as StudyContentSource, S as StudySessionNewItem, f as StudySessionReviewItem, g as ScheduledCard, W as WeightedCard } from './classroomDB-BgfrVb8d.js';
2
- export { J as ActivityRecord, A as AdminDBInterface, v as AssignedCard, h as AssignedContent, u as AssignedCourse, t as AssignedTag, c as ClassroomDBInterface, F as ClassroomRegistration, E as ClassroomRegistrationDesignation, G as ClassroomRegistrationDoc, k as ContentNavigator, q as ContentSourceID, C as CourseDBInterface, d as CourseInfo, K as CourseRegistration, b as CoursesDBInterface, V as DocumentUpdater, O as NavigatorRole, P as NavigatorRoles, N as Navigators, H as SessionTrackingData, L as StrategyContribution, j as StudentClassroomDBInterface, m as StudySessionFailedItem, n as StudySessionFailedNewItem, o as StudySessionFailedReviewItem, T as TeacherClassroomDBInterface, I as UserConfig, z as UserCourseSetting, y as UserCourseSettings, x as UserDBAuthenticator, a as UserDBReader, w as UserDBWriter, B as UsrCrsDataInterface, M as getCardOrigin, r as getStudySource, R as isFilter, Q as isGenerator, p as isReview, X as newInterval } from './classroomDB-BgfrVb8d.js';
3
- import { D as DataLayerProvider } from './dataLayerProvider-CZxC9GtB.js';
4
- import { C as CardHistory, c as CardRecord } from './types-legacy-6ettoclI.js';
5
- export { d as CardData, e as CourseListData, g as DataShapeData, f as DisplayableData, D as DocType, b as DocTypePrefixes, F as Field, G as GuestUsername, Q as QualifiedCardID, h as QuestionData, i as QuestionRecord, S as SkuilderCourseData, a as Tag, T as TagStub, l as log } from './types-legacy-6ettoclI.js';
1
+ import { U as UserDBInterface, s as CourseRegistrationDoc, S as StudySessionItem, W as WeightedCard, h as StudyContentSource } from './contentSource-BP9hznNV.js';
2
+ export { J as ActivityRecord, A as AdminDBInterface, v as AssignedCard, g as AssignedContent, u as AssignedCourse, t as AssignedTag, c as ClassroomDBInterface, F as ClassroomRegistration, E as ClassroomRegistrationDesignation, G as ClassroomRegistrationDoc, f as ContentNavigator, q as ContentSourceID, C as CourseDBInterface, d as CourseInfo, K as CourseRegistration, b as CoursesDBInterface, V as DocumentUpdater, O as NavigatorRole, P as NavigatorRoles, N as Navigators, j as ScheduledCard, H as SessionTrackingData, L as StrategyContribution, i as StudentClassroomDBInterface, k as StudySessionFailedItem, l as StudySessionFailedNewItem, m as StudySessionFailedReviewItem, n as StudySessionNewItem, o as StudySessionReviewItem, T as TeacherClassroomDBInterface, I as UserConfig, z as UserCourseSetting, y as UserCourseSettings, x as UserDBAuthenticator, a as UserDBReader, w as UserDBWriter, B as UsrCrsDataInterface, M as getCardOrigin, r as getStudySource, R as isFilter, Q as isGenerator, p as isReview, X as newInterval } from './contentSource-BP9hznNV.js';
3
+ import { D as DataLayerProvider } from './dataLayerProvider-MDTxXq2l.js';
4
+ import { C as CardHistory, c as CardRecord } from './types-legacy-DDY4N-Uq.js';
5
+ export { d as CardData, e as CourseListData, g as DataShapeData, f as DisplayableData, D as DocType, b as DocTypePrefixes, F as Field, G as GuestUsername, Q as QualifiedCardID, h as QuestionData, i as QuestionRecord, S as SkuilderCourseData, a as Tag, T as TagStub, l as log } from './types-legacy-DDY4N-Uq.js';
6
6
  import { Loggable } from './core/index.js';
7
- export { BulkCardProcessorConfig, CardFilter, CardFilterFactory, CardGenerator, CardGeneratorFactory, FilterContext, GeneratorContext, ImportResult, areQuestionRecords, docIsDeleted, getCardHistoryID, importParsedCards, isQuestionRecord, parseCardHistoryID, validateProcessorConfig } from './core/index.js';
7
+ export { BulkCardProcessorConfig, CardFilter, CardFilterFactory, CardGenerator, CardGeneratorFactory, FilterContext, GeneratorContext, ImportResult, StrategyStateDoc, StrategyStateId, areQuestionRecords, buildStrategyStateId, docIsDeleted, getCardHistoryID, importParsedCards, isQuestionRecord, parseCardHistoryID, validateProcessorConfig } from './core/index.js';
8
8
  import { TagFilter } from '@vue-skuilder/common';
9
- import { S as StaticCourseManifest } from './types-CewsN87z.js';
10
- export { A as AttachmentData, C as ChunkMetadata, D as DesignDocument, I as IndexMetadata, a as PackedCourseData, P as PackerConfig } from './types-CewsN87z.js';
11
- import { F as FileSystemAdapter } from './index-CD8BZz2k.js';
12
- export { C as CouchDBToStaticPacker, a as FileStats, b as FileSystemError } from './index-CD8BZz2k.js';
9
+ import { S as StaticCourseManifest } from './types-DQaXnuoc.js';
10
+ export { A as AttachmentData, C as ChunkMetadata, D as DesignDocument, I as IndexMetadata, a as PackedCourseData, P as PackerConfig } from './types-DQaXnuoc.js';
11
+ import { F as FileSystemAdapter } from './index-Dj0SEgk3.js';
12
+ export { C as CouchDBToStaticPacker, a as FileStats, b as FileSystemError } from './index-Dj0SEgk3.js';
13
13
  import 'moment';
14
14
 
15
15
  /**
@@ -242,30 +242,47 @@ declare function getDbPath(dbName: string): string;
242
242
  declare function initializeDataDirectory(): Promise<void>;
243
243
 
244
244
  /**
245
- * Initialize TUI logging - redirect console logs to file in Node.js
245
+ * Represents a batch of content fetched from a single StudyContentSource.
246
246
  */
247
- declare function initializeTuiLogging(): void;
248
- /**
249
- * Get the current log file path (for debugging)
250
- */
251
- declare function getLogFilePath(): string | null;
252
- /**
253
- * Show user-facing message (always visible in TUI)
254
- */
255
- declare function showUserMessage(message: string): void;
247
+ interface SourceBatch {
248
+ sourceIndex: number;
249
+ weighted: WeightedCard[];
250
+ }
256
251
  /**
257
- * Show user-facing error (always visible in TUI)
252
+ * Strategy interface for mixing content from multiple sources into a unified
253
+ * set of weighted candidates.
254
+ *
255
+ * Different implementations can provide different balancing strategies:
256
+ * - QuotaRoundRobinMixer: Equal representation per source
257
+ * - MinMaxNormalizingMixer: Score normalization then global sort
258
+ * - PercentileBucketMixer: Bucketed round-robin
259
+ * etc.
258
260
  */
259
- declare function showUserError(message: string): void;
261
+ interface SourceMixer {
262
+ /**
263
+ * Mix weighted cards from multiple sources into a unified, ordered list.
264
+ *
265
+ * @param batches - Content batches from each source
266
+ * @param limit - Target number of cards to return
267
+ * @returns Mixed and ordered weighted cards
268
+ */
269
+ mix(batches: SourceBatch[], limit: number): WeightedCard[];
270
+ }
260
271
  /**
261
- * Logger object with standard log levels
272
+ * Simple quota-based mixer: allocates equal representation to each source,
273
+ * taking the top-N cards by score from each.
274
+ *
275
+ * Guarantees balanced representation across sources regardless of absolute
276
+ * score differences. A low-scoring source gets the same quota as a high-scoring
277
+ * source.
278
+ *
279
+ * This is the KISS approach - simple, predictable, and fair in terms of
280
+ * source representation (though not necessarily optimal in terms of absolute
281
+ * card quality).
262
282
  */
263
- declare const logger: {
264
- debug: (message: string, ...args: any[]) => void;
265
- info: (message: string, ...args: any[]) => void;
266
- warn: (message: string, ...args: any[]) => void;
267
- error: (message: string, ...args: any[]) => void;
268
- };
283
+ declare class QuotaRoundRobinMixer implements SourceMixer {
284
+ mix(batches: SourceBatch[], limit: number): WeightedCard[];
285
+ }
269
286
 
270
287
  interface StudySessionRecord {
271
288
  card: {
@@ -294,6 +311,7 @@ declare class SessionController<TView = unknown> extends Loggable {
294
311
  private srsService;
295
312
  private eloService;
296
313
  private hydrationService;
314
+ private mixer;
297
315
  private sources;
298
316
  private _sessionRecord;
299
317
  set sessionRecord(r: StudySessionRecord[]);
@@ -309,9 +327,13 @@ declare class SessionController<TView = unknown> extends Loggable {
309
327
  get detailedReport(): string;
310
328
  private _intervalHandle;
311
329
  /**
312
- *
330
+ * @param sources - Array of content sources to mix for the session
331
+ * @param time - Session duration in seconds
332
+ * @param dataLayer - Data layer provider
333
+ * @param getViewComponent - Function to resolve view components
334
+ * @param mixer - Optional source mixer strategy (defaults to QuotaRoundRobinMixer)
313
335
  */
314
- constructor(sources: StudyContentSource[], time: number, dataLayer: DataLayerProvider, getViewComponent: (viewId: string) => TView);
336
+ constructor(sources: StudyContentSource[], time: number, dataLayer: DataLayerProvider, getViewComponent: (viewId: string) => TView, mixer?: SourceMixer);
315
337
  private tick;
316
338
  /**
317
339
  * Returns a rough, erring toward conservative, guess at
@@ -369,34 +391,29 @@ declare class SessionController<TView = unknown> extends Loggable {
369
391
  };
370
392
  hydratedCache: {
371
393
  count: number;
372
- failedCacheSize: number;
373
- items: any[];
394
+ cardIds: string[];
374
395
  };
375
396
  };
376
397
  /**
377
- * Fetch content using the new getWeightedCards API.
378
- *
379
- * This method uses getWeightedCards() to get scored candidates, then uses the
380
- * scores to determine ordering. For reviews, we still need the full ScheduledCard
381
- * data from getPendingReviews(), so we fetch both and use scores for ordering.
398
+ * Fetch content using the getWeightedCards API and mix across sources.
382
399
  *
383
- * The hybrid approach:
384
- * 1. Fetch weighted cards to get scoring/ordering information
385
- * 2. Fetch full review data via legacy getPendingReviews()
386
- * 3. Order reviews by their weighted scores
387
- * 4. Add new cards ordered by their weighted scores
400
+ * This method:
401
+ * 1. Fetches weighted cards from each source
402
+ * 2. Fetches full review data (we need ScheduledCard fields for queue)
403
+ * 3. Uses SourceMixer to balance content across sources
404
+ * 4. Populates review and new card queues with mixed results
388
405
  */
389
406
  private getWeightedContent;
390
407
  /**
391
- * @deprecated Use getWeightedContent() instead. This method is kept for backward
392
- * compatibility with sources that don't support getWeightedCards().
408
+ * Returns items that should be pre-hydrated.
409
+ * Deterministic: top N items from each queue to ensure coverage.
410
+ * Failed queue items will typically already be hydrated (from initial render).
393
411
  */
394
- private getScheduledReviews;
412
+ private _getItemsToHydrate;
395
413
  /**
396
- * @deprecated Use getWeightedContent() instead. This method is kept for backward
397
- * compatibility with sources that don't support getWeightedCards().
414
+ * Selects the next item to present to the user.
415
+ * Nondeterministic: uses probability to balance between queues based on session state.
398
416
  */
399
- private getNewCards;
400
417
  private _selectNextItemToHydrate;
401
418
  nextCard(action?: SessionAction): Promise<HydratedCard<TView> | null>;
402
419
  /**
@@ -414,9 +431,8 @@ declare class SessionController<TView = unknown> extends Loggable {
414
431
  */
415
432
  submitResponse(cardRecord: CardRecord, cardHistory: Promise<CardHistory<CardRecord>>, courseRegistrationDoc: CourseRegistrationDoc, currentCard: StudySessionRecord, courseId: string, cardId: string, maxAttemptsPerView: number, maxSessionViews: number, sessionViews: number): Promise<ResponseResult>;
416
433
  private dismissCurrentCard;
417
- private hasAvailableCards;
418
434
  /**
419
- * Helper method for CardHydrationService to remove items from appropriate queue.
435
+ * Remove an item from its source queue after consumption by nextCard().
420
436
  */
421
437
  private removeItemFromQueue;
422
438
  }
@@ -445,20 +461,12 @@ declare class TagFilteredContentSource implements StudyContentSource {
445
461
  * - Cards in `exclude` tags are removed from the result
446
462
  */
447
463
  private resolveFilteredCardIds;
448
- /**
449
- * Gets new cards that match the tag filter and are not already active for the user.
450
- */
451
- getNewCards(limit?: number): Promise<StudySessionNewItem[]>;
452
- /**
453
- * Gets pending reviews, filtered to only include cards that match the tag filter.
454
- */
455
- getPendingReviews(): Promise<(StudySessionReviewItem & ScheduledCard)[]>;
456
464
  /**
457
465
  * Get cards with suitability scores for presentation.
458
466
  *
459
- * This implementation wraps the legacy getNewCards/getPendingReviews methods,
460
- * assigning score=1.0 to all cards. TagFilteredContentSource does not currently
461
- * support pluggable navigation strategies - it returns flat-scored candidates.
467
+ * Filters cards by tag inclusion/exclusion and assigns score=1.0 to all.
468
+ * TagFilteredContentSource does not currently support pluggable navigation
469
+ * strategies - it returns flat-scored candidates.
462
470
  *
463
471
  * @param limit - Maximum number of cards to return
464
472
  * @returns Cards sorted by score descending (all scores = 1.0)
@@ -577,4 +585,4 @@ interface CouchDbUserDoc extends PouchDB.Authentication.User {
577
585
  entitlements: UserEntitlements;
578
586
  }
579
587
 
580
- export { type AggregatedDocument, type AttachmentUploadResult, CardHistory, CardRecord, type CouchDbUserDoc, CourseLookup, CourseRegistrationDoc, DEFAULT_MIGRATION_OPTIONS, type DataLayerConfig, DataLayerProvider, type DocumentCounts, ENV, type Entitlement, FileSystemAdapter, Loggable, type MigrationOptions, type MigrationResult, NOT_SET, type ResponseResult, type RestoreProgress, ScheduledCard, type SessionAction, SessionController, StaticCourseManifest, type StaticCourseValidation, StaticToCouchDBMigrator, StudyContentSource, StudySessionItem, StudySessionNewItem, type StudySessionRecord, StudySessionReviewItem, TagFilteredContentSource, type UserAccountStatus, UserDBInterface, type UserEntitlements, type ValidationIssue, type ValidationResult, WeightedCard, _resetDataLayer, ensureAppDataDirectory, getAppDataDirectory, getDataLayer, getDbPath, getLogFilePath, initializeDataDirectory, initializeDataLayer, initializeTuiLogging, logger, showUserError, showUserMessage, validateMigration, validateStaticCourse };
588
+ export { type AggregatedDocument, type AttachmentUploadResult, CardHistory, CardRecord, type CouchDbUserDoc, CourseLookup, CourseRegistrationDoc, DEFAULT_MIGRATION_OPTIONS, type DataLayerConfig, DataLayerProvider, type DocumentCounts, ENV, type Entitlement, FileSystemAdapter, Loggable, type MigrationOptions, type MigrationResult, NOT_SET, QuotaRoundRobinMixer, type ResponseResult, type RestoreProgress, type SessionAction, SessionController, type SourceBatch, type SourceMixer, StaticCourseManifest, type StaticCourseValidation, StaticToCouchDBMigrator, StudyContentSource, StudySessionItem, type StudySessionRecord, TagFilteredContentSource, type UserAccountStatus, UserDBInterface, type UserEntitlements, type ValidationIssue, type ValidationResult, WeightedCard, _resetDataLayer, ensureAppDataDirectory, getAppDataDirectory, getDataLayer, getDbPath, initializeDataDirectory, initializeDataLayer, validateMigration, validateStaticCourse };