@vue-skuilder/db 0.1.18 → 0.1.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/CLAUDE.md +2 -2
  2. package/dist/{classroomDB-BgfrVb8d.d.ts → contentSource-BP9hznNV.d.ts} +220 -197
  3. package/dist/{classroomDB-CTOenngH.d.cts → contentSource-DsJadoBU.d.cts} +220 -197
  4. package/dist/core/index.d.cts +80 -6
  5. package/dist/core/index.d.ts +80 -6
  6. package/dist/core/index.js +735 -1560
  7. package/dist/core/index.js.map +1 -1
  8. package/dist/core/index.mjs +708 -1539
  9. package/dist/core/index.mjs.map +1 -1
  10. package/dist/{dataLayerProvider-D6PoCwS6.d.cts → dataLayerProvider-CHYrQ5pB.d.cts} +1 -1
  11. package/dist/{dataLayerProvider-CZxC9GtB.d.ts → dataLayerProvider-MDTxXq2l.d.ts} +1 -1
  12. package/dist/impl/couch/index.d.cts +8 -23
  13. package/dist/impl/couch/index.d.ts +8 -23
  14. package/dist/impl/couch/index.js +723 -1578
  15. package/dist/impl/couch/index.js.map +1 -1
  16. package/dist/impl/couch/index.mjs +692 -1552
  17. package/dist/impl/couch/index.mjs.map +1 -1
  18. package/dist/impl/static/index.d.cts +25 -8
  19. package/dist/impl/static/index.d.ts +25 -8
  20. package/dist/impl/static/index.js +700 -1400
  21. package/dist/impl/static/index.js.map +1 -1
  22. package/dist/impl/static/index.mjs +688 -1393
  23. package/dist/impl/static/index.mjs.map +1 -1
  24. package/dist/{index-D-Fa4Smt.d.cts → index-B_j6u5E4.d.cts} +1 -1
  25. package/dist/{index-CD8BZz2k.d.ts → index-Dj0SEgk3.d.ts} +1 -1
  26. package/dist/index.d.cts +71 -63
  27. package/dist/index.d.ts +71 -63
  28. package/dist/index.js +1162 -1996
  29. package/dist/index.js.map +1 -1
  30. package/dist/index.mjs +1124 -1955
  31. package/dist/index.mjs.map +1 -1
  32. package/dist/pouch/index.js +3 -0
  33. package/dist/pouch/index.js.map +1 -1
  34. package/dist/pouch/index.mjs +3 -0
  35. package/dist/pouch/index.mjs.map +1 -1
  36. package/dist/{types-CzPDLAK6.d.cts → types-Bn0itutr.d.cts} +1 -1
  37. package/dist/{types-CewsN87z.d.ts → types-DQaXnuoc.d.ts} +1 -1
  38. package/dist/{types-legacy-6ettoclI.d.cts → types-legacy-DDY4N-Uq.d.cts} +3 -1
  39. package/dist/{types-legacy-6ettoclI.d.ts → types-legacy-DDY4N-Uq.d.ts} +3 -1
  40. package/dist/util/packer/index.d.cts +3 -3
  41. package/dist/util/packer/index.d.ts +3 -3
  42. package/docs/navigators-architecture.md +115 -17
  43. package/package.json +4 -4
  44. package/src/core/index.ts +1 -0
  45. package/src/core/interfaces/classroomDB.ts +5 -13
  46. package/src/core/interfaces/contentSource.ts +6 -66
  47. package/src/core/interfaces/courseDB.ts +15 -7
  48. package/src/core/interfaces/userDB.ts +32 -0
  49. package/src/core/navigators/Pipeline.ts +136 -52
  50. package/src/core/navigators/PipelineAssembler.ts +1 -1
  51. package/src/core/navigators/defaults.ts +84 -0
  52. package/src/core/navigators/{hierarchyDefinition.ts → filters/hierarchyDefinition.ts} +15 -29
  53. package/src/core/navigators/filters/index.ts +3 -0
  54. package/src/core/navigators/filters/inferredPreferenceStub.ts +107 -0
  55. package/src/core/navigators/{interferenceMitigator.ts → filters/interferenceMitigator.ts} +11 -37
  56. package/src/core/navigators/{relativePriority.ts → filters/relativePriority.ts} +12 -38
  57. package/src/core/navigators/filters/userGoalStub.ts +136 -0
  58. package/src/core/navigators/filters/userTagPreference.ts +217 -0
  59. package/src/core/navigators/{CompositeGenerator.ts → generators/CompositeGenerator.ts} +15 -64
  60. package/src/core/navigators/{elo.ts → generators/elo.ts} +13 -63
  61. package/src/core/navigators/{srs.ts → generators/srs.ts} +11 -40
  62. package/src/core/navigators/generators/types.ts +1 -1
  63. package/src/core/navigators/index.ts +95 -91
  64. package/src/core/types/strategyState.ts +84 -0
  65. package/src/core/types/types-legacy.ts +2 -0
  66. package/src/impl/common/BaseUserDB.ts +74 -7
  67. package/src/impl/couch/adminDB.ts +1 -2
  68. package/src/impl/couch/classroomDB.ts +100 -103
  69. package/src/impl/couch/courseDB.ts +35 -91
  70. package/src/impl/couch/pouchdb-setup.ts +7 -0
  71. package/src/impl/static/StaticDataUnpacker.ts +50 -1
  72. package/src/impl/static/courseDB.ts +87 -37
  73. package/src/study/SessionController.ts +122 -202
  74. package/src/study/SourceMixer.ts +65 -0
  75. package/src/study/TagFilteredContentSource.ts +49 -92
  76. package/src/study/index.ts +1 -0
  77. package/src/study/services/CardHydrationService.ts +165 -81
  78. package/src/util/dataDirectory.ts +1 -1
  79. package/src/util/index.ts +0 -1
  80. package/tests/core/navigators/CompositeGenerator.test.ts +44 -168
  81. package/tests/core/navigators/Pipeline.test.ts +6 -72
  82. package/tests/core/navigators/PipelineAssembler.test.ts +8 -58
  83. package/tests/core/navigators/navigators.test.ts +118 -151
  84. package/docs/todo-pipeline-optimization.md +0 -117
  85. package/docs/todo-strategy-state-storage.md +0 -278
  86. package/src/core/navigators/hardcodedOrder.ts +0 -163
  87. package/src/util/tuiLogger.ts +0 -139
@@ -1,278 +0,0 @@
1
- # TODO: Strategy-Specific State Storage in UserDB
2
-
3
- ## Status: NOT STARTED
4
-
5
- ## Goal
6
-
7
- Enable NavigationStrategies (ContentNavigators) to persist their own state data in the
8
- user's database, allowing strategies to maintain context across sessions.
9
-
10
- ## Current State
11
-
12
- ### What Strategies Can Read
13
-
14
- | Data | Method | Notes |
15
- |------|--------|-------|
16
- | User's global ELO | `user.getCourseRegDoc(courseId).elo.global` | ✅ Available |
17
- | User's per-tag ELO | `user.getCourseRegDoc(courseId).elo.tags` | ✅ Available |
18
- | Seen cards | `user.getSeenCards(courseId)` | ✅ Card IDs only |
19
- | Active cards | `user.getActiveCards()` | ✅ Available |
20
- | Pending reviews | `user.getPendingReviews(courseId)` | ✅ ScheduledCard objects |
21
- | Card history | `user.putCardRecord()` returns `CardHistory` | 🟡 Only after write |
22
-
23
- ### What Strategies Cannot Do
24
-
25
- - **Store arbitrary state**: No namespaced storage for strategy-specific data
26
- - **Track temporal patterns**: No easy way to record "when did I last introduce tag X?"
27
- - **Persist learning context**: Strategy state is lost between sessions
28
-
29
- ## Use Cases
30
-
31
- ### 1. InterferenceMitigator
32
-
33
- **Need**: Track when interfering concepts were last introduced together.
34
-
35
- ```typescript
36
- // Desired: Store last introduction time per tag
37
- {
38
- "lastIntroduction": {
39
- "letter-b": "2024-01-15T10:30:00Z",
40
- "letter-d": "2024-01-16T14:20:00Z"
41
- }
42
- }
43
- ```
44
-
45
- ### 2. Minimal Pairs Strategy (Future)
46
-
47
- **Need**: Track discrimination training progress between confusable pairs.
48
-
49
- ```typescript
50
- // Desired: Store discrimination scores per pair
51
- {
52
- "pairScores": {
53
- "b-d": { "correct": 15, "total": 20, "lastPracticed": "..." },
54
- "m-n": { "correct": 8, "total": 10, "lastPracticed": "..." }
55
- }
56
- }
57
- ```
58
-
59
- ### 3. Adaptive Pacing Strategy (Future)
60
-
61
- **Need**: Track user's engagement patterns and optimal session timing.
62
-
63
- ```typescript
64
- // Desired: Store engagement metrics
65
- {
66
- "sessionMetrics": {
67
- "avgAccuracyByTimeOfDay": { "morning": 0.85, "afternoon": 0.78 },
68
- "optimalSessionLength": 180, // seconds
69
- "fatigueThreshold": 12 // cards before accuracy drops
70
- }
71
- }
72
- ```
73
-
74
- ## Proposed Solutions
75
-
76
- ### Option A: Extend CourseRegistration
77
-
78
- Add a `strategyState` field to the existing `CourseRegistration` document.
79
-
80
- **Schema:**
81
- ```typescript
82
- interface CourseRegistration {
83
- // ... existing fields ...
84
-
85
- /**
86
- * Strategy-specific state, keyed by strategy ID or type.
87
- * Each strategy owns its own namespace.
88
- */
89
- strategyState?: {
90
- [strategyKey: string]: unknown;
91
- };
92
- }
93
- ```
94
-
95
- **Pros:**
96
- - No new document types
97
- - Lives alongside other course-specific user data
98
- - Already synced via existing mechanisms
99
-
100
- **Cons:**
101
- - Potential for large documents if strategies store lots of data
102
- - All strategies share one document (contention on updates)
103
-
104
- ---
105
-
106
- ### Option B: Separate Strategy State Documents
107
-
108
- Create a new document type for strategy state.
109
-
110
- **Schema:**
111
- ```typescript
112
- interface StrategyStateDoc {
113
- _id: `STRATEGY_STATE-${courseId}-${strategyKey}`;
114
- docType: DocType.STRATEGY_STATE;
115
- courseId: string;
116
- strategyKey: string; // e.g., "interferenceMitigator" or strategy instance ID
117
- data: unknown;
118
- updatedAt: string;
119
- }
120
- ```
121
-
122
- **Pros:**
123
- - Clean separation
124
- - No document size concerns
125
- - Independent updates (no contention)
126
-
127
- **Cons:**
128
- - New document type to manage
129
- - More queries to fetch state
130
-
131
- ---
132
-
133
- ### Option C: Generic Key-Value Store in UserDB
134
-
135
- Add generic methods for namespaced storage.
136
-
137
- **Interface:**
138
- ```typescript
139
- interface UserDBWriter {
140
- // ... existing methods ...
141
-
142
- /**
143
- * Store data in a namespaced location.
144
- * @param namespace - Unique namespace (e.g., "strategy:interferenceMitigator:course123")
145
- * @param data - Arbitrary JSON-serializable data
146
- */
147
- putNamespacedData(namespace: string, data: unknown): Promise<void>;
148
-
149
- /**
150
- * Retrieve namespaced data.
151
- * @param namespace - The namespace to retrieve
152
- * @returns The stored data, or null if not found
153
- */
154
- getNamespacedData<T>(namespace: string): Promise<T | null>;
155
-
156
- /**
157
- * Delete namespaced data.
158
- * @param namespace - The namespace to delete
159
- */
160
- deleteNamespacedData(namespace: string): Promise<void>;
161
- }
162
- ```
163
-
164
- **Pros:**
165
- - Most flexible
166
- - Reusable beyond strategies
167
- - Clean API
168
-
169
- **Cons:**
170
- - Very generic (might be too permissive)
171
- - Namespace collision risk
172
-
173
- ---
174
-
175
- ## Recommended Approach
176
-
177
- **Option B (Separate Documents)** with a convenience wrapper:
178
-
179
- ```typescript
180
- // In ContentNavigator base class or a mixin
181
- abstract class ContentNavigator {
182
- // ... existing methods ...
183
-
184
- /**
185
- * Get this strategy's persisted state for the current course.
186
- */
187
- protected async getStrategyState<T>(): Promise<T | null> {
188
- const key = `STRATEGY_STATE-${this.course.getCourseID()}-${this.strategyKey}`;
189
- try {
190
- return await this.user.get<T>(key);
191
- } catch (e) {
192
- return null; // Not found
193
- }
194
- }
195
-
196
- /**
197
- * Persist this strategy's state for the current course.
198
- */
199
- protected async putStrategyState<T>(data: T): Promise<void> {
200
- const key = `STRATEGY_STATE-${this.course.getCourseID()}-${this.strategyKey}`;
201
- const existing = await this.getStrategyState<T>();
202
- await this.user.put({
203
- _id: key,
204
- _rev: existing?._rev,
205
- docType: DocType.STRATEGY_STATE,
206
- courseId: this.course.getCourseID(),
207
- strategyKey: this.strategyKey,
208
- data,
209
- updatedAt: new Date().toISOString(),
210
- });
211
- }
212
-
213
- /**
214
- * Unique key for this strategy instance.
215
- * Override in subclasses if multiple instances need separate state.
216
- */
217
- protected get strategyKey(): string {
218
- return this.constructor.name; // e.g., "InterferenceMitigatorNavigator"
219
- }
220
- }
221
- ```
222
-
223
- ## Implementation Plan
224
-
225
- ### Phase 1: Add DocType and Interface
226
-
227
- 1. Add `STRATEGY_STATE` to `DocType` enum in `packages/db/src/core/types/types-legacy.ts`
228
- 2. Define `StrategyStateDoc` interface
229
- 3. Add prefix to `DocTypePrefixes`
230
-
231
- ### Phase 2: Add UserDB Methods
232
-
233
- 1. Add `getStrategyState()` and `putStrategyState()` to `UserDBInterface`
234
- 2. Implement in `CouchUserDB`
235
-
236
- ### Phase 3: Add ContentNavigator Helpers
237
-
238
- 1. Add protected helper methods to `ContentNavigator` base class
239
- 2. Document usage pattern
240
-
241
- ### Phase 4: Update Strategies
242
-
243
- 1. Update `InterferenceMitigatorNavigator` to use state storage
244
- 2. Add tests for state persistence
245
-
246
- ## Files to Create/Modify
247
-
248
- | File | Action | Description |
249
- |------|--------|-------------|
250
- | `packages/db/src/core/types/types-legacy.ts` | MODIFY | Add STRATEGY_STATE DocType |
251
- | `packages/db/src/core/types/strategyState.ts` | CREATE | StrategyStateDoc interface |
252
- | `packages/db/src/core/interfaces/userDB.ts` | MODIFY | Add state storage methods |
253
- | `packages/db/src/impl/couch/userDB.ts` | MODIFY | Implement state storage |
254
- | `packages/db/src/core/navigators/index.ts` | MODIFY | Add helper methods to base class |
255
- | `packages/db/src/core/navigators/interferenceMitigator.ts` | MODIFY | Use state storage |
256
-
257
- ## Test Cases
258
-
259
- 1. **Store and retrieve**: Write state, read it back, verify equality
260
- 2. **Update existing**: Write state, update it, verify new value
261
- 3. **Separate namespaces**: Two strategies store data, each gets their own
262
- 4. **Cross-session persistence**: Store state, simulate new session, verify data persists
263
- 5. **Missing state**: Read state that doesn't exist, get null (not error)
264
-
265
- ## Open Questions
266
-
267
- 1. **State migration**: How to handle strategy state when strategy config changes?
268
- 2. **State cleanup**: When a strategy is deleted, should its state be cleaned up?
269
- 3. **State size limits**: Should we enforce maximum state size?
270
- 4. **Sync behavior**: How does state sync across devices for multi-device users?
271
-
272
- ## Related Files
273
-
274
- - `packages/db/src/core/interfaces/userDB.ts` — UserDB interface
275
- - `packages/db/src/impl/couch/userDB.ts` — UserDB implementation
276
- - `packages/db/src/core/navigators/index.ts` — ContentNavigator base class
277
- - `packages/db/src/core/types/types-legacy.ts` — DocType enum
278
- - `packages/db/docs/navigators-architecture.md` — Architecture overview
@@ -1,163 +0,0 @@
1
- import type {
2
- CourseDBInterface,
3
- QualifiedCardID,
4
- StudySessionNewItem,
5
- StudySessionReviewItem,
6
- UserDBInterface,
7
- } from '..';
8
- import type { ContentNavigationStrategyData } from '../types/contentNavigationStrategy';
9
- import type { ScheduledCard } from '../types/user';
10
- import { ContentNavigator } from './index';
11
- import type { WeightedCard } from './index';
12
- import type { CardGenerator, GeneratorContext } from './generators/types';
13
- import { logger } from '../../util/logger';
14
-
15
- // ============================================================================
16
- // HARDCODED ORDER NAVIGATOR
17
- // ============================================================================
18
- //
19
- // A generator strategy that presents cards in a fixed, author-defined order.
20
- //
21
- // Use case: When course authors want explicit control over content sequencing,
22
- // e.g., teaching letters in a specific pedagogical order.
23
- //
24
- // The order is defined in serializedData as a JSON array of card IDs.
25
- // Earlier positions in the array get higher scores.
26
- //
27
- // ============================================================================
28
-
29
- /**
30
- * A navigation strategy that presents cards in a fixed order.
31
- *
32
- * Implements CardGenerator for use in Pipeline architecture.
33
- * Also extends ContentNavigator for backward compatibility with legacy code.
34
- *
35
- * Scoring:
36
- * - Earlier cards in the sequence get higher scores
37
- * - Reviews get score 1.0 (highest priority)
38
- * - New cards scored by position: 1.0 - (position / total) * 0.5
39
- */
40
- export default class HardcodedOrderNavigator extends ContentNavigator implements CardGenerator {
41
- /** Human-readable name for CardGenerator interface */
42
- name: string;
43
-
44
- private orderedCardIds: string[] = [];
45
-
46
- constructor(
47
- user: UserDBInterface,
48
- course: CourseDBInterface,
49
- strategyData: ContentNavigationStrategyData
50
- ) {
51
- super(user, course, strategyData);
52
- this.name = strategyData.name || 'Hardcoded Order';
53
-
54
- if (strategyData.serializedData) {
55
- try {
56
- this.orderedCardIds = JSON.parse(strategyData.serializedData);
57
- } catch (e) {
58
- logger.error('Failed to parse serializedData for HardcodedOrderNavigator', e);
59
- }
60
- }
61
- }
62
-
63
- async getPendingReviews(): Promise<(StudySessionReviewItem & ScheduledCard)[]> {
64
- const reviews = await this.user.getPendingReviews(this.course.getCourseID());
65
- return reviews.map((r) => {
66
- return {
67
- ...r,
68
- contentSourceType: 'course',
69
- contentSourceID: this.course.getCourseID(),
70
- cardID: r.cardId,
71
- courseID: r.courseId,
72
- reviewID: r._id,
73
- status: 'review',
74
- };
75
- });
76
- }
77
-
78
- async getNewCards(limit: number = 99): Promise<StudySessionNewItem[]> {
79
- const activeCardIds = (await this.user.getActiveCards()).map((c: QualifiedCardID) => c.cardID);
80
-
81
- const newCardIds = this.orderedCardIds.filter((cardId) => !activeCardIds.includes(cardId));
82
-
83
- const cardsToReturn = newCardIds.slice(0, limit);
84
-
85
- return cardsToReturn.map((cardId) => {
86
- return {
87
- cardID: cardId,
88
- courseID: this.course.getCourseID(),
89
- contentSourceType: 'course',
90
- contentSourceID: this.course.getCourseID(),
91
- status: 'new',
92
- };
93
- });
94
- }
95
-
96
- /**
97
- * Get cards in hardcoded order with scores based on position.
98
- *
99
- * Earlier cards in the sequence get higher scores.
100
- * Score formula: 1.0 - (position / totalCards) * 0.5
101
- * This ensures scores range from 1.0 (first card) to 0.5+ (last card).
102
- *
103
- * This method supports both the legacy signature (limit only) and the
104
- * CardGenerator interface signature (limit, context).
105
- *
106
- * @param limit - Maximum number of cards to return
107
- * @param _context - Optional GeneratorContext (currently unused, but required for interface)
108
- */
109
- async getWeightedCards(limit: number, _context?: GeneratorContext): Promise<WeightedCard[]> {
110
- const activeCardIds = (await this.user.getActiveCards()).map((c: QualifiedCardID) => c.cardID);
111
- const reviews = await this.getPendingReviews();
112
-
113
- // Filter out already-active cards
114
- const newCardIds = this.orderedCardIds.filter((cardId) => !activeCardIds.includes(cardId));
115
-
116
- const totalCards = newCardIds.length;
117
-
118
- // Score new cards by position in sequence
119
- const scoredNew: WeightedCard[] = newCardIds.slice(0, limit).map((cardId, index) => {
120
- const position = index + 1;
121
- const score = Math.max(0.5, 1.0 - (index / totalCards) * 0.5);
122
-
123
- return {
124
- cardId,
125
- courseId: this.course.getCourseID(),
126
- score,
127
- provenance: [
128
- {
129
- strategy: 'hardcodedOrder',
130
- strategyName: this.strategyName || this.name,
131
- strategyId: this.strategyId || 'NAVIGATION_STRATEGY-hardcoded',
132
- action: 'generated',
133
- score,
134
- reason: `Position ${position} of ${totalCards} in fixed sequence, new card`,
135
- },
136
- ],
137
- };
138
- });
139
-
140
- // Score reviews at 1.0 (highest priority)
141
- const scoredReviews: WeightedCard[] = reviews.map((r) => ({
142
- cardId: r.cardID,
143
- courseId: r.courseID,
144
- score: 1.0,
145
- provenance: [
146
- {
147
- strategy: 'hardcodedOrder',
148
- strategyName: this.strategyName || this.name,
149
- strategyId: this.strategyId || 'NAVIGATION_STRATEGY-hardcoded',
150
- action: 'generated',
151
- score: 1.0,
152
- reason: 'Scheduled review, highest priority',
153
- },
154
- ],
155
- }));
156
-
157
- // Combine (reviews already sorted at top due to score=1.0)
158
- const all = [...scoredReviews, ...scoredNew];
159
- all.sort((a, b) => b.score - a.score);
160
-
161
- return all.slice(0, limit);
162
- }
163
- }
@@ -1,139 +0,0 @@
1
- // TUI-aware logging utility that redirects logs to file in Node.js
2
- import * as fs from 'fs';
3
- import * as path from 'path';
4
- import { getAppDataDirectory } from './dataDirectory';
5
-
6
- let logFile: string | null = null;
7
- let isNodeEnvironment = false;
8
-
9
- /**
10
- * Initialize TUI logging - redirect console logs to file in Node.js
11
- */
12
- export function initializeTuiLogging(): void {
13
- // Detect Node.js environment
14
- isNodeEnvironment = typeof window === 'undefined' && typeof process !== 'undefined';
15
-
16
- if (!isNodeEnvironment) {
17
- return; // Browser environment - keep normal console logging
18
- }
19
-
20
- try {
21
- // Set up log file path
22
- logFile = path.join(getAppDataDirectory(), 'lastrun.log');
23
-
24
- // Clear previous log file
25
- if (fs.existsSync(logFile)) {
26
- fs.unlinkSync(logFile);
27
- }
28
-
29
- // Create initial log entry
30
- const startTime = new Date().toISOString();
31
- fs.writeFileSync(logFile, `=== TUI Session Started: ${startTime} ===\n`);
32
-
33
- // Redirect console methods to file
34
- const originalConsole = {
35
- // eslint-disable-next-line no-console
36
- log: console.log,
37
- // eslint-disable-next-line no-console
38
- error: console.error,
39
- // eslint-disable-next-line no-console
40
- warn: console.warn,
41
- // eslint-disable-next-line no-console
42
- info: console.info
43
- };
44
-
45
- const writeToLog = (level: string, args: any[]) => {
46
- const timestamp = new Date().toISOString();
47
- const message = args.map(arg =>
48
- typeof arg === 'object' ? JSON.stringify(arg, null, 2) : String(arg)
49
- ).join(' ');
50
-
51
- const logEntry = `[${timestamp}] ${level}: ${message}\n`;
52
-
53
- try {
54
- fs.appendFileSync(logFile!, logEntry);
55
- } catch (err) {
56
- // Fallback to original console if file write fails
57
- originalConsole.error('Failed to write to log file:', err);
58
- originalConsole[level.toLowerCase() as keyof typeof originalConsole](...args);
59
- }
60
- };
61
-
62
- // Override console methods
63
- // eslint-disable-next-line no-console
64
- console.log = (...args) => writeToLog('INFO', args);
65
- // eslint-disable-next-line no-console
66
- console.info = (...args) => writeToLog('INFO', args);
67
- // eslint-disable-next-line no-console
68
- console.warn = (...args) => writeToLog('WARN', args);
69
- // eslint-disable-next-line no-console
70
- console.error = (...args) => writeToLog('ERROR', args);
71
-
72
- // Store original methods for potential restoration
73
- (console as any)._originalMethods = originalConsole;
74
-
75
- // eslint-disable-next-line no-console
76
- console.log('TUI logging initialized - logs redirected to', logFile);
77
-
78
- } catch (err) {
79
- // eslint-disable-next-line no-console
80
- console.error('Failed to initialize TUI logging:', err);
81
- }
82
- }
83
-
84
- /**
85
- * Get the current log file path (for debugging)
86
- */
87
- export function getLogFilePath(): string | null {
88
- return logFile;
89
- }
90
-
91
- /**
92
- * Show user-facing message (always visible in TUI)
93
- */
94
- export function showUserMessage(message: string): void {
95
- if (isNodeEnvironment) {
96
- // In Node.js, write directly to stdout to bypass log redirection
97
- process.stdout.write(message + '\n');
98
- } else {
99
- // In browser, use normal console
100
- // eslint-disable-next-line no-console
101
- console.log(message);
102
- }
103
- }
104
-
105
- /**
106
- * Show user-facing error (always visible in TUI)
107
- */
108
- export function showUserError(message: string): void {
109
- if (isNodeEnvironment) {
110
- // In Node.js, write directly to stderr to bypass log redirection
111
- process.stderr.write('Error: ' + message + '\n');
112
- } else {
113
- // In browser, use normal console
114
- // eslint-disable-next-line no-console
115
- console.error(message);
116
- }
117
- }
118
-
119
- /**
120
- * Logger object with standard log levels
121
- */
122
- export const logger = {
123
- debug: (message: string, ...args: any[]) => {
124
- // eslint-disable-next-line no-console
125
- console.log(`[DEBUG] ${message}`, ...args);
126
- },
127
- info: (message: string, ...args: any[]) => {
128
- // eslint-disable-next-line no-console
129
- console.info(`[INFO] ${message}`, ...args);
130
- },
131
- warn: (message: string, ...args: any[]) => {
132
- // eslint-disable-next-line no-console
133
- console.warn(`[WARN] ${message}`, ...args);
134
- },
135
- error: (message: string, ...args: any[]) => {
136
- // eslint-disable-next-line no-console
137
- console.error(`[ERROR] ${message}`, ...args);
138
- },
139
- };