@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.
- package/dist/{userDB-BqwxtJ_7.d.mts → classroomDB-CZdMBiTU.d.ts} +427 -104
- package/dist/{userDB-DNa0XPtn.d.ts → classroomDB-PxDZTky3.d.cts} +427 -104
- package/dist/core/index.d.cts +304 -0
- package/dist/core/index.d.ts +237 -25
- package/dist/core/index.js +2246 -118
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +2235 -114
- package/dist/core/index.mjs.map +1 -1
- package/dist/{dataLayerProvider-VlngD19_.d.mts → dataLayerProvider-D0MoZMjH.d.cts} +1 -1
- package/dist/{dataLayerProvider-BV5iZqt_.d.ts → dataLayerProvider-D8o6ZnKW.d.ts} +1 -1
- package/dist/impl/couch/{index.d.mts → index.d.cts} +47 -5
- package/dist/impl/couch/index.d.ts +46 -4
- package/dist/impl/couch/index.js +2250 -134
- package/dist/impl/couch/index.js.map +1 -1
- package/dist/impl/couch/index.mjs +2212 -97
- package/dist/impl/couch/index.mjs.map +1 -1
- package/dist/impl/static/{index.d.mts → index.d.cts} +6 -6
- package/dist/impl/static/index.d.ts +5 -5
- package/dist/impl/static/index.js +1950 -143
- package/dist/impl/static/index.js.map +1 -1
- package/dist/impl/static/index.mjs +1922 -117
- package/dist/impl/static/index.mjs.map +1 -1
- package/dist/{index-Bmll7Xse.d.mts → index-B_j6u5E4.d.cts} +1 -1
- package/dist/{index-CD8BZz2k.d.ts → index-Dj0SEgk3.d.ts} +1 -1
- package/dist/{index.d.mts → index.d.cts} +97 -13
- package/dist/index.d.ts +96 -12
- package/dist/index.js +2439 -180
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2386 -135
- package/dist/index.mjs.map +1 -1
- package/dist/pouch/index.js +3 -3
- package/dist/{types-Dbp5DaRR.d.mts → types-Bn0itutr.d.cts} +1 -1
- package/dist/{types-CewsN87z.d.ts → types-DQaXnuoc.d.ts} +1 -1
- package/dist/{types-legacy-6ettoclI.d.ts → types-legacy-DDY4N-Uq.d.cts} +3 -1
- package/dist/{types-legacy-6ettoclI.d.mts → types-legacy-DDY4N-Uq.d.ts} +3 -1
- package/dist/util/packer/{index.d.mts → index.d.cts} +3 -3
- package/dist/util/packer/index.d.ts +3 -3
- package/dist/util/packer/index.js.map +1 -1
- package/dist/util/packer/index.mjs.map +1 -1
- package/docs/brainstorm-navigation-paradigm.md +369 -0
- package/docs/navigators-architecture.md +370 -0
- package/docs/todo-evolutionary-orchestration.md +310 -0
- package/docs/todo-nominal-tag-types.md +121 -0
- package/docs/todo-strategy-authoring.md +401 -0
- package/eslint.config.mjs +1 -1
- package/package.json +9 -4
- package/src/core/index.ts +1 -0
- package/src/core/interfaces/contentSource.ts +88 -4
- package/src/core/interfaces/courseDB.ts +13 -0
- package/src/core/interfaces/navigationStrategyManager.ts +0 -5
- package/src/core/interfaces/userDB.ts +32 -0
- package/src/core/navigators/CompositeGenerator.ts +268 -0
- package/src/core/navigators/Pipeline.ts +318 -0
- package/src/core/navigators/PipelineAssembler.ts +194 -0
- package/src/core/navigators/elo.ts +104 -15
- package/src/core/navigators/filters/eloDistance.ts +132 -0
- package/src/core/navigators/filters/index.ts +9 -0
- package/src/core/navigators/filters/types.ts +115 -0
- package/src/core/navigators/filters/userTagPreference.ts +232 -0
- package/src/core/navigators/generators/index.ts +2 -0
- package/src/core/navigators/generators/types.ts +107 -0
- package/src/core/navigators/hardcodedOrder.ts +111 -12
- package/src/core/navigators/hierarchyDefinition.ts +266 -0
- package/src/core/navigators/index.ts +404 -3
- package/src/core/navigators/inferredPreference.ts +107 -0
- package/src/core/navigators/interferenceMitigator.ts +355 -0
- package/src/core/navigators/relativePriority.ts +255 -0
- package/src/core/navigators/srs.ts +195 -0
- package/src/core/navigators/userGoal.ts +136 -0
- package/src/core/types/strategyState.ts +84 -0
- package/src/core/types/types-legacy.ts +2 -0
- package/src/impl/common/BaseUserDB.ts +74 -7
- package/src/impl/couch/adminDB.ts +1 -2
- package/src/impl/couch/classroomDB.ts +51 -0
- package/src/impl/couch/courseDB.ts +147 -49
- package/src/impl/static/courseDB.ts +11 -4
- package/src/study/SessionController.ts +149 -1
- package/src/study/TagFilteredContentSource.ts +255 -0
- package/src/study/index.ts +1 -0
- package/src/util/dataDirectory.test.ts +51 -22
- package/src/util/logger.ts +0 -1
- package/tests/core/navigators/CompositeGenerator.test.ts +455 -0
- package/tests/core/navigators/Pipeline.test.ts +406 -0
- package/tests/core/navigators/PipelineAssembler.test.ts +351 -0
- package/tests/core/navigators/SRSNavigator.test.ts +344 -0
- package/tests/core/navigators/eloDistanceFilter.test.ts +192 -0
- package/tests/core/navigators/navigators.test.ts +710 -0
- package/tsconfig.json +1 -1
- package/vitest.config.ts +29 -0
- package/dist/core/index.d.mts +0 -92
- /package/dist/{SyncStrategy-CyATpyLQ.d.mts → SyncStrategy-CyATpyLQ.d.cts} +0 -0
- /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.
|
|
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.
|
|
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.
|
|
61
|
+
"typescript": "~5.9.3",
|
|
62
|
+
"vite": "^7.0.0",
|
|
63
|
+
"vitest": "^4.0.15"
|
|
59
64
|
},
|
|
60
|
-
"stableVersion": "0.1.
|
|
65
|
+
"stableVersion": "0.1.20"
|
|
61
66
|
}
|
package/src/core/index.ts
CHANGED
|
@@ -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
|
|
63
|
-
|
|
64
|
-
|
|
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
|
}
|