@vue-skuilder/db 0.1.20 → 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 (70) hide show
  1. package/CLAUDE.md +2 -2
  2. package/dist/{classroomDB-CZdMBiTU.d.ts → contentSource-BP9hznNV.d.ts} +150 -196
  3. package/dist/{classroomDB-PxDZTky3.d.cts → contentSource-DsJadoBU.d.cts} +150 -196
  4. package/dist/core/index.d.cts +3 -3
  5. package/dist/core/index.d.ts +3 -3
  6. package/dist/core/index.js +615 -1758
  7. package/dist/core/index.js.map +1 -1
  8. package/dist/core/index.mjs +579 -1727
  9. package/dist/core/index.mjs.map +1 -1
  10. package/dist/{dataLayerProvider-D8o6ZnKW.d.ts → dataLayerProvider-CHYrQ5pB.d.cts} +1 -1
  11. package/dist/{dataLayerProvider-D0MoZMjH.d.cts → dataLayerProvider-MDTxXq2l.d.ts} +1 -1
  12. package/dist/impl/couch/index.d.cts +6 -22
  13. package/dist/impl/couch/index.d.ts +6 -22
  14. package/dist/impl/couch/index.js +598 -1769
  15. package/dist/impl/couch/index.js.map +1 -1
  16. package/dist/impl/couch/index.mjs +579 -1755
  17. package/dist/impl/couch/index.mjs.map +1 -1
  18. package/dist/impl/static/index.d.cts +22 -6
  19. package/dist/impl/static/index.d.ts +22 -6
  20. package/dist/impl/static/index.js +617 -1629
  21. package/dist/impl/static/index.js.map +1 -1
  22. package/dist/impl/static/index.mjs +607 -1624
  23. package/dist/impl/static/index.mjs.map +1 -1
  24. package/dist/index.d.cts +64 -56
  25. package/dist/index.d.ts +64 -56
  26. package/dist/index.js +1000 -2161
  27. package/dist/index.js.map +1 -1
  28. package/dist/index.mjs +970 -2127
  29. package/dist/index.mjs.map +1 -1
  30. package/dist/pouch/index.js +3 -0
  31. package/dist/pouch/index.js.map +1 -1
  32. package/dist/pouch/index.mjs +3 -0
  33. package/dist/pouch/index.mjs.map +1 -1
  34. package/docs/navigators-architecture.md +2 -9
  35. package/package.json +3 -3
  36. package/src/core/interfaces/classroomDB.ts +5 -13
  37. package/src/core/interfaces/contentSource.ts +6 -66
  38. package/src/core/interfaces/courseDB.ts +2 -7
  39. package/src/core/navigators/Pipeline.ts +24 -53
  40. package/src/core/navigators/PipelineAssembler.ts +1 -1
  41. package/src/core/navigators/defaults.ts +84 -0
  42. package/src/core/navigators/{hierarchyDefinition.ts → filters/hierarchyDefinition.ts} +11 -25
  43. package/src/core/navigators/{interferenceMitigator.ts → filters/interferenceMitigator.ts} +10 -24
  44. package/src/core/navigators/{relativePriority.ts → filters/relativePriority.ts} +10 -24
  45. package/src/core/navigators/filters/userTagPreference.ts +1 -16
  46. package/src/core/navigators/{CompositeGenerator.ts → generators/CompositeGenerator.ts} +15 -64
  47. package/src/core/navigators/{elo.ts → generators/elo.ts} +13 -63
  48. package/src/core/navigators/{srs.ts → generators/srs.ts} +11 -40
  49. package/src/core/navigators/generators/types.ts +1 -1
  50. package/src/core/navigators/index.ts +36 -91
  51. package/src/impl/couch/classroomDB.ts +100 -103
  52. package/src/impl/couch/courseDB.ts +5 -81
  53. package/src/impl/couch/pouchdb-setup.ts +7 -0
  54. package/src/impl/static/StaticDataUnpacker.ts +50 -1
  55. package/src/impl/static/courseDB.ts +76 -37
  56. package/src/study/SessionController.ts +122 -202
  57. package/src/study/SourceMixer.ts +65 -0
  58. package/src/study/TagFilteredContentSource.ts +49 -92
  59. package/src/study/index.ts +1 -0
  60. package/src/study/services/CardHydrationService.ts +165 -81
  61. package/src/util/dataDirectory.ts +1 -1
  62. package/src/util/index.ts +0 -1
  63. package/tests/core/navigators/CompositeGenerator.test.ts +44 -168
  64. package/tests/core/navigators/Pipeline.test.ts +5 -72
  65. package/tests/core/navigators/PipelineAssembler.test.ts +8 -58
  66. package/tests/core/navigators/navigators.test.ts +118 -151
  67. package/src/core/navigators/hardcodedOrder.ts +0 -163
  68. package/src/util/tuiLogger.ts +0 -139
  69. /package/src/core/navigators/{inferredPreference.ts → filters/inferredPreferenceStub.ts} +0 -0
  70. /package/src/core/navigators/{userGoal.ts → filters/userGoalStub.ts} +0 -0
@@ -40,6 +40,9 @@ var import_pouchdb_find = __toESM(require("pouchdb-find"), 1);
40
40
  var import_pouchdb_authentication = __toESM(require("@nilock2/pouchdb-authentication"), 1);
41
41
  import_pouchdb.default.plugin(import_pouchdb_find.default);
42
42
  import_pouchdb.default.plugin(import_pouchdb_authentication.default);
43
+ if (typeof import_pouchdb.default.debug !== "undefined") {
44
+ import_pouchdb.default.debug.disable();
45
+ }
43
46
  import_pouchdb.default.defaults({
44
47
  // ajax: {
45
48
  // timeout: 60000,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/pouch/index.ts","../../src/impl/couch/pouchdb-setup.ts"],"sourcesContent":["// Export configured PouchDB instance\nexport { default } from '../impl/couch/pouchdb-setup.js';","import PouchDB from 'pouchdb';\nimport PouchDBFind from 'pouchdb-find';\nimport PouchDBAuth from '@nilock2/pouchdb-authentication';\n\n// Register plugins\nPouchDB.plugin(PouchDBFind);\nPouchDB.plugin(PouchDBAuth);\n\n// Configure PouchDB globally\nPouchDB.defaults({\n // ajax: {\n // timeout: 60000,\n // },\n});\n\nexport default PouchDB;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,qBAAoB;AACpB,0BAAwB;AACxB,oCAAwB;AAGxB,eAAAA,QAAQ,OAAO,oBAAAC,OAAW;AAC1B,eAAAD,QAAQ,OAAO,8BAAAE,OAAW;AAG1B,eAAAF,QAAQ,SAAS;AAAA;AAAA;AAAA;AAIjB,CAAC;AAED,IAAO,wBAAQ,eAAAA;","names":["PouchDB","PouchDBFind","PouchDBAuth"]}
1
+ {"version":3,"sources":["../../src/pouch/index.ts","../../src/impl/couch/pouchdb-setup.ts"],"sourcesContent":["// Export configured PouchDB instance\nexport { default } from '../impl/couch/pouchdb-setup.js';","import PouchDB from 'pouchdb';\nimport PouchDBFind from 'pouchdb-find';\nimport PouchDBAuth from '@nilock2/pouchdb-authentication';\n\n// Register plugins\nPouchDB.plugin(PouchDBFind);\nPouchDB.plugin(PouchDBAuth);\n\n// Disable PouchDB debug logging to prevent interference with CLI prompts\n// Debug logging (like DerivedLogger.emit) will still go to the TUI log file\n// if initializeTuiLogging() has been called, but won't clutter terminal output\nif (typeof PouchDB.debug !== 'undefined') {\n PouchDB.debug.disable();\n}\n\n// Configure PouchDB globally\nPouchDB.defaults({\n // ajax: {\n // timeout: 60000,\n // },\n});\n\nexport default PouchDB;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,qBAAoB;AACpB,0BAAwB;AACxB,oCAAwB;AAGxB,eAAAA,QAAQ,OAAO,oBAAAC,OAAW;AAC1B,eAAAD,QAAQ,OAAO,8BAAAE,OAAW;AAK1B,IAAI,OAAO,eAAAF,QAAQ,UAAU,aAAa;AACxC,iBAAAA,QAAQ,MAAM,QAAQ;AACxB;AAGA,eAAAA,QAAQ,SAAS;AAAA;AAAA;AAAA;AAIjB,CAAC;AAED,IAAO,wBAAQ,eAAAA;","names":["PouchDB","PouchDBFind","PouchDBAuth"]}
@@ -4,6 +4,9 @@ import PouchDBFind from "pouchdb-find";
4
4
  import PouchDBAuth from "@nilock2/pouchdb-authentication";
5
5
  PouchDB.plugin(PouchDBFind);
6
6
  PouchDB.plugin(PouchDBAuth);
7
+ if (typeof PouchDB.debug !== "undefined") {
8
+ PouchDB.debug.disable();
9
+ }
7
10
  PouchDB.defaults({
8
11
  // ajax: {
9
12
  // timeout: 60000,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/impl/couch/pouchdb-setup.ts"],"sourcesContent":["import PouchDB from 'pouchdb';\nimport PouchDBFind from 'pouchdb-find';\nimport PouchDBAuth from '@nilock2/pouchdb-authentication';\n\n// Register plugins\nPouchDB.plugin(PouchDBFind);\nPouchDB.plugin(PouchDBAuth);\n\n// Configure PouchDB globally\nPouchDB.defaults({\n // ajax: {\n // timeout: 60000,\n // },\n});\n\nexport default PouchDB;\n"],"mappings":";AAAA,OAAO,aAAa;AACpB,OAAO,iBAAiB;AACxB,OAAO,iBAAiB;AAGxB,QAAQ,OAAO,WAAW;AAC1B,QAAQ,OAAO,WAAW;AAG1B,QAAQ,SAAS;AAAA;AAAA;AAAA;AAIjB,CAAC;AAED,IAAO,wBAAQ;","names":[]}
1
+ {"version":3,"sources":["../../src/impl/couch/pouchdb-setup.ts"],"sourcesContent":["import PouchDB from 'pouchdb';\nimport PouchDBFind from 'pouchdb-find';\nimport PouchDBAuth from '@nilock2/pouchdb-authentication';\n\n// Register plugins\nPouchDB.plugin(PouchDBFind);\nPouchDB.plugin(PouchDBAuth);\n\n// Disable PouchDB debug logging to prevent interference with CLI prompts\n// Debug logging (like DerivedLogger.emit) will still go to the TUI log file\n// if initializeTuiLogging() has been called, but won't clutter terminal output\nif (typeof PouchDB.debug !== 'undefined') {\n PouchDB.debug.disable();\n}\n\n// Configure PouchDB globally\nPouchDB.defaults({\n // ajax: {\n // timeout: 60000,\n // },\n});\n\nexport default PouchDB;\n"],"mappings":";AAAA,OAAO,aAAa;AACpB,OAAO,iBAAiB;AACxB,OAAO,iBAAiB;AAGxB,QAAQ,OAAO,WAAW;AAC1B,QAAQ,OAAO,WAAW;AAK1B,IAAI,OAAO,QAAQ,UAAU,aAAa;AACxC,UAAQ,MAAM,QAAQ;AACxB;AAGA,QAAQ,SAAS;AAAA;AAAA;AAAA;AAIjB,CAAC;AAED,IAAO,wBAAQ;","names":[]}
@@ -9,7 +9,7 @@ The navigation strategy system selects and scores cards for study sessions. It u
9
9
 
10
10
  ### WeightedCard
11
11
 
12
- A card with a suitability score, audit trail, and pre-fetched data:
12
+ A card with a suitability score, audit trail, and pre-fetched metadata:
13
13
 
14
14
  ```typescript
15
15
  interface WeightedCard {
@@ -210,10 +210,6 @@ class MyGenerator extends ContentNavigator implements CardGenerator {
210
210
  }]
211
211
  }));
212
212
  }
213
-
214
- // Legacy methods - stub or implement for backward compat
215
- async getNewCards() { return []; }
216
- async getPendingReviews() { return []; }
217
213
  }
218
214
  ```
219
215
 
@@ -246,10 +242,8 @@ class MyFilter extends ContentNavigator implements CardFilter {
246
242
  });
247
243
  }
248
244
 
249
- // Legacy methods - filters don't generate cards
245
+ // Legacy method - filters don't generate cards
250
246
  async getWeightedCards() { throw new Error('Use transform() via Pipeline'); }
251
- async getNewCards() { return []; }
252
- async getPendingReviews() { return []; }
253
247
  }
254
248
  ```
255
249
 
@@ -364,7 +358,6 @@ return { ...card, score: card.score * multiplier };
364
358
 
365
359
  ## Related Documentation
366
360
 
367
- - `todo-pipeline-optimization.md` — Batch tag hydration implementation (✅ completed)
368
361
  - `todo-strategy-authoring.md` — UX and DX for authoring strategies
369
362
  - `todo-evolutionary-orchestration.md` — Long-term adaptive strategy vision
370
363
  - `devlog/1004` — Implementation details for tag hydration optimization
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
7
- "version": "0.1.20",
7
+ "version": "0.1.21",
8
8
  "description": "Database layer for vue-skuilder",
9
9
  "main": "dist/index.js",
10
10
  "module": "dist/index.mjs",
@@ -48,7 +48,7 @@
48
48
  },
49
49
  "dependencies": {
50
50
  "@nilock2/pouchdb-authentication": "^1.0.2",
51
- "@vue-skuilder/common": "0.1.20",
51
+ "@vue-skuilder/common": "0.1.21",
52
52
  "cross-fetch": "^4.1.0",
53
53
  "moment": "^2.29.4",
54
54
  "pouchdb": "^9.0.0",
@@ -62,5 +62,5 @@
62
62
  "vite": "^7.0.0",
63
63
  "vitest": "^4.0.15"
64
64
  },
65
- "stableVersion": "0.1.20"
65
+ "stableVersion": "0.1.21"
66
66
  }
@@ -1,6 +1,4 @@
1
1
  import { ClassroomConfig } from '@vue-skuilder/common';
2
- import { ScheduledCard } from '../types/user';
3
- import { StudySessionNewItem, StudySessionReviewItem } from './contentSource';
4
2
 
5
3
  /**
6
4
  * Classroom management
@@ -29,17 +27,11 @@ export interface TeacherClassroomDBInterface extends ClassroomDBInterface {
29
27
  removeContent?(content: AssignedContent): Promise<void>;
30
28
  }
31
29
 
32
- export interface StudentClassroomDBInterface extends ClassroomDBInterface {
33
- /**
34
- * For student interfaces: get pending reviews
35
- */
36
- getPendingReviews?(): Promise<(StudySessionReviewItem & ScheduledCard)[]>;
37
-
38
- /**
39
- * For student interfaces: get new cards
40
- */
41
- getNewCards?(limit?: number): Promise<StudySessionNewItem[]>;
42
- }
30
+ /**
31
+ * Student-facing classroom interface.
32
+ * Content is accessed via StudyContentSource.getWeightedCards().
33
+ */
34
+ export type StudentClassroomDBInterface = ClassroomDBInterface;
43
35
 
44
36
  export type AssignedContent = AssignedCourse | AssignedTag | AssignedCard;
45
37
 
@@ -1,43 +1,10 @@
1
1
  import { getDataLayer } from '@db/factory';
2
2
  import { UserDBInterface } from '..';
3
3
  import { StudentClassroomDB } from '../../impl/couch/classroomDB';
4
- import { ScheduledCard } from '@db/core/types/user';
5
4
  import { WeightedCard } from '../navigators';
6
5
  import { TagFilter, hasActiveFilter } from '@vue-skuilder/common';
7
6
  import { TagFilteredContentSource } from '../../study/TagFilteredContentSource';
8
7
 
9
- // ============================================================================
10
- // API MIGRATION NOTICE
11
- // ============================================================================
12
- //
13
- // The StudyContentSource interface is being superseded by the ContentNavigator
14
- // class and its getWeightedCards() API. See:
15
- // packages/db/src/core/navigators/ARCHITECTURE.md
16
- //
17
- // HISTORICAL CONTEXT:
18
- // - This interface was designed to abstract 'classrooms' and 'courses' as
19
- // content sources for study sessions.
20
- // - getNewCards() and getPendingReviews() were artifacts of two hard-coded
21
- // navigation strategies: ELO proximity (new) and SRS scheduling (reviews).
22
- // - The new/review split reflected implementation details, not fundamentals.
23
- //
24
- // THE PROBLEM:
25
- // - "What does 'get reviews' mean for an interference mitigator?" - it doesn't.
26
- // - SRS is just one strategy that could express review urgency as scores.
27
- // - Some strategies generate candidates, others filter/score them.
28
- //
29
- // THE SOLUTION:
30
- // - ContentNavigator.getWeightedCards() returns unified scored candidates.
31
- // - WeightedCard.source field distinguishes new/review/failed (metadata, not API).
32
- // - Strategies compose via delegate pattern (filter wraps generator).
33
- //
34
- // MIGRATION PATH:
35
- // 1. ContentNavigator implements StudyContentSource for backward compat
36
- // 2. SessionController will migrate to call getWeightedCards()
37
- // 3. Legacy methods will be deprecated, then removed
38
- //
39
- // ============================================================================
40
-
41
8
  export type StudySessionFailedItem = StudySessionFailedNewItem | StudySessionFailedReviewItem;
42
9
 
43
10
  export interface StudySessionFailedNewItem extends StudySessionItem {
@@ -89,49 +56,22 @@ export interface ContentSourceID {
89
56
  /**
90
57
  * Interface for sources that provide study content to SessionController.
91
58
  *
92
- * @deprecated This interface will be superseded by ContentNavigator.getWeightedCards().
93
- * The getNewCards/getPendingReviews split was an artifact of hard-coded ELO and SRS
94
- * strategies. The new API returns unified WeightedCard[] with scores.
95
- *
96
- * MIGRATION:
97
- * - Implement ContentNavigator instead of StudyContentSource directly
98
- * - Override getWeightedCards() as the primary method
99
- * - Legacy methods can delegate to getWeightedCards() or be left as-is
59
+ * Content sources return scored candidates via getWeightedCards(), which
60
+ * SessionController uses to populate study queues.
100
61
  *
101
- * See: packages/db/src/core/navigators/ARCHITECTURE.md
62
+ * See: packages/db/docs/navigators-architecture.md
102
63
  */
103
64
  export interface StudyContentSource {
104
- /**
105
- * Get cards scheduled for review based on SRS algorithm.
106
- *
107
- * @deprecated Will be replaced by getWeightedCards() which returns scored candidates.
108
- * Review urgency will be expressed as a score rather than a separate method.
109
- */
110
- getPendingReviews(): Promise<(StudySessionReviewItem & ScheduledCard)[]>;
111
-
112
- /**
113
- * Get new cards for introduction, typically ordered by ELO proximity.
114
- *
115
- * @deprecated Will be replaced by getWeightedCards() which returns scored candidates.
116
- * New card selection and scoring will be unified with review scoring.
117
- *
118
- * @param n - Maximum number of new cards to return
119
- */
120
- getNewCards(n?: number): Promise<StudySessionNewItem[]>;
121
-
122
65
  /**
123
66
  * Get cards with suitability scores for presentation.
124
67
  *
125
- * This is the PRIMARY API for content sources going forward. Returns unified
126
- * scored candidates that can be sorted and selected by SessionController.
127
- *
128
- * The `source` field on WeightedCard indicates origin ('new' | 'review' | 'failed')
129
- * for queue routing purposes during the migration period.
68
+ * Returns unified scored candidates that can be sorted and selected by SessionController.
69
+ * The card origin ('new' | 'review' | 'failed') is determined by provenance metadata.
130
70
  *
131
71
  * @param limit - Maximum number of cards to return
132
72
  * @returns Cards sorted by score descending
133
73
  */
134
- getWeightedCards?(limit: number): Promise<WeightedCard[]>;
74
+ getWeightedCards(limit: number): Promise<WeightedCard[]>;
135
75
  }
136
76
  // #endregion docs_StudyContentSource
137
77
 
@@ -1,5 +1,5 @@
1
1
  import { CourseConfig, CourseElo, DataShape, SkuilderCourseData } from '@vue-skuilder/common';
2
- import { StudySessionNewItem, StudySessionItem } from './contentSource';
2
+ import { StudyContentSource, StudySessionItem } from './contentSource';
3
3
  import { TagStub, Tag, QualifiedCardID } from '../types/types-legacy';
4
4
  import { DataLayerResult } from '../types/db';
5
5
  import { NavigationStrategyManager } from './navigationStrategyManager';
@@ -26,7 +26,7 @@ export interface CourseInfo {
26
26
  registeredUsers: number;
27
27
  }
28
28
 
29
- export interface CourseDBInterface extends NavigationStrategyManager {
29
+ export interface CourseDBInterface extends NavigationStrategyManager, StudyContentSource {
30
30
  /**
31
31
  * Get course config
32
32
  */
@@ -74,11 +74,6 @@ export interface CourseDBInterface extends NavigationStrategyManager {
74
74
  */
75
75
  updateCardElo(cardId: string, elo: CourseElo): Promise<PouchDB.Core.Response>;
76
76
 
77
- /**
78
- * Get new cards for study
79
- */
80
- getNewCards(limit?: number): Promise<StudySessionNewItem[]>;
81
-
82
77
  /**
83
78
  * Get cards centered at a particular ELO rating
84
79
  */
@@ -1,12 +1,10 @@
1
1
  import { toCourseElo } from '@vue-skuilder/common';
2
2
  import type { CourseDBInterface } from '../interfaces/courseDB';
3
3
  import type { UserDBInterface } from '../interfaces/userDB';
4
- import type { ScheduledCard } from '../types/user';
5
4
  import { ContentNavigator } from './index';
6
5
  import type { WeightedCard } from './index';
7
6
  import type { CardFilter, FilterContext } from './filters/types';
8
7
  import type { CardGenerator, GeneratorContext } from './generators/types';
9
- import type { StudySessionNewItem, StudySessionReviewItem } from '../interfaces/contentSource';
10
8
  import { logger } from '../../util/logger';
11
9
 
12
10
  // ============================================================================
@@ -22,14 +20,11 @@ import { logger } from '../../util/logger';
22
20
  * Shows generator and filter chain structure.
23
21
  */
24
22
  function logPipelineConfig(generator: CardGenerator, filters: CardFilter[]): void {
25
- const filterList = filters.length > 0
26
- ? '\n - ' + filters.map(f => f.name).join('\n - ')
27
- : ' none';
23
+ const filterList =
24
+ filters.length > 0 ? '\n - ' + filters.map((f) => f.name).join('\n - ') : ' none';
28
25
 
29
26
  logger.info(
30
- `[Pipeline] Configuration:\n` +
31
- ` Generator: ${generator.name}\n` +
32
- ` Filters:${filterList}`
27
+ `[Pipeline] Configuration:\n` + ` Generator: ${generator.name}\n` + ` Filters:${filterList}`
33
28
  );
34
29
  }
35
30
 
@@ -39,11 +34,11 @@ function logPipelineConfig(generator: CardGenerator, filters: CardFilter[]): voi
39
34
  */
40
35
  function logTagHydration(cards: WeightedCard[], tagsByCard: Map<string, string[]>): void {
41
36
  const totalTags = Array.from(tagsByCard.values()).reduce((sum, tags) => sum + tags.length, 0);
42
- const cardsWithTags = Array.from(tagsByCard.values()).filter(tags => tags.length > 0).length;
37
+ const cardsWithTags = Array.from(tagsByCard.values()).filter((tags) => tags.length > 0).length;
43
38
 
44
39
  logger.debug(
45
40
  `[Pipeline] Tag hydration: ${cards.length} cards, ` +
46
- `${cardsWithTags} have tags (${totalTags} total tags) - single batch query`
41
+ `${cardsWithTags} have tags (${totalTags} total tags) - single batch query`
47
42
  );
48
43
  }
49
44
 
@@ -58,13 +53,12 @@ function logExecutionSummary(
58
53
  finalCount: number,
59
54
  topScores: number[]
60
55
  ): void {
61
- const scoreDisplay = topScores.length > 0
62
- ? topScores.map(s => s.toFixed(2)).join(', ')
63
- : 'none';
56
+ const scoreDisplay =
57
+ topScores.length > 0 ? topScores.map((s) => s.toFixed(2)).join(', ') : 'none';
64
58
 
65
59
  logger.info(
66
60
  `[Pipeline] Execution: ${generatorName} produced ${generatedCount} → ` +
67
- `${filterCount} filters → ${finalCount} results (top scores: ${scoreDisplay})`
61
+ `${filterCount} filters → ${finalCount} results (top scores: ${scoreDisplay})`
68
62
  );
69
63
  }
70
64
 
@@ -154,6 +148,14 @@ export class Pipeline extends ContentNavigator {
154
148
  this.user = user;
155
149
  this.course = course;
156
150
 
151
+ course
152
+ .getCourseConfig()
153
+ .then((cfg) => {
154
+ logger.debug(`[pipeline] Crated pipeline for ${cfg.name}`);
155
+ })
156
+ .catch((e) => {
157
+ logger.error(`[pipeline] Failed to lookup courseCfg: ${e}`);
158
+ });
157
159
  // Toggle pipeline configuration logging:
158
160
  logPipelineConfig(generator, filters);
159
161
  }
@@ -210,8 +212,14 @@ export class Pipeline extends ContentNavigator {
210
212
  const result = cards.slice(0, limit);
211
213
 
212
214
  // Toggle execution summary logging:
213
- const topScores = result.slice(0, 3).map(c => c.score);
214
- logExecutionSummary(this.generator.name, generatedCount, this.filters.length, result.length, topScores);
215
+ const topScores = result.slice(0, 3).map((c) => c.score);
216
+ logExecutionSummary(
217
+ this.generator.name,
218
+ generatedCount,
219
+ this.filters.length,
220
+ result.length,
221
+ topScores
222
+ );
215
223
 
216
224
  // Toggle provenance logging (shows scoring history for top cards):
217
225
  logCardProvenance(result, 3);
@@ -272,43 +280,6 @@ export class Pipeline extends ContentNavigator {
272
280
  };
273
281
  }
274
282
 
275
- // ===========================================================================
276
- // Legacy StudyContentSource methods
277
- // ===========================================================================
278
- //
279
- // These delegate to the generator for backward compatibility.
280
- // Eventually SessionController will use getWeightedCards() exclusively.
281
- //
282
-
283
- /**
284
- * Get new cards via legacy API.
285
- * Delegates to the generator if it supports the legacy interface.
286
- */
287
- async getNewCards(n?: number): Promise<StudySessionNewItem[]> {
288
- // Check if generator has legacy method (ContentNavigator-based generators do)
289
- if ('getNewCards' in this.generator && typeof this.generator.getNewCards === 'function') {
290
- return (this.generator as ContentNavigator).getNewCards(n);
291
- }
292
- // Pure CardGenerator without legacy support - return empty
293
- return [];
294
- }
295
-
296
- /**
297
- * Get pending reviews via legacy API.
298
- * Delegates to the generator if it supports the legacy interface.
299
- */
300
- async getPendingReviews(): Promise<(StudySessionReviewItem & ScheduledCard)[]> {
301
- // Check if generator has legacy method (ContentNavigator-based generators do)
302
- if (
303
- 'getPendingReviews' in this.generator &&
304
- typeof this.generator.getPendingReviews === 'function'
305
- ) {
306
- return (this.generator as ContentNavigator).getPendingReviews();
307
- }
308
- // Pure CardGenerator without legacy support - return empty
309
- return [];
310
- }
311
-
312
283
  /**
313
284
  * Get the course ID for this pipeline.
314
285
  */
@@ -7,7 +7,7 @@ import { DocType } from '../types/types-legacy';
7
7
  import { logger } from '../../util/logger';
8
8
  import type { CourseDBInterface } from '../interfaces/courseDB';
9
9
  import type { UserDBInterface } from '../interfaces/userDB';
10
- import CompositeGenerator from './CompositeGenerator';
10
+ import CompositeGenerator from './generators/CompositeGenerator';
11
11
 
12
12
  // ============================================================================
13
13
  // PIPELINE ASSEMBLER
@@ -0,0 +1,84 @@
1
+ import { Navigators } from './index';
2
+ import { Pipeline } from './Pipeline';
3
+ import CompositeGenerator from './generators/CompositeGenerator';
4
+ import ELONavigator from './generators/elo';
5
+ import SRSNavigator from './generators/srs';
6
+ import { createEloDistanceFilter } from './filters/eloDistance';
7
+ import type { ContentNavigationStrategyData } from '../types/contentNavigationStrategy';
8
+ import { DocType } from '../types/types-legacy';
9
+ import type { CourseDBInterface, UserDBInterface } from '../interfaces';
10
+
11
+ /**
12
+ * Default navigation pipeline configuration.
13
+ *
14
+ * This module provides factory functions for creating the canonical default
15
+ * navigation pipeline used by both CouchDB and static course implementations.
16
+ */
17
+
18
+ /**
19
+ * Create default ELO navigation strategy data.
20
+ * Used when no custom strategies are configured.
21
+ *
22
+ * @param courseId - The course ID to associate with this strategy
23
+ * @returns Strategy data for default ELO navigation
24
+ */
25
+ export function createDefaultEloStrategy(courseId: string): ContentNavigationStrategyData {
26
+ return {
27
+ _id: 'NAVIGATION_STRATEGY-ELO-default',
28
+ docType: DocType.NAVIGATION_STRATEGY,
29
+ name: 'ELO (default)',
30
+ description: 'Default ELO-based navigation strategy for new cards',
31
+ implementingClass: Navigators.ELO,
32
+ course: courseId,
33
+ serializedData: '',
34
+ };
35
+ }
36
+
37
+ /**
38
+ * Create default SRS navigation strategy data.
39
+ * Used when no custom strategies are configured.
40
+ *
41
+ * @param courseId - The course ID to associate with this strategy
42
+ * @returns Strategy data for default SRS navigation
43
+ */
44
+ export function createDefaultSrsStrategy(courseId: string): ContentNavigationStrategyData {
45
+ return {
46
+ _id: 'NAVIGATION_STRATEGY-SRS-default',
47
+ docType: DocType.NAVIGATION_STRATEGY,
48
+ name: 'SRS (default)',
49
+ description: 'Default SRS-based navigation strategy for reviews',
50
+ implementingClass: Navigators.SRS,
51
+ course: courseId,
52
+ serializedData: '',
53
+ };
54
+ }
55
+
56
+ /**
57
+ * Creates the default navigation pipeline for courses with no configured strategies.
58
+ *
59
+ * Default: Pipeline(Composite(ELO, SRS), [eloDistanceFilter])
60
+ * - ELO generator: scores new cards by skill proximity
61
+ * - SRS generator: scores reviews by overdueness and interval recency
62
+ * - ELO distance filter: penalizes cards far from user's current level
63
+ *
64
+ * This is the canonical default configuration used when:
65
+ * - No navigation strategy documents exist in the course
66
+ * - PipelineAssembler fails to build from strategy documents
67
+ *
68
+ * @param user - User database interface for accessing user state
69
+ * @param course - Course database interface for accessing course data
70
+ * @returns Configured Pipeline ready for use
71
+ */
72
+ export function createDefaultPipeline(
73
+ user: UserDBInterface,
74
+ course: CourseDBInterface
75
+ ): Pipeline {
76
+ const courseId = course.getCourseID();
77
+ const eloNavigator = new ELONavigator(user, course, createDefaultEloStrategy(courseId));
78
+ const srsNavigator = new SRSNavigator(user, course, createDefaultSrsStrategy(courseId));
79
+
80
+ const compositeGenerator = new CompositeGenerator([eloNavigator, srsNavigator]);
81
+ const eloDistanceFilter = createEloDistanceFilter();
82
+
83
+ return new Pipeline(compositeGenerator, [eloDistanceFilter], user, course);
84
+ }
@@ -1,11 +1,9 @@
1
- import type { ScheduledCard } from '../types/user';
2
- import type { CourseDBInterface } from '../interfaces/courseDB';
3
- import type { UserDBInterface } from '../interfaces/userDB';
4
- import { ContentNavigator } from './index';
5
- import type { WeightedCard } from './index';
6
- import type { ContentNavigationStrategyData } from '../types/contentNavigationStrategy';
7
- import type { StudySessionReviewItem, StudySessionNewItem } from '..';
8
- import type { CardFilter, FilterContext } from './filters/types';
1
+ import type { CourseDBInterface } from '../../interfaces/courseDB';
2
+ import type { UserDBInterface } from '../../interfaces/userDB';
3
+ import { ContentNavigator } from '../index';
4
+ import type { WeightedCard } from '../index';
5
+ import type { ContentNavigationStrategyData } from '../../types/contentNavigationStrategy';
6
+ import type { CardFilter, FilterContext } from './types';
9
7
  import { toCourseElo } from '@vue-skuilder/common';
10
8
 
11
9
  /**
@@ -53,7 +51,6 @@ const DEFAULT_MIN_COUNT = 3;
53
51
  */
54
52
  export default class HierarchyDefinitionNavigator extends ContentNavigator implements CardFilter {
55
53
  private config: HierarchyConfig;
56
- private _strategyData: ContentNavigationStrategyData;
57
54
 
58
55
  /** Human-readable name for CardFilter interface */
59
56
  name: string;
@@ -61,12 +58,11 @@ export default class HierarchyDefinitionNavigator extends ContentNavigator imple
61
58
  constructor(
62
59
  user: UserDBInterface,
63
60
  course: CourseDBInterface,
64
- _strategyData: ContentNavigationStrategyData
61
+ strategyData: ContentNavigationStrategyData
65
62
  ) {
66
- super(user, course, _strategyData);
67
- this._strategyData = _strategyData;
68
- this.config = this.parseConfig(_strategyData.serializedData);
69
- this.name = _strategyData.name || 'Hierarchy Definition';
63
+ super(user, course, strategyData);
64
+ this.config = this.parseConfig(strategyData.serializedData);
65
+ this.name = strategyData.name || 'Hierarchy Definition';
70
66
  }
71
67
 
72
68
  private parseConfig(serializedData: string): HierarchyConfig {
@@ -159,7 +155,7 @@ export default class HierarchyDefinitionNavigator extends ContentNavigator imple
159
155
  */
160
156
  private async checkCardUnlock(
161
157
  card: WeightedCard,
162
- course: CourseDBInterface,
158
+ _course: CourseDBInterface,
163
159
  unlockedTags: Set<string>,
164
160
  masteredTags: Set<string>
165
161
  ): Promise<{ isUnlocked: boolean; reason: string }> {
@@ -253,14 +249,4 @@ export default class HierarchyDefinitionNavigator extends ContentNavigator imple
253
249
  'Use Pipeline with a generator and this filter via transform().'
254
250
  );
255
251
  }
256
-
257
- // Legacy methods - stub implementations since filters don't generate cards
258
-
259
- async getNewCards(_n?: number): Promise<StudySessionNewItem[]> {
260
- return [];
261
- }
262
-
263
- async getPendingReviews(): Promise<(StudySessionReviewItem & ScheduledCard)[]> {
264
- return [];
265
- }
266
252
  }
@@ -1,11 +1,9 @@
1
- import type { ScheduledCard } from '../types/user';
2
- import type { CourseDBInterface } from '../interfaces/courseDB';
3
- import type { UserDBInterface } from '../interfaces/userDB';
4
- import { ContentNavigator } from './index';
5
- import type { WeightedCard } from './index';
6
- import type { ContentNavigationStrategyData } from '../types/contentNavigationStrategy';
7
- import type { StudySessionReviewItem, StudySessionNewItem } from '..';
8
- import type { CardFilter, FilterContext } from './filters/types';
1
+ import type { CourseDBInterface } from '../../interfaces/courseDB';
2
+ import type { UserDBInterface } from '../../interfaces/userDB';
3
+ import { ContentNavigator } from '../index';
4
+ import type { WeightedCard } from '../index';
5
+ import type { ContentNavigationStrategyData } from '../../types/contentNavigationStrategy';
6
+ import type { CardFilter, FilterContext } from './types';
9
7
  import { toCourseElo } from '@vue-skuilder/common';
10
8
 
11
9
  /**
@@ -80,7 +78,6 @@ const DEFAULT_INTERFERENCE_DECAY = 0.8;
80
78
  */
81
79
  export default class InterferenceMitigatorNavigator extends ContentNavigator implements CardFilter {
82
80
  private config: InterferenceConfig;
83
- private _strategyData: ContentNavigationStrategyData;
84
81
 
85
82
  /** Human-readable name for CardFilter interface */
86
83
  name: string;
@@ -91,13 +88,12 @@ export default class InterferenceMitigatorNavigator extends ContentNavigator imp
91
88
  constructor(
92
89
  user: UserDBInterface,
93
90
  course: CourseDBInterface,
94
- _strategyData: ContentNavigationStrategyData
91
+ strategyData: ContentNavigationStrategyData
95
92
  ) {
96
- super(user, course, _strategyData);
97
- this._strategyData = _strategyData;
98
- this.config = this.parseConfig(_strategyData.serializedData);
93
+ super(user, course, strategyData);
94
+ this.config = this.parseConfig(strategyData.serializedData);
99
95
  this.interferenceMap = this.buildInterferenceMap();
100
- this.name = _strategyData.name || 'Interference Mitigator';
96
+ this.name = strategyData.name || 'Interference Mitigator';
101
97
  }
102
98
 
103
99
  private parseConfig(serializedData: string): InterferenceConfig {
@@ -342,14 +338,4 @@ export default class InterferenceMitigatorNavigator extends ContentNavigator imp
342
338
  'Use Pipeline with a generator and this filter via transform().'
343
339
  );
344
340
  }
345
-
346
- // Legacy methods - stub implementations since filters don't generate cards
347
-
348
- async getNewCards(_n?: number): Promise<StudySessionNewItem[]> {
349
- return [];
350
- }
351
-
352
- async getPendingReviews(): Promise<(StudySessionReviewItem & ScheduledCard)[]> {
353
- return [];
354
- }
355
341
  }