@vue-skuilder/db 0.1.17 → 0.1.20

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 (92) hide show
  1. package/dist/{userDB-BqwxtJ_7.d.mts → classroomDB-CZdMBiTU.d.ts} +427 -104
  2. package/dist/{userDB-DNa0XPtn.d.ts → classroomDB-PxDZTky3.d.cts} +427 -104
  3. package/dist/core/index.d.cts +304 -0
  4. package/dist/core/index.d.ts +237 -25
  5. package/dist/core/index.js +2246 -118
  6. package/dist/core/index.js.map +1 -1
  7. package/dist/core/index.mjs +2235 -114
  8. package/dist/core/index.mjs.map +1 -1
  9. package/dist/{dataLayerProvider-VlngD19_.d.mts → dataLayerProvider-D0MoZMjH.d.cts} +1 -1
  10. package/dist/{dataLayerProvider-BV5iZqt_.d.ts → dataLayerProvider-D8o6ZnKW.d.ts} +1 -1
  11. package/dist/impl/couch/{index.d.mts → index.d.cts} +47 -5
  12. package/dist/impl/couch/index.d.ts +46 -4
  13. package/dist/impl/couch/index.js +2250 -134
  14. package/dist/impl/couch/index.js.map +1 -1
  15. package/dist/impl/couch/index.mjs +2212 -97
  16. package/dist/impl/couch/index.mjs.map +1 -1
  17. package/dist/impl/static/{index.d.mts → index.d.cts} +6 -6
  18. package/dist/impl/static/index.d.ts +5 -5
  19. package/dist/impl/static/index.js +1950 -143
  20. package/dist/impl/static/index.js.map +1 -1
  21. package/dist/impl/static/index.mjs +1922 -117
  22. package/dist/impl/static/index.mjs.map +1 -1
  23. package/dist/{index-Bmll7Xse.d.mts → index-B_j6u5E4.d.cts} +1 -1
  24. package/dist/{index-CD8BZz2k.d.ts → index-Dj0SEgk3.d.ts} +1 -1
  25. package/dist/{index.d.mts → index.d.cts} +97 -13
  26. package/dist/index.d.ts +96 -12
  27. package/dist/index.js +2439 -180
  28. package/dist/index.js.map +1 -1
  29. package/dist/index.mjs +2386 -135
  30. package/dist/index.mjs.map +1 -1
  31. package/dist/pouch/index.js +3 -3
  32. package/dist/{types-Dbp5DaRR.d.mts → types-Bn0itutr.d.cts} +1 -1
  33. package/dist/{types-CewsN87z.d.ts → types-DQaXnuoc.d.ts} +1 -1
  34. package/dist/{types-legacy-6ettoclI.d.ts → types-legacy-DDY4N-Uq.d.cts} +3 -1
  35. package/dist/{types-legacy-6ettoclI.d.mts → types-legacy-DDY4N-Uq.d.ts} +3 -1
  36. package/dist/util/packer/{index.d.mts → index.d.cts} +3 -3
  37. package/dist/util/packer/index.d.ts +3 -3
  38. package/dist/util/packer/index.js.map +1 -1
  39. package/dist/util/packer/index.mjs.map +1 -1
  40. package/docs/brainstorm-navigation-paradigm.md +369 -0
  41. package/docs/navigators-architecture.md +370 -0
  42. package/docs/todo-evolutionary-orchestration.md +310 -0
  43. package/docs/todo-nominal-tag-types.md +121 -0
  44. package/docs/todo-strategy-authoring.md +401 -0
  45. package/eslint.config.mjs +1 -1
  46. package/package.json +9 -4
  47. package/src/core/index.ts +1 -0
  48. package/src/core/interfaces/contentSource.ts +88 -4
  49. package/src/core/interfaces/courseDB.ts +13 -0
  50. package/src/core/interfaces/navigationStrategyManager.ts +0 -5
  51. package/src/core/interfaces/userDB.ts +32 -0
  52. package/src/core/navigators/CompositeGenerator.ts +268 -0
  53. package/src/core/navigators/Pipeline.ts +318 -0
  54. package/src/core/navigators/PipelineAssembler.ts +194 -0
  55. package/src/core/navigators/elo.ts +104 -15
  56. package/src/core/navigators/filters/eloDistance.ts +132 -0
  57. package/src/core/navigators/filters/index.ts +9 -0
  58. package/src/core/navigators/filters/types.ts +115 -0
  59. package/src/core/navigators/filters/userTagPreference.ts +232 -0
  60. package/src/core/navigators/generators/index.ts +2 -0
  61. package/src/core/navigators/generators/types.ts +107 -0
  62. package/src/core/navigators/hardcodedOrder.ts +111 -12
  63. package/src/core/navigators/hierarchyDefinition.ts +266 -0
  64. package/src/core/navigators/index.ts +404 -3
  65. package/src/core/navigators/inferredPreference.ts +107 -0
  66. package/src/core/navigators/interferenceMitigator.ts +355 -0
  67. package/src/core/navigators/relativePriority.ts +255 -0
  68. package/src/core/navigators/srs.ts +195 -0
  69. package/src/core/navigators/userGoal.ts +136 -0
  70. package/src/core/types/strategyState.ts +84 -0
  71. package/src/core/types/types-legacy.ts +2 -0
  72. package/src/impl/common/BaseUserDB.ts +74 -7
  73. package/src/impl/couch/adminDB.ts +1 -2
  74. package/src/impl/couch/classroomDB.ts +51 -0
  75. package/src/impl/couch/courseDB.ts +147 -49
  76. package/src/impl/static/courseDB.ts +11 -4
  77. package/src/study/SessionController.ts +149 -1
  78. package/src/study/TagFilteredContentSource.ts +255 -0
  79. package/src/study/index.ts +1 -0
  80. package/src/util/dataDirectory.test.ts +51 -22
  81. package/src/util/logger.ts +0 -1
  82. package/tests/core/navigators/CompositeGenerator.test.ts +455 -0
  83. package/tests/core/navigators/Pipeline.test.ts +406 -0
  84. package/tests/core/navigators/PipelineAssembler.test.ts +351 -0
  85. package/tests/core/navigators/SRSNavigator.test.ts +344 -0
  86. package/tests/core/navigators/eloDistanceFilter.test.ts +192 -0
  87. package/tests/core/navigators/navigators.test.ts +710 -0
  88. package/tsconfig.json +1 -1
  89. package/vitest.config.ts +29 -0
  90. package/dist/core/index.d.mts +0 -92
  91. /package/dist/{SyncStrategy-CyATpyLQ.d.mts → SyncStrategy-CyATpyLQ.d.cts} +0 -0
  92. /package/dist/pouch/{index.d.mts → index.d.cts} +0 -0
@@ -0,0 +1,121 @@
1
+ # TODO: Nominal Tag Types
2
+
3
+ ## Status: NOT STARTED
4
+
5
+ ## Problem
6
+
7
+ Tag identification in the codebase is ambiguous. Two representations are used interchangeably:
8
+
9
+ - **TagName**: Human-readable name (e.g., `"scales"`, `"C Major"`)
10
+ - **TagID**: Prefixed document ID (e.g., `"TAG-scales"`, `"TAG-C Major"`)
11
+
12
+ The `getTagID()` function normalizes between them:
13
+
14
+ ```typescript
15
+ // packages/db/src/impl/couch/courseAPI.ts
16
+ export function getTagID(tagName: string): string {
17
+ const tagPrefix = DocType.TAG.valueOf() + '-';
18
+ if (tagName.indexOf(tagPrefix) === 0) {
19
+ return tagName;
20
+ } else {
21
+ return tagPrefix + tagName;
22
+ }
23
+ }
24
+ ```
25
+
26
+ This defensive pattern (accepting either form) suggests the ambiguity causes friction. Function signatures like `getTag(courseID: string, tagName: string)` don't indicate whether they expect a name or ID.
27
+
28
+ ## Proposed Solution: Branded Types
29
+
30
+ Use TypeScript branded types to distinguish tag names from tag IDs at compile time:
31
+
32
+ ```typescript
33
+ // Branded type definitions
34
+ type TagName = string & { readonly __brand: 'TagName' };
35
+ type TagID = string & { readonly __brand: 'TagID' };
36
+
37
+ // Smart constructors
38
+ function tagName(s: string): TagName {
39
+ // Strip prefix if present (normalize to name)
40
+ const prefix = DocType.TAG.valueOf() + '-';
41
+ return (s.startsWith(prefix) ? s.slice(prefix.length) : s) as TagName;
42
+ }
43
+
44
+ function tagID(name: TagName): TagID {
45
+ return `${DocType.TAG.valueOf()}-${name}` as TagID;
46
+ }
47
+
48
+ // Now function signatures are self-documenting:
49
+ function getTag(courseID: string, tagName: TagName): Promise<Tag>;
50
+ function addTagToCard(courseID: string, cardID: string, tagID: TagID): Promise<Response>;
51
+ ```
52
+
53
+ ## Benefits
54
+
55
+ 1. **Self-documenting APIs**: Function signatures indicate expected format
56
+ 2. **Compile-time safety**: Can't accidentally pass a TagID where TagName expected
57
+ 3. **Reduced defensive code**: No need for `getTagID()` normalization everywhere
58
+ 4. **IDE support**: Autocomplete and type hints clarify intent
59
+
60
+ ## Implementation Steps
61
+
62
+ ### Step 1: Define Branded Types
63
+
64
+ - [ ] Create `packages/db/src/core/types/tagTypes.ts`
65
+ - [ ] Define `TagName` and `TagID` branded types
66
+ - [ ] Create smart constructors with normalization
67
+ - [ ] Export from `packages/db/src/core/types/index.ts`
68
+
69
+ ### Step 2: Update Tag Interface
70
+
71
+ - [ ] Update `Tag.name` to be `TagName` type
72
+ - [ ] Update `Tag._id` typing if applicable
73
+
74
+ ### Step 3: Update CourseDB Interface
75
+
76
+ - [ ] Update `getTag(tagName: TagName)`
77
+ - [ ] Update `createTag(tagName: TagName, ...)`
78
+ - [ ] Update `addTagToCard(cardId, tagId: TagID)`
79
+ - [ ] Update `removeTagFromCard(cardId, tagId: TagID)`
80
+
81
+ ### Step 4: Update Implementations
82
+
83
+ - [ ] `packages/db/src/impl/couch/courseDB.ts`
84
+ - [ ] `packages/db/src/impl/couch/courseAPI.ts`
85
+ - [ ] `packages/db/src/impl/static/courseDB.ts` (if applicable)
86
+
87
+ ### Step 5: Update Consumers
88
+
89
+ - [ ] Navigation strategies using tags
90
+ - [ ] Classroom tag assignments
91
+ - [ ] MCP tag resources
92
+ - [ ] UI components displaying/selecting tags
93
+
94
+ ## Files Affected
95
+
96
+ | File | Changes |
97
+ |------|---------|
98
+ | `core/types/tagTypes.ts` | New file with branded types |
99
+ | `core/types/types-legacy.ts` | Update `Tag` interface |
100
+ | `core/interfaces/courseDB.ts` | Update method signatures |
101
+ | `impl/couch/courseAPI.ts` | Update `getTagID`, `addTagToCard` |
102
+ | `impl/couch/courseDB.ts` | Update tag-related functions |
103
+ | Multiple navigators | Update tag handling |
104
+
105
+ ## Migration Strategy
106
+
107
+ 1. Start with type definitions that are compatible with plain strings
108
+ 2. Add branded types gradually, file by file
109
+ 3. Use `as TagName` / `as TagID` at boundaries during migration
110
+ 4. Tighten constraints once all code is updated
111
+
112
+ ## Considerations
113
+
114
+ - **Runtime cost**: Zero - branded types are compile-time only
115
+ - **Breaking change**: Moderate - requires updating call sites
116
+ - **Effort**: Medium - touches many files but changes are mechanical
117
+
118
+ ## Related
119
+
120
+ - `TagFilter` interface uses `string[]` for simplicity; could use `TagName[]` after this work
121
+ - Hierarchical tags (e.g., `"parent>child"`) may need additional consideration
@@ -0,0 +1,401 @@
1
+ # NavigationStrategy Authoring Tools
2
+
3
+ ## Status: UI COMPLETE ✅ (CLI/MCP Optional)
4
+
5
+ ## Goal
6
+
7
+ Provide tools for course authors to create and configure NavigationStrategy documents
8
+ without direct database manipulation. This includes UI components, CLI commands, and
9
+ potentially MCP tools.
10
+
11
+ **UI implementation completed.** CLI and MCP remain as optional future enhancements for
12
+ scriptable/agent-based workflows.
13
+
14
+ ## Current State (Updated)
15
+
16
+ ### What Exists ✅
17
+
18
+ | Component | Location | Status |
19
+ |-----------|----------|--------|
20
+ | `HardcodedOrderConfigForm.vue` | `packages/edit-ui/src/components/NavigationStrategy/` | ✅ Complete |
21
+ | `HierarchyConfigForm.vue` | `packages/edit-ui/src/components/NavigationStrategy/` | ✅ Complete |
22
+ | `InterferenceConfigForm.vue` | `packages/edit-ui/src/components/NavigationStrategy/` | ✅ Complete |
23
+ | `RelativePriorityConfigForm.vue` | `packages/edit-ui/src/components/NavigationStrategy/` | ✅ Complete |
24
+ | `NavigationStrategyEditor.vue` | `packages/edit-ui/src/components/NavigationStrategy/` | ✅ All strategy types supported |
25
+ | `NavigationStrategyList.vue` | `packages/edit-ui/src/components/NavigationStrategy/` | ✅ Compact layout, edit/delete |
26
+ | `CourseDB.addNavigationStrategy()` | `packages/db/src/impl/couch/courseDB.ts` | ✅ DB write method |
27
+ | `CourseDB.updateNavigationStrategy()` | `packages/db/src/impl/couch/courseDB.ts` | ✅ DB update method |
28
+ | `CourseDB.getAllNavigationStrategies()` | `packages/db/src/impl/couch/courseDB.ts` | ✅ DB read method |
29
+
30
+ ### What's Complete
31
+
32
+ - ✅ UI forms for all four strategy types (Hardcoded, Hierarchy, Interference, RelativePriority)
33
+ - ✅ Dual input modes (Visual Editor / JSON Editor) for each form
34
+ - ✅ Tag loading from course database
35
+ - ✅ Real-time validation with error messages
36
+ - ✅ Edit functionality for existing strategies
37
+ - ✅ Compact, non-modal two-column layout
38
+ - ✅ Responsive design (desktop and mobile)
39
+ - ✅ Full CRUD operations (Create, Read, Update, Delete)
40
+
41
+ ### What's Optional (Future Enhancements)
42
+
43
+ - ⏸️ CLI commands for strategy creation (scriptable workflows)
44
+ - ⏸️ MCP tools for agent-based authoring (LLM-guided config generation)
45
+ - ⏸️ Strategy behavior preview/simulation
46
+
47
+ ## Strategy Types and Their Configs
48
+
49
+ ### 1. HierarchyDefinition
50
+
51
+ ```typescript
52
+ interface HierarchyConfig {
53
+ prerequisites: {
54
+ [tagId: string]: TagPrerequisite[];
55
+ };
56
+ delegateStrategy?: string; // default: "elo"
57
+ }
58
+
59
+ interface TagPrerequisite {
60
+ tag: string;
61
+ masteryThreshold?: {
62
+ minElo?: number;
63
+ minCount?: number;
64
+ };
65
+ }
66
+ ```
67
+
68
+ **Example:**
69
+ ```json
70
+ {
71
+ "prerequisites": {
72
+ "cvc-words": [
73
+ { "tag": "letter-sounds", "masteryThreshold": { "minCount": 10, "minElo": 1050 } }
74
+ ],
75
+ "blends": [
76
+ { "tag": "cvc-words", "masteryThreshold": { "minCount": 20 } }
77
+ ]
78
+ },
79
+ "delegateStrategy": "elo"
80
+ }
81
+ ```
82
+
83
+ **UI Requirements:**
84
+ - Tag selector (from course's available tags)
85
+ - Prerequisite builder (tag → requires tags with thresholds)
86
+ - Threshold inputs (minCount, minElo)
87
+ - Delegate strategy selector
88
+
89
+ ---
90
+
91
+ ### 2. InterferenceMitigator
92
+
93
+ ```typescript
94
+ interface InterferenceConfig {
95
+ interferenceSets: InterferenceGroup[];
96
+ maturityThreshold?: {
97
+ minCount?: number;
98
+ minElo?: number;
99
+ minElapsedDays?: number;
100
+ };
101
+ defaultDecay?: number;
102
+ delegateStrategy?: string;
103
+ }
104
+
105
+ interface InterferenceGroup {
106
+ tags: string[];
107
+ decay?: number;
108
+ }
109
+ ```
110
+
111
+ **Example:**
112
+ ```json
113
+ {
114
+ "interferenceSets": [
115
+ { "tags": ["letter-b", "letter-d", "letter-p"], "decay": 0.9 },
116
+ { "tags": ["letter-m", "letter-n"], "decay": 0.7 }
117
+ ],
118
+ "maturityThreshold": { "minCount": 15, "minElapsedDays": 3 },
119
+ "defaultDecay": 0.8
120
+ }
121
+ ```
122
+
123
+ **UI Requirements:**
124
+ - Interference group builder (add/remove groups)
125
+ - Tag multi-selector for each group
126
+ - Decay slider (0-1) per group
127
+ - Maturity threshold inputs
128
+ - Delegate strategy selector
129
+
130
+ ---
131
+
132
+ ### 3. RelativePriority
133
+
134
+ ```typescript
135
+ interface RelativePriorityConfig {
136
+ tagPriorities: { [tagId: string]: number };
137
+ defaultPriority?: number;
138
+ combineMode?: 'max' | 'average' | 'min';
139
+ priorityInfluence?: number;
140
+ delegateStrategy?: string;
141
+ }
142
+ ```
143
+
144
+ **Example:**
145
+ ```json
146
+ {
147
+ "tagPriorities": {
148
+ "letter-s": 0.95,
149
+ "letter-t": 0.90,
150
+ "letter-a": 0.88,
151
+ "letter-x": 0.10,
152
+ "letter-z": 0.05
153
+ },
154
+ "defaultPriority": 0.5,
155
+ "combineMode": "max",
156
+ "priorityInfluence": 0.5
157
+ }
158
+ ```
159
+
160
+ **UI Requirements:**
161
+ - Tag list with priority sliders (0-1)
162
+ - Default priority input
163
+ - Combine mode selector (max/average/min)
164
+ - Priority influence slider
165
+ - Delegate strategy selector
166
+
167
+ ---
168
+
169
+ ## Implementation Options
170
+
171
+ ### Option A: Extend NavigationStrategyEditor.vue
172
+
173
+ **Pros:**
174
+ - Single location for all strategy editing
175
+ - Consistent with existing UI patterns
176
+ - User-facing, accessible to course authors
177
+
178
+ **Cons:**
179
+ - More complex Vue component
180
+ - Requires form validation logic
181
+
182
+ **Implementation:**
183
+ 1. Add strategy type selector dropdown
184
+ 2. Conditional rendering of config forms based on type
185
+ 3. Form validation before save
186
+ 4. Serialize config to `serializedData` JSON
187
+
188
+ ---
189
+
190
+ ### Option B: CLI Commands
191
+
192
+ **Pros:**
193
+ - Scriptable, automatable
194
+ - Good for bulk operations
195
+ - Can be used by agents
196
+
197
+ **Cons:**
198
+ - Not user-friendly for non-technical authors
199
+ - Requires terminal access
200
+
201
+ **Implementation:**
202
+ ```bash
203
+ # Create a hierarchy strategy
204
+ yarn cli strategy:create <courseId> \
205
+ --type hierarchy \
206
+ --name "Phonics Progression" \
207
+ --config ./hierarchy-config.json
208
+
209
+ # Create an interference strategy
210
+ yarn cli strategy:create <courseId> \
211
+ --type interference \
212
+ --config ./interference-config.json
213
+
214
+ # List strategies
215
+ yarn cli strategy:list <courseId>
216
+
217
+ # Set default strategy
218
+ yarn cli strategy:set-default <courseId> <strategyId>
219
+
220
+ # Delete strategy
221
+ yarn cli strategy:delete <courseId> <strategyId>
222
+ ```
223
+
224
+ **Files to modify:**
225
+ - `packages/cli/src/commands/` — Add strategy commands
226
+ - `packages/cli/src/index.ts` — Register new commands
227
+
228
+ ---
229
+
230
+ ### Option C: MCP Tools
231
+
232
+ **Pros:**
233
+ - Agent-accessible for automated course building
234
+ - Consistent with existing MCP pattern
235
+ - Can leverage LLM for config generation
236
+
237
+ **Cons:**
238
+ - Requires MCP client
239
+ - Not directly user-facing
240
+
241
+ **Implementation:**
242
+ Add to `packages/mcp/src/tools/`:
243
+
244
+ ```typescript
245
+ // create_navigation_strategy tool
246
+ {
247
+ name: 'create_navigation_strategy',
248
+ description: 'Create a navigation strategy for the course',
249
+ inputSchema: {
250
+ type: 'object',
251
+ properties: {
252
+ strategyType: { enum: ['hierarchy', 'interference', 'relativePriority'] },
253
+ name: { type: 'string' },
254
+ description: { type: 'string' },
255
+ config: { type: 'object' }
256
+ }
257
+ }
258
+ }
259
+ ```
260
+
261
+ ---
262
+
263
+ ## Implementation Completed: UI-First Approach ✅
264
+
265
+ **Phase 1: UI Forms (COMPLETED)**
266
+ - ✅ All four strategy configuration forms implemented
267
+ - ✅ Visual editor with guided inputs
268
+ - ✅ JSON editor for power users
269
+ - ✅ Tag selectors, sliders, multi-selects
270
+ - ✅ Validation and error messages
271
+ - ✅ Edit/Create/Delete functionality
272
+ - ✅ Compact two-column layout (list + form)
273
+
274
+ **Phase 2 (Optional): CLI Commands**
275
+ - Scriptable strategy creation from JSON files
276
+ - Bulk operations and automation
277
+ - Terminal-based workflows
278
+ - **Status:** Not started, optional enhancement
279
+
280
+ **Phase 3 (Optional): MCP Tools**
281
+ - Agent-accessible strategy authoring
282
+ - LLM-guided config generation
283
+ - Extends existing MCP infrastructure
284
+ - **Status:** Not started, optional enhancement
285
+
286
+ ---
287
+
288
+ ## Validation Requirements
289
+
290
+ All authoring tools should validate:
291
+
292
+ 1. **Tag existence**: All referenced tags exist in the course
293
+ 2. **Circular dependencies** (Hierarchy): No circular prerequisite chains
294
+ 3. **Config completeness**: Required fields present
295
+ 4. **Value ranges**: Scores/thresholds in valid ranges (e.g., 0-1)
296
+ 5. **Delegate validity**: Delegate strategy type exists
297
+
298
+ ```typescript
299
+ interface ValidationResult {
300
+ valid: boolean;
301
+ errors: Array<{
302
+ field: string;
303
+ message: string;
304
+ }>;
305
+ warnings: Array<{
306
+ field: string;
307
+ message: string;
308
+ }>;
309
+ }
310
+ ```
311
+
312
+ ---
313
+
314
+ ## Files Created/Modified
315
+
316
+ ### UI Implementation (COMPLETED ✅)
317
+
318
+ | File | Action | Description | Status |
319
+ |------|--------|-------------|--------|
320
+ | `packages/edit-ui/src/components/NavigationStrategy/HardcodedOrderConfigForm.vue` | CREATED | Card ID list form | ✅ |
321
+ | `packages/edit-ui/src/components/NavigationStrategy/HierarchyConfigForm.vue` | CREATED | Prerequisite gating config | ✅ |
322
+ | `packages/edit-ui/src/components/NavigationStrategy/InterferenceConfigForm.vue` | CREATED | Interference groups config | ✅ |
323
+ | `packages/edit-ui/src/components/NavigationStrategy/RelativePriorityConfigForm.vue` | CREATED | Tag priorities config | ✅ |
324
+ | `packages/edit-ui/src/components/NavigationStrategy/NavigationStrategyEditor.vue` | MODIFIED | Two-column layout, all strategy types | ✅ |
325
+ | `packages/edit-ui/src/components/NavigationStrategy/NavigationStrategyList.vue` | MODIFIED | Compact density, simplified display | ✅ |
326
+
327
+ ### CLI Implementation (OPTIONAL, NOT STARTED)
328
+
329
+ | File | Action | Description | Status |
330
+ |------|--------|-------------|--------|
331
+ | `packages/cli/src/commands/strategy.ts` | CREATE | Strategy management commands | ⏸️ |
332
+ | `packages/cli/src/utils/strategy-validation.ts` | CREATE | Config validation utilities | ⏸️ |
333
+ | `packages/cli/src/index.ts` | MODIFY | Register strategy commands | ⏸️ |
334
+
335
+ ### MCP Implementation (OPTIONAL, NOT STARTED)
336
+
337
+ | File | Action | Description | Status |
338
+ |------|--------|-------------|--------|
339
+ | `packages/mcp/src/tools/create-navigation-strategy.ts` | CREATE | Create strategy tool | ⏸️ |
340
+ | `packages/mcp/src/tools/update-navigation-strategy.ts` | CREATE | Update strategy tool | ⏸️ |
341
+ | `packages/mcp/src/tools/list-navigation-strategies.ts` | CREATE | List strategies tool | ⏸️ |
342
+ | `packages/mcp/src/tools/delete-navigation-strategy.ts` | CREATE | Delete strategy tool | ⏸️ |
343
+ | `packages/mcp/src/types/tools.ts` | MODIFY | Add strategy schemas | ⏸️ |
344
+ | `packages/mcp/src/tools/index.ts` | MODIFY | Export new tools | ⏸️ |
345
+
346
+ ---
347
+
348
+ ## Key Features Delivered
349
+
350
+ ### Dual Input Modes
351
+ All forms support two modes for flexibility:
352
+ - **Visual Editor**: Guided UI with selectors, sliders, and inputs
353
+ - **JSON Editor**: Direct JSON editing with real-time validation
354
+
355
+ ### Strategy-Specific Capabilities
356
+
357
+ **HierarchyConfigForm:**
358
+ - Add/remove prerequisite rules
359
+ - Tag selectors for gated tags and their prerequisites
360
+ - Mastery thresholds (minCount, minElo)
361
+ - Delegate strategy selector
362
+
363
+ **InterferenceConfigForm:**
364
+ - Add/remove interference groups
365
+ - Multi-tag selector for each group
366
+ - Per-group decay sliders (0-1)
367
+ - Maturity threshold configuration (minCount, minElo, minElapsedDays)
368
+ - Default decay setting
369
+
370
+ **RelativePriorityConfigForm:**
371
+ - Individual priority sliders for all course tags
372
+ - Default priority configuration
373
+ - Combine mode selector (max/average/min)
374
+ - Priority influence slider
375
+
376
+ ### User Experience
377
+ - **Compact Layout**: Two-column design (strategy list + form)
378
+ - **Always Visible**: No modal dialogs, form always accessible
379
+ - **Responsive**: Works on desktop and mobile
380
+ - **Real-time Validation**: Inline error messages
381
+ - **Tag Loading**: Automatically fetches course tags
382
+ - **Edit Support**: Click any strategy to edit
383
+
384
+ ---
385
+
386
+ ## Access
387
+
388
+ The NavigationStrategy editor is accessible in:
389
+ - **studio-ui**: `/course-editor` route → "Navigation" tab
390
+ - **platform-ui**: `/edit/:courseId` route → "Navigation" tab
391
+
392
+ ---
393
+
394
+ ## Related Files
395
+
396
+ - `packages/db/src/core/types/contentNavigationStrategy.ts` — Strategy data schema
397
+ - `packages/db/src/impl/couch/courseDB.ts` — DB methods for strategies
398
+ - `packages/db/src/core/navigators/hierarchyDefinition.ts` — Config interface reference
399
+ - `packages/db/src/core/navigators/interferenceMitigator.ts` — Config interface reference
400
+ - `packages/db/src/core/navigators/relativePriority.ts` — Config interface reference
401
+ - `packages/edit-ui/src/components/NavigationStrategy/` — UI components (all forms)
package/eslint.config.mjs CHANGED
@@ -3,7 +3,7 @@ import backendConfig from '../../eslint.config.backend.mjs';
3
3
  export default [
4
4
  ...backendConfig,
5
5
  {
6
- ignores: ['node_modules/**', 'dist/**', 'eslint.config.mjs', 'tsup.config.ts'],
6
+ ignores: ['node_modules/**', 'dist/**', 'eslint.config.mjs', 'tsup.config.ts', 'vitest.config.ts'],
7
7
  },
8
8
  {
9
9
  languageOptions: {
package/package.json CHANGED
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "name": "@vue-skuilder/db",
3
+ "type": "module",
3
4
  "publishConfig": {
4
5
  "access": "public"
5
6
  },
6
- "version": "0.1.17",
7
+ "version": "0.1.20",
7
8
  "description": "Database layer for vue-skuilder",
8
9
  "main": "dist/index.js",
9
10
  "module": "dist/index.mjs",
@@ -39,13 +40,15 @@
39
40
  "build": "tsup",
40
41
  "build:debug": "tsup --sourcemap inline",
41
42
  "dev": "tsup --watch",
43
+ "test": "vitest run",
44
+ "test:watch": "vitest",
42
45
  "lint": "npx eslint .",
43
46
  "lint:fix": "npx eslint . --fix",
44
47
  "lint:check": "npx eslint . --max-warnings 0"
45
48
  },
46
49
  "dependencies": {
47
50
  "@nilock2/pouchdb-authentication": "^1.0.2",
48
- "@vue-skuilder/common": "0.1.17",
51
+ "@vue-skuilder/common": "0.1.20",
49
52
  "cross-fetch": "^4.1.0",
50
53
  "moment": "^2.29.4",
51
54
  "pouchdb": "^9.0.0",
@@ -55,7 +58,9 @@
55
58
  "devDependencies": {
56
59
  "@types/uuid": "^10.0.0",
57
60
  "tsup": "^8.0.2",
58
- "typescript": "~5.7.2"
61
+ "typescript": "~5.9.3",
62
+ "vite": "^7.0.0",
63
+ "vitest": "^4.0.15"
59
64
  },
60
- "stableVersion": "0.1.17"
65
+ "stableVersion": "0.1.20"
61
66
  }
package/src/core/index.ts CHANGED
@@ -3,6 +3,7 @@
3
3
  export * from './interfaces';
4
4
  export * from './types/types-legacy';
5
5
  export * from './types/user';
6
+ export * from './types/strategyState';
6
7
  export * from '../util/Loggable';
7
8
  export * from './util';
8
9
  export * from './navigators';
@@ -2,6 +2,41 @@ import { getDataLayer } from '@db/factory';
2
2
  import { UserDBInterface } from '..';
3
3
  import { StudentClassroomDB } from '../../impl/couch/classroomDB';
4
4
  import { ScheduledCard } from '@db/core/types/user';
5
+ import { WeightedCard } from '../navigators';
6
+ import { TagFilter, hasActiveFilter } from '@vue-skuilder/common';
7
+ import { TagFilteredContentSource } from '../../study/TagFilteredContentSource';
8
+
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
+ // ============================================================================
5
40
 
6
41
  export type StudySessionFailedItem = StudySessionFailedNewItem | StudySessionFailedReviewItem;
7
42
 
@@ -43,12 +78,60 @@ export interface StudySessionItem {
43
78
  export interface ContentSourceID {
44
79
  type: 'course' | 'classroom';
45
80
  id: string;
81
+ /**
82
+ * Optional tag filter for scoped study sessions.
83
+ * When present, creates a TagFilteredContentSource instead of a regular course source.
84
+ */
85
+ tagFilter?: TagFilter;
46
86
  }
47
87
 
48
88
  // #region docs_StudyContentSource
89
+ /**
90
+ * Interface for sources that provide study content to SessionController.
91
+ *
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
100
+ *
101
+ * See: packages/db/src/core/navigators/ARCHITECTURE.md
102
+ */
49
103
  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
+ */
50
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
+ */
51
120
  getNewCards(n?: number): Promise<StudySessionNewItem[]>;
121
+
122
+ /**
123
+ * Get cards with suitability scores for presentation.
124
+ *
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.
130
+ *
131
+ * @param limit - Maximum number of cards to return
132
+ * @returns Cards sorted by score descending
133
+ */
134
+ getWeightedCards?(limit: number): Promise<WeightedCard[]>;
52
135
  }
53
136
  // #endregion docs_StudyContentSource
54
137
 
@@ -59,11 +142,12 @@ export async function getStudySource(
59
142
  if (source.type === 'classroom') {
60
143
  return await StudentClassroomDB.factory(source.id, user);
61
144
  } else {
62
- // if (source.type === 'course') - removed so tsc is certain something returns
63
- // return new CourseDB(source.id, async () => {
64
- // return user;
65
- // });
145
+ // Check if this is a tag-filtered course source
146
+ if (hasActiveFilter(source.tagFilter)) {
147
+ return new TagFilteredContentSource(source.id, source.tagFilter!, user);
148
+ }
66
149
 
150
+ // Regular course source
67
151
  return getDataLayer().getCourseDB(source.id) as unknown as StudyContentSource;
68
152
  }
69
153
  }