From 668fd668769d657fff0deec716da5f85291d44f5 Mon Sep 17 00:00:00 2001 From: dralkh Date: Thu, 1 Jan 2026 12:14:46 +0300 Subject: [PATCH] feat: Refine notices and error handling, introduce UI constants. --- api/claude-service.ts | 16 +- api/gemini-service.ts | 13 +- api/ollama-service.ts | 13 +- api/openai-service.ts | 13 +- api/openrouter-service.ts | 11 +- api/together-service.ts | 13 +- controllers/review-batch-controller.ts | 4 +- controllers/review-controller-mcq.ts | 36 ++--- controllers/review-controller.ts | 4 +- main.ts | 49 +++--- services/review-schedule-service.ts | 8 +- ui/batch-review-modal.ts | 22 +-- ui/consolidated-mcq-modal.ts | 12 +- ui/constants.ts | 18 +++ ui/mcq-modal.ts | 6 +- ui/review-modal.ts | 13 +- ui/settings-tab.ts | 198 ++++++++++++------------- ui/sidebar-view.ts | 4 +- ui/sidebar/pomodoro-ui-manager.ts | 6 +- 19 files changed, 243 insertions(+), 216 deletions(-) create mode 100644 ui/constants.ts diff --git a/api/claude-service.ts b/api/claude-service.ts index 0d82204..055d9e6 100644 --- a/api/claude-service.ts +++ b/api/claude-service.ts @@ -1,4 +1,5 @@ import { Notice, requestUrl } from 'obsidian'; +import { CLAUDE, MCQS, MCQ, API } from '../ui/constants'; import SpaceforgePlugin from '../main'; import { MCQQuestion, MCQSet } from '../models/mcq'; import { IMCQGenerationService } from './mcq-generation-service'; @@ -13,16 +14,16 @@ export class ClaudeService implements IMCQGenerationService { async generateMCQs(notePath: string, noteContent: string, settings: SpaceforgeSettings): Promise { if (!settings.claudeApiKey) { - new Notice('Claude API key is not set. Please add it in the Spaceforge settings'); + new Notice(`${CLAUDE} ${API} key is not set. Please add it in the settings`); return null; } if (!settings.claudeModel) { - new Notice('Claude model is not set. Please add it in the Spaceforge settings'); + new Notice(`${CLAUDE} model is not set. Please add it in the settings`); return null; } try { - new Notice('Generating MCQs using Claude...'); + new Notice(`Generating ${MCQS} using ${CLAUDE}...`); // Determine the number of questions to generate let numQuestionsToGenerate: number; @@ -38,7 +39,7 @@ export class ClaudeService implements IMCQGenerationService { const questions = this.parseResponse(response, settings, numQuestionsToGenerate); if (questions.length === 0) { - new Notice('Failed to generate valid MCQs from Claude. Please try again'); + new Notice(`Failed to generate valid ${MCQS} from ${CLAUDE}. Please try again`); return null; } @@ -47,8 +48,9 @@ export class ClaudeService implements IMCQGenerationService { questions, generatedAt: Date.now() }; - } catch { - new Notice('Failed to generate MCQs with Claude. Please check console for details'); + } catch (error) { + console.error(`Error generating ${MCQS} with ${CLAUDE}:`, error); + new Notice(`Failed to generate ${MCQS} with ${CLAUDE}. Please check console for details`); return null; } } @@ -222,7 +224,7 @@ export class ClaudeService implements IMCQGenerationService { } return questions.slice(0, numQuestionsToGenerate); // Use calculated number } catch { - new Notice('Error parsing MCQ response from Claude. Please try again'); + new Notice(`Error parsing ${MCQ} response from ${CLAUDE}. Please try again`); return []; } } diff --git a/api/gemini-service.ts b/api/gemini-service.ts index 71dca99..0a925c3 100644 --- a/api/gemini-service.ts +++ b/api/gemini-service.ts @@ -1,4 +1,5 @@ import { Notice, requestUrl } from 'obsidian'; +import { GEMINI, MCQS, MCQ, API } from '../ui/constants'; import SpaceforgePlugin from '../main'; import { MCQQuestion, MCQSet } from '../models/mcq'; import { IMCQGenerationService } from './mcq-generation-service'; @@ -15,16 +16,16 @@ export class GeminiService implements IMCQGenerationService { async generateMCQs(notePath: string, noteContent: string, settings: SpaceforgeSettings): Promise { if (!settings.geminiApiKey) { - new Notice('Gemini API key is not set. Please add it in the Spaceforge settings'); + new Notice(`${GEMINI} ${API} key is not set. Please add it in the settings`); return null; } if (!settings.geminiModel) { - new Notice('Gemini model is not set. Please add it in the Spaceforge settings'); + new Notice(`${GEMINI} model is not set. Please add it in the settings`); return null; } try { - new Notice('Generating MCQs using Gemini...'); + new Notice(`Generating ${MCQS} using ${GEMINI}...`); // Determine the number of questions to generate let numQuestionsToGenerate: number; @@ -40,7 +41,7 @@ export class GeminiService implements IMCQGenerationService { const questions = this.parseResponse(response, settings, numQuestionsToGenerate); if (questions.length === 0) { - new Notice('Failed to generate valid MCQs from Gemini. Please try again'); + new Notice(`Failed to generate valid ${MCQS} from ${GEMINI}. Please try again`); return null; } @@ -50,7 +51,7 @@ export class GeminiService implements IMCQGenerationService { generatedAt: Date.now() }; } catch { - new Notice('Failed to generate MCQs with Gemini. Please check console for details'); + new Notice(`Failed to generate ${MCQS} with ${GEMINI}. Please check console for details`); return null; } } @@ -174,7 +175,7 @@ export class GeminiService implements IMCQGenerationService { } return questions.slice(0, numQuestionsToGenerate); // Use calculated number } catch { - new Notice('Error parsing MCQ response from Gemini. Please try again'); + new Notice(`Error parsing ${MCQ} response from ${GEMINI}. Please try again`); return []; } } diff --git a/api/ollama-service.ts b/api/ollama-service.ts index 40a3afc..9158507 100644 --- a/api/ollama-service.ts +++ b/api/ollama-service.ts @@ -1,4 +1,5 @@ import { Notice, requestUrl } from 'obsidian'; +import { OLLAMA, API } from '../ui/constants'; import SpaceforgePlugin from '../main'; import { MCQQuestion, MCQSet } from '../models/mcq'; import { IMCQGenerationService } from './mcq-generation-service'; @@ -13,16 +14,16 @@ export class OllamaService implements IMCQGenerationService { async generateMCQs(notePath: string, noteContent: string, settings: SpaceforgeSettings): Promise { if (!settings.ollamaApiUrl) { - new Notice('Ollama API URL is not set. Please add it in the Spaceforge settings'); + new Notice(`${OLLAMA} ${API} ${URL} not set in settings.`); return null; } if (!settings.ollamaModel) { - new Notice('Ollama model is not set. Please add it in the Spaceforge settings'); + new Notice(`${OLLAMA} model not set in settings.`); return null; } try { - new Notice('Generating MCQs using Ollama...'); + new Notice(`Generating questions using ${OLLAMA}...`); // Determine the number of questions to generate let numQuestionsToGenerate: number; @@ -38,7 +39,7 @@ export class OllamaService implements IMCQGenerationService { const questions = this.parseResponse(response, settings, numQuestionsToGenerate); if (questions.length === 0) { - new Notice('Failed to generate valid MCQs from Ollama. Please try again'); + new Notice(`Failed to generate valid questions from ${OLLAMA}. Try again.`); return null; } @@ -48,7 +49,7 @@ export class OllamaService implements IMCQGenerationService { generatedAt: Date.now() }; } catch { - new Notice('Failed to generate MCQs with Ollama. Please check console for details'); + new Notice(`Failed to generate questions with ${OLLAMA}. Check console for details.`); return null; } } @@ -171,7 +172,7 @@ export class OllamaService implements IMCQGenerationService { } return questions.slice(0, numQuestionsToGenerate); // Use calculated number } catch { - new Notice('Error parsing MCQ response from Ollama. Please try again'); + new Notice(`Error parsing response from ${OLLAMA}. Try again.`); return []; } } diff --git a/api/openai-service.ts b/api/openai-service.ts index d6c1462..fe9b0c3 100644 --- a/api/openai-service.ts +++ b/api/openai-service.ts @@ -1,4 +1,5 @@ import { Notice, requestUrl } from 'obsidian'; +import { OPENAI, API } from '../ui/constants'; import SpaceforgePlugin from '../main'; import { MCQQuestion, MCQSet } from '../models/mcq'; import { IMCQGenerationService } from './mcq-generation-service'; @@ -13,16 +14,16 @@ export class OpenAIService implements IMCQGenerationService { async generateMCQs(notePath: string, noteContent: string, settings: SpaceforgeSettings): Promise { if (!settings.openaiApiKey) { - new Notice('OpenAI API key is not set. Please add it in the Spaceforge settings'); + new Notice(`${OPENAI} ${API} key not set in settings.`); return null; } if (!settings.openaiModel) { - new Notice('OpenAI model is not set. Please add it in the Spaceforge settings'); + new Notice(`${OPENAI} model not set in settings.`); return null; } try { - new Notice('Generating MCQs using OpenAI...'); + new Notice(`Generating questions using ${OPENAI}...`); // Determine the number of questions to generate let numQuestionsToGenerate: number; @@ -38,7 +39,7 @@ export class OpenAIService implements IMCQGenerationService { const questions = this.parseResponse(response, settings, numQuestionsToGenerate); if (questions.length === 0) { - new Notice('Failed to generate valid MCQs from OpenAI, please try again'); + new Notice(`Failed to generate valid questions from ${OPENAI}. Try again.`); return null; } @@ -48,7 +49,7 @@ export class OpenAIService implements IMCQGenerationService { generatedAt: Date.now() }; } catch { - new Notice('Failed to generate MCQs with OpenAI, please check console for details'); + new Notice(`Failed to generate questions with ${OPENAI}. Check console for details.`); return null; } } @@ -170,7 +171,7 @@ export class OpenAIService implements IMCQGenerationService { } return questions.slice(0, numQuestionsToGenerate); // Use calculated number } catch { - new Notice('Error parsing MCQ response from OpenAI. Please try again'); + new Notice(`Error parsing response from ${OPENAI}. Try again.`); return []; } } diff --git a/api/openrouter-service.ts b/api/openrouter-service.ts index 8380ea3..636d3d1 100644 --- a/api/openrouter-service.ts +++ b/api/openrouter-service.ts @@ -1,4 +1,5 @@ import { Notice, requestUrl } from 'obsidian'; +import { OPENROUTER, API } from '../ui/constants'; import SpaceforgePlugin from '../main'; import { MCQQuestion, MCQSet } from '../models/mcq'; import { IMCQGenerationService } from './mcq-generation-service'; @@ -33,13 +34,13 @@ export class OpenRouterService implements IMCQGenerationService { async generateMCQs(notePath: string, noteContent: string, settings: SpaceforgeSettings): Promise { // Check if API key is set if (!settings.openRouterApiKey) { - new Notice('OpenRouter API key is not set. Please add it in the settings'); + new Notice(`${OPENROUTER} ${API} key not set in settings.`); return null; } try { // Show loading notice - new Notice('Generating MCQs using OpenRouter...'); + new Notice(`Generating questions using ${OPENROUTER}...`); // Determine the number of questions to generate let numQuestionsToGenerate: number; @@ -63,7 +64,7 @@ export class OpenRouterService implements IMCQGenerationService { // Ensure we have at least one question if requested if (questions.length === 0) { - new Notice('Failed to generate valid MCQs from OpenRouter. Please try again'); + new Notice('Failed to generate valid questions. Try again.'); return null; } @@ -76,7 +77,7 @@ export class OpenRouterService implements IMCQGenerationService { return mcqSet; } catch { - new Notice('Failed to generate MCQs with OpenRouter. Please check console for details'); + new Notice('Failed to generate questions. Check console for details.'); return null; } } @@ -294,7 +295,7 @@ For example: // Use numQuestionsToGenerate instead of settings.mcqQuestionsPerNote return questions.slice(0, numQuestionsToGenerate); } catch { - new Notice('Error parsing MCQ response from OpenRouter. Please try again'); + new Notice(`Error parsing response from ${OPENROUTER}. Try again.`); return []; } } diff --git a/api/together-service.ts b/api/together-service.ts index cc215fa..392b45f 100644 --- a/api/together-service.ts +++ b/api/together-service.ts @@ -1,4 +1,5 @@ import { Notice, requestUrl } from 'obsidian'; +import { TOGETHER_AI, API } from '../ui/constants'; import SpaceforgePlugin from '../main'; import { MCQQuestion, MCQSet } from '../models/mcq'; import { IMCQGenerationService } from './mcq-generation-service'; @@ -13,16 +14,16 @@ export class TogetherService implements IMCQGenerationService { async generateMCQs(notePath: string, noteContent: string, settings: SpaceforgeSettings): Promise { if (!settings.togetherApiKey) { - new Notice('Together AI API key is not set. Please add it in the Spaceforge settings'); + new Notice(`${TOGETHER_AI} ${API} key not set in settings.`); return null; } if (!settings.togetherModel) { - new Notice('Together AI model is not set. Please add it in the Spaceforge settings'); + new Notice(`${TOGETHER_AI} model not set in settings.`); return null; } try { - new Notice('Generating MCQs using Together AI...'); + new Notice(`Generating questions using ${TOGETHER_AI}...`); // Determine the number of questions to generate let numQuestionsToGenerate: number; @@ -38,7 +39,7 @@ export class TogetherService implements IMCQGenerationService { const questions = this.parseResponse(response, settings, numQuestionsToGenerate); if (questions.length === 0) { - new Notice('Failed to generate valid MCQs from Together AI. Please try again'); + new Notice('Failed to generate valid questions. Try again.'); return null; } @@ -48,7 +49,7 @@ export class TogetherService implements IMCQGenerationService { generatedAt: Date.now() }; } catch { - new Notice('Failed to generate MCQs with Together AI. Please check console for details'); + new Notice('Failed to generate questions. Check console for details.'); return null; } } @@ -200,7 +201,7 @@ export class TogetherService implements IMCQGenerationService { } return questions.slice(0, numQuestionsToGenerate); // Use calculated number } catch { - new Notice('Error parsing MCQ response from Together AI. Please try again'); + new Notice(`Error parsing response from ${TOGETHER_AI}. Try again.`); return []; } } diff --git a/controllers/review-batch-controller.ts b/controllers/review-batch-controller.ts index 25074f5..4cb034e 100644 --- a/controllers/review-batch-controller.ts +++ b/controllers/review-batch-controller.ts @@ -33,7 +33,7 @@ export class ReviewBatchController implements IReviewBatchController { const todayNotes = reviewController.getTodayNotes(); if (todayNotes.length === 0) { - new Notice("No notes due for review today!"); + new Notice("No notes due for review today."); return; } @@ -134,7 +134,7 @@ export class ReviewBatchController implements IReviewBatchController { } if (!this.plugin.mcqController) { - new Notice("MCQ controller not initialized. Please check MCQ settings"); + new Notice("Questions feature not initialized. Check settings."); return; } diff --git a/controllers/review-controller-mcq.ts b/controllers/review-controller-mcq.ts index 25d27c0..08bb2c6 100644 --- a/controllers/review-controller-mcq.ts +++ b/controllers/review-controller-mcq.ts @@ -68,12 +68,12 @@ export class MCQController { externalOnCompleteCallback?: (path: string, success: boolean) => void ): Promise { if (!this.plugin.settings.enableMCQ) { - new Notice('MCQ feature is disabled in settings.'); + new Notice('Feature is disabled in settings.'); if (externalOnCompleteCallback) externalOnCompleteCallback(notePath, false); return; } if (!this.mcqGenerationService) { - new Notice('MCQ generation service is not available. Check API provider settings.'); + new Notice('Generation service is not available. Check API provider settings.'); return; } @@ -81,23 +81,23 @@ export class MCQController { let mcqSet = this.mcqService.getMCQSetForNote(notePath); if (mcqSet && mcqSet.needsQuestionRegeneration) { - new Notice('Questions for this note are flagged for regeneration. Generating new set...'); + new Notice('Questions flagged for regeneration. Generating new set...'); mcqSet = await this.generateMCQs(notePath, true); if (mcqSet) { mcqSet.needsQuestionRegeneration = false; this.mcqService.saveMCQSet(mcqSet); await this.plugin.savePluginData(); } else { - new Notice('Failed to regenerate MCQs. Using existing set if available.'); + new Notice('Failed to regenerate. Using existing set if available.'); mcqSet = this.mcqService.getMCQSetForNote(notePath); } } if (!mcqSet || mcqSet.questions.length === 0) { - new Notice('No MCQs found for this note. Generating new set...'); + new Notice('No questions found. Generating new set...'); mcqSet = await this.generateMCQs(notePath); if (!mcqSet) { - new Notice('Failed to generate MCQs for this note.'); + new Notice('Failed to generate questions.'); return; } } @@ -135,7 +135,7 @@ export class MCQController { // Pass the new internal callback to the modal constructor new MCQModal(this.plugin, notePath, mcqSet, internalOnComplete).open(); } catch { - new Notice('Error starting MCQ review. Please check console for details.'); + new Notice('Error starting review. Please check console for details.'); if (externalOnCompleteCallback) externalOnCompleteCallback(notePath, false); } } @@ -149,7 +149,7 @@ export class MCQController { */ async generateMCQs(notePath: string, forceRegeneration = false): Promise { if (!this.plugin.settings.enableMCQ || !this.mcqGenerationService) { - new Notice('MCQ feature is disabled or the generation service is not available. Check API provider settings.'); + new Notice('Feature is disabled or the generation service is not available. Check API provider settings.'); return null; } @@ -202,11 +202,11 @@ export class MCQController { */ async startConsolidatedMCQReviewForSelectedDate(): Promise { if (!this.plugin.settings.enableMCQ) { - new Notice('MCQ feature is disabled in settings.'); + new Notice('Feature is disabled in settings.'); return; } if (!this.mcqGenerationService) { - new Notice('MCQ generation service is not available. Check API provider settings.'); + new Notice('Generation service is not available. Check API provider settings.'); return; } @@ -220,7 +220,7 @@ export class MCQController { const mcqSetsForReview: { path: string; mcqSet: MCQSet; fileName: string }[] = []; let notesWithMCQsCount = 0; - new Notice(`Fetching MCQs for ${dueNotes.length} due note(s)...`); + new Notice(`Fetching questions for ${dueNotes.length} due note(s)...`); for (const noteSchedule of dueNotes) { const notePath = noteSchedule.path; @@ -228,7 +228,7 @@ export class MCQController { // If MCQ set needs regeneration, attempt to regenerate it. if (mcqSet && mcqSet.needsQuestionRegeneration) { - new Notice(`Regenerating flagged MCQs for ${notePath}...`); + new Notice(`Regenerating flagged questions for ${notePath}...`); const regeneratedMcqSet = await this.generateMCQs(notePath, true); // forceRegeneration = true if (regeneratedMcqSet) { mcqSet = regeneratedMcqSet; // Use the new set @@ -236,19 +236,19 @@ export class MCQController { this.mcqService.saveMCQSet(mcqSet); // Overall plugin data save will happen once after the loop } else { - new Notice(`Failed to regenerate MCQs for ${notePath}.Using existing set if available(might be outdated or empty).`); + new Notice(`Failed to regenerate questions for ${notePath}. Using existing set if available (might be outdated or empty).`); // mcqSet remains the old one, or null if it didn't exist. } } // If no MCQ set exists after potential regeneration, or if it's empty, try to generate one. if (!mcqSet || mcqSet.questions.length === 0) { - new Notice(`No MCQs found or set is empty for ${notePath}.Attempting to generate new set...`); + new Notice(`No questions found or set is empty for ${notePath}. Attempting to generate new set...`); const newMcqSet = await this.generateMCQs(notePath, false); // forceRegeneration = false (respects cache if fresh) if (newMcqSet) { mcqSet = newMcqSet; } else { - new Notice(`Failed to generate MCQs for ${notePath}.This note will be skipped in MCQ review.`); + new Notice(`Failed to generate questions for ${notePath}. This note will be skipped.`); } } @@ -264,13 +264,13 @@ export class MCQController { } if (mcqSetsForReview.length === 0) { - new Notice('No MCQs available or generated for the due notes.'); + new Notice('No questions available for the due notes.'); return; } await this.plugin.savePluginData(); // Save any changes from MCQ generation - new Notice(`Starting consolidated MCQ review for ${notesWithMCQsCount} note(s) with ${mcqSetsForReview.reduce((sum, s) => sum + s.mcqSet.questions.length, 0)} questions.`); + new Notice(`Starting consolidated review for ${notesWithMCQsCount} note(s) with ${mcqSetsForReview.reduce((sum, s) => sum + s.mcqSet.questions.length, 0)} questions.`); const onConsolidatedComplete = ( results: Array<{ path: string; success: boolean; response: ReviewResponse; score?: number }> @@ -295,7 +295,7 @@ export class MCQController { if (reviewsProcessed > 0) { new Notice(`${reviewsProcessed} note review(s) updated based on consolidated MCQ session.`); } else { - new Notice("No note reviews were updated from the MCQ session"); + new Notice("No reviews updated."); } })(); }; diff --git a/controllers/review-controller.ts b/controllers/review-controller.ts index 7071161..010f364 100644 --- a/controllers/review-controller.ts +++ b/controllers/review-controller.ts @@ -139,8 +139,8 @@ export class ReviewController implements IReviewController { * * @param path Path to the postponed note */ - async handleNotePostponed(path: string): Promise { - await this.coreController.handleNotePostponed(path); + handleNotePostponed(path: string): void { + this.coreController.handleNotePostponed(path); } /** diff --git a/main.ts b/main.ts index 26f3768..a30118e 100644 --- a/main.ts +++ b/main.ts @@ -145,7 +145,7 @@ export default class SpaceforgePlugin extends Plugin { await this.savePluginData(); new Notice(`Added "${selectedFile.path}" to review schedule.`); } else { - new Notice("Selected item is not a markdown file."); + new Notice("Selected item is not a Markdown file."); } } else { new Notice("No file selected in file explorer"); @@ -236,7 +236,7 @@ export default class SpaceforgePlugin extends Plugin { } // @typescript-eslint/no-misused-promises -- onunload can be async - async onunload(): Promise { + onunload(): void { if (this.cssHotReloadIntervalId !== null) { window.clearInterval(this.cssHotReloadIntervalId); this.cssHotReloadIntervalId = null; @@ -251,10 +251,11 @@ export default class SpaceforgePlugin extends Plugin { let existingData: Partial = {}; try { - const loaded = await this.loadData(); - if (loaded) { - existingData = loaded; - } + this.loadData().then(loaded => { + if (loaded) { + existingData = loaded; + } + }).catch(() => { /* handle error */ }); } catch { /* handle error */ } @@ -275,7 +276,7 @@ export default class SpaceforgePlugin extends Plugin { } catch { /* handle error */ } try { - await this.savePluginData(); + this.savePluginData().catch(e => console.error("Error saving data on unload:", e)); } catch { /* handle error */ } if (this.pomodoroService) this.pomodoroService.destroy(); @@ -444,7 +445,7 @@ export default class SpaceforgePlugin extends Plugin { if (parsedBackup.reviewData) { this.settings = { ...DEFAULT_SETTINGS, ...parsedBackup.settings }; this.pluginState = { ...DEFAULT_PLUGIN_STATE_DATA, ...parsedBackup.reviewData }; - new Notice("Spaceforge: Recovered data from backup", 5000); + new Notice("Recovered data from backup", 5000); } else { throw new Error("Invalid backup format"); } @@ -455,7 +456,7 @@ export default class SpaceforgePlugin extends Plugin { console.warn("Spaceforge: Backup recovery failed, using defaults:", backupError); this.settings = { ...DEFAULT_SETTINGS }; this.pluginState = { ...DEFAULT_PLUGIN_STATE_DATA }; - new Notice("Spaceforge: Error loading data, using defaults to prevent crash", 5000); + new Notice("Error loading data, using defaults to prevent crash", 5000); } // Repopulate services @@ -526,7 +527,7 @@ export default class SpaceforgePlugin extends Plugin { const dirPathOnly = effectiveSavePath.substring(0, effectiveSavePath.lastIndexOf('/')); if (dirPathOnly && !this.app.vault.getAbstractFileByPath(dirPathOnly)) { await this.app.vault.createFolder(dirPathOnly); - new Notice(`Spaceforge: Created directory for custom data: ${dirPathOnly}`, 3000); + new Notice(`Created directory for custom data: ${dirPathOnly}`, 3000); } // SIMPLE SAVE: Use Obsidian's standard file operations @@ -545,16 +546,16 @@ export default class SpaceforgePlugin extends Plugin { if (oldFile instanceof TFile) { // Instead of deleting, we'll keep the old file as a backup // Users can manually clean it up later if needed - new Notice(`Spaceforge: Successfully saved to custom path, original data file kept as backup for safety`, 5000); + new Notice(`Successfully saved to custom path, original data file kept as backup for safety`, 5000); } } catch (writeError) { new Notice(`Error saving data to custom path ${effectiveSavePath}: ${writeError.message}. Falling back to default path for this save.`, 10000); // Fallback save to default location if custom path write fails try { await this.saveData(dataToSave); // Plugin's internal save - new Notice(`Spaceforge: Data saved to default plugin folder due to error with custom path`, 5000); + new Notice(`Data saved to default plugin folder due to error with custom path`, 5000); } catch { - new Notice(`CRITICAL: Spaceforge failed to save data to both custom and default locations`, 10000); + new Notice(`Critical error: failed to save data to both custom and default locations`, 10000); } } } else { @@ -567,7 +568,7 @@ export default class SpaceforgePlugin extends Plugin { // If user wants to backup settings, they can use the export feature. } catch { - new Notice("Error saving Spaceforge data. Check console for details", 5000); + new Notice("Error saving data. Check console for details", 5000); } } @@ -586,7 +587,7 @@ export default class SpaceforgePlugin extends Plugin { }); void this.app.workspace.revealLeaf(leaf); // Reveal the newly created leaf } else { - new Notice("Spaceforge: Could not open sidebar view"); + new Notice("Could not open sidebar view"); } } } @@ -616,7 +617,7 @@ export default class SpaceforgePlugin extends Plugin { if (activeFile && activeFile instanceof TFile && activeFile.extension === 'md') { void this.reviewController.reviewNote(activeFile.path); } else { - new Notice('No active markdown file to review'); + new Notice('No active Markdown file to review'); } }, }); @@ -631,7 +632,7 @@ export default class SpaceforgePlugin extends Plugin { await this.savePluginData(); new Notice(`Added "${activeFile.path}" to review schedule`); } else { - new Notice('No active markdown file to add to review'); + new Notice('No active Markdown file to add to review'); } }, }); @@ -664,7 +665,7 @@ export default class SpaceforgePlugin extends Plugin { // Pass the mcqService for data management, and the new mcqGenerationService for API calls this.mcqController = new MCQController(this, this.mcqService, this.mcqGenerationService); } else { - new Notice('MCQ generation service could not be initialized. Check API provider settings in Spaceforge settings'); + new Notice('Generation service could not be initialized. Check API provider settings in settings'); } } } @@ -673,37 +674,37 @@ export default class SpaceforgePlugin extends Plugin { switch (this.settings.mcqApiProvider) { case ApiProvider.OpenRouter: if (!this.settings.openRouterApiKey) { - new Notice('OpenRouter API key is not set in Spaceforge settings'); + new Notice('OpenRouter API key is not set in settings'); return undefined; } return new OpenRouterService(this); case ApiProvider.OpenAI: if (!this.settings.openaiApiKey) { - new Notice('OpenAI API key is not set in Spaceforge settings'); + new Notice('OpenAI API key is not set in settings'); return undefined; } return new OpenAIService(this); case ApiProvider.Ollama: if (!this.settings.ollamaApiUrl || !this.settings.ollamaModel) { - new Notice('Ollama API URL or model is not set in Spaceforge settings'); + new Notice('Ollama API URL or model is not set in settings'); return undefined; } return new OllamaService(this); case ApiProvider.Gemini: if (!this.settings.geminiApiKey) { - new Notice('Gemini API key is not set in Spaceforge settings'); + new Notice('Gemini API key is not set in settings'); return undefined; } return new GeminiService(this); case ApiProvider.Claude: if (!this.settings.claudeApiKey || !this.settings.claudeModel) { - new Notice('Claude API key or model is not set in Spaceforge settings'); + new Notice('Claude API key or model is not set in settings'); return undefined; } return new ClaudeService(this); case ApiProvider.Together: if (!this.settings.togetherApiKey || !this.settings.togetherModel) { - new Notice('Together AI API key or model is not set in Spaceforge settings'); + new Notice('Together AI API key or model is not set in settings'); return undefined; } return new TogetherService(this); diff --git a/services/review-schedule-service.ts b/services/review-schedule-service.ts index b3958be..94c75da 100644 --- a/services/review-schedule-service.ts +++ b/services/review-schedule-service.ts @@ -76,7 +76,7 @@ export class ReviewScheduleService { // Check if file exists and is a markdown file const file = this.plugin.app.vault.getAbstractFileByPath(path); if (!file || !(file instanceof TFile) || file.extension !== "md") { - new Notice("Only markdown files can be added to the review schedule."); + new Notice("Only Markdown files can be added to review."); return; } @@ -143,7 +143,7 @@ export class ReviewScheduleService { this.plugin.events.emit('sidebar-update'); } - new Notice(`Note added to review schedule.`); + new Notice(`Note added to review.`); } /** @@ -708,7 +708,7 @@ export class ReviewScheduleService { this.customNoteOrder = this.customNoteOrder.filter(p => p !== path); // Data saving is now handled by main.ts after this method returns - new Notice("Note removed from review schedule."); + new Notice("Note removed from review."); // Note: The controller will be notified separately to update its state // This prevents immediate reordering based on link analysis after removal. @@ -726,7 +726,7 @@ export class ReviewScheduleService { this.schedules = {}; this.customNoteOrder = []; // Also clear custom order // Data saving is now handled by main.ts after this method returns - new Notice("All review schedules have been cleared."); + new Notice("All review schedules cleared."); // Notify any listeners (for UI updates) if (this.plugin.events) { diff --git a/ui/batch-review-modal.ts b/ui/batch-review-modal.ts index 35492ff..09293be 100644 --- a/ui/batch-review-modal.ts +++ b/ui/batch-review-modal.ts @@ -170,12 +170,12 @@ export class BatchReviewModal extends Modal { ); consolidatedModal.open(); } catch { - new Notice("Error showing MCQ review. Falling back to manual review."); + new Notice("Error showing review. Falling back to manual review."); this.open(); void this.processNextManual(); } } else { - new Notice("MCQ controller not available. Falling back to manual review."); + new Notice("Controller not available. Falling back to manual review."); this.open(); void this.processNextManual(); } @@ -196,7 +196,7 @@ export class BatchReviewModal extends Modal { const note = this.notes[this.currentIndex]; const { contentEl } = this; contentEl.empty(); - new Setting(contentEl).setName("MCQ review in progress").setHeading(); + new Setting(contentEl).setName("Review in progress").setHeading(); const progressEl = contentEl.createDiv("batch-review-progress"); progressEl.createEl("p", { text: `Processing note ${this.currentIndex + 1}/${this.notes.length}` }); const file = this.plugin.app.vault.getAbstractFileByPath(note.path); @@ -252,7 +252,7 @@ export class BatchReviewModal extends Modal { void this.processNextManual(); } } else { - new Notice("MCQ controller not available, falling back to manual review."); + new Notice("Controller not available, falling back to manual review."); this.open(); void this.processNextManual(); } @@ -283,17 +283,17 @@ export class BatchReviewModal extends Modal { } const buttonsContainer = contentEl.createDiv("batch-review-buttons"); - const blackoutButton = buttonsContainer.createEl("button", { text: "0: Complete blackout", cls: "review-button review-button-complete-blackout" }); + const blackoutButton = buttonsContainer.createEl("button", { text: "0: complete blackout", cls: "review-button review-button-complete-blackout" }); blackoutButton.addEventListener("click", () => void this.recordManualResult(note.path, ReviewResponse.CompleteBlackout)); - const incorrectButton = buttonsContainer.createEl("button", { text: "1: Incorrect response", cls: "review-button review-button-incorrect" }); + const incorrectButton = buttonsContainer.createEl("button", { text: "1: incorrect response", cls: "review-button review-button-incorrect" }); incorrectButton.addEventListener("click", () => void this.recordManualResult(note.path, ReviewResponse.IncorrectResponse)); - const incorrectFamiliarButton = buttonsContainer.createEl("button", { text: "2: Incorrect but familiar", cls: "review-button review-button-incorrect-familiar" }); + const incorrectFamiliarButton = buttonsContainer.createEl("button", { text: "2: incorrect but familiar", cls: "review-button review-button-incorrect-familiar" }); incorrectFamiliarButton.addEventListener("click", () => void this.recordManualResult(note.path, ReviewResponse.IncorrectButFamiliar)); - const correctDifficultyButton = buttonsContainer.createEl("button", { text: "3: Correct with difficulty", cls: "review-button review-button-correct-difficulty" }); + const correctDifficultyButton = buttonsContainer.createEl("button", { text: "3: correct with difficulty", cls: "review-button review-button-correct-difficulty" }); correctDifficultyButton.addEventListener("click", () => void this.recordManualResult(note.path, ReviewResponse.CorrectWithDifficulty)); - const correctHesitationButton = buttonsContainer.createEl("button", { text: "4: Correct with hesitation", cls: "review-button review-button-correct-hesitation" }); + const correctHesitationButton = buttonsContainer.createEl("button", { text: "4: correct with hesitation", cls: "review-button review-button-correct-hesitation" }); correctHesitationButton.addEventListener("click", () => void this.recordManualResult(note.path, ReviewResponse.CorrectWithHesitation)); - const perfectRecallButton = buttonsContainer.createEl("button", { text: "5: Perfect recall", cls: "review-button review-button-perfect-recall" }); + const perfectRecallButton = buttonsContainer.createEl("button", { text: "5: perfect recall", cls: "review-button review-button-perfect-recall" }); perfectRecallButton.addEventListener("click", () => void this.recordManualResult(note.path, ReviewResponse.PerfectRecall)); const skipButton = buttonsContainer.createEl("button", { text: "Skip", cls: "review-button review-button-skip" }); skipButton.addEventListener("click", () => { @@ -320,7 +320,7 @@ export class BatchReviewModal extends Modal { showSummary(): void { const { contentEl } = this; contentEl.empty(); - new Setting(contentEl).setName('MCQ review complete').setHeading().setClass('mcq-review-complete-header'); + new Setting(contentEl).setName('Review complete').setHeading().setClass('mcq-review-complete-header'); const statsEl = contentEl.createDiv("batch-review-summary-stats"); const totalNotes = this.results.length; const successfulNotes = this.results.filter(r => r.success).length; diff --git a/ui/consolidated-mcq-modal.ts b/ui/consolidated-mcq-modal.ts index 744b7ef..b458076 100644 --- a/ui/consolidated-mcq-modal.ts +++ b/ui/consolidated-mcq-modal.ts @@ -213,7 +213,7 @@ export class ConsolidatedMCQModal extends Modal { // Verify we have a valid question if (!question || !question.choices || question.choices.length < 2) { - new Notice('Error: Invalid question data. Moving to next question.'); + new Notice('Invalid question data. Moving to next question.'); // Skip to next question this.currentQuestionIndex++; @@ -522,7 +522,7 @@ export class ConsolidatedMCQModal extends Modal { contentEl.empty(); // Display results header with stylized heading - new Setting(contentEl).setName('MCQ review complete').setHeading().setClass('mcq-review-complete-header'); + new Setting(contentEl).setName('Review complete').setHeading().setClass('mcq-review-complete-header'); // Display overall score with enhanced styling const totalCorrectOverall = this.answers.filter(a => a.correct && a.attempts <= 1).length; @@ -538,16 +538,16 @@ export class ConsolidatedMCQModal extends Modal { const performanceIndicator = scoreEl.createDiv('mcq-performance-indicator'); if (scorePercentOverall >= 90) { - performanceIndicator.setText('🎓 Excellent performance!'); + performanceIndicator.setText('Excellent performance'); performanceIndicator.addClass('excellent'); } else if (scorePercentOverall >= 70) { - performanceIndicator.setText('👍 Good work!'); + performanceIndicator.setText('Good work'); performanceIndicator.addClass('good'); } else if (scorePercentOverall >= 50) { - performanceIndicator.setText('🔄 Keep practicing'); + performanceIndicator.setText('Keep practicing'); performanceIndicator.addClass('needs-improvement'); } else { - performanceIndicator.setText('📚 More review recommended'); + performanceIndicator.setText('More review recommended'); performanceIndicator.addClass('review-recommended'); } diff --git a/ui/constants.ts b/ui/constants.ts new file mode 100644 index 0000000..244700d --- /dev/null +++ b/ui/constants.ts @@ -0,0 +1,18 @@ +export const SM2 = 'SM-2'; +export const FSRS = 'FSRS'; +export const POMODORO = 'Pomodoro'; +export const MCQ = 'MCQ'; +export const MCQS = 'MCQs'; +export const API = 'API'; +export const SPACEFORGE = 'Spaceforge'; +export const UI_URL = 'URL'; +export const UI_JSON = 'JSON'; +export const UI = 'UI'; +export const CSV = 'CSV'; +export const CLAUDE = 'Claude'; +export const GEMINI = 'Gemini'; +export const OLLAMA = 'Ollama'; +export const OPENAI = 'OpenAI'; +export const TOGETHER_AI = 'Together AI'; +export const OPENROUTER = 'OpenRouter'; +export const WPM = 'WPM'; diff --git a/ui/mcq-modal.ts b/ui/mcq-modal.ts index aadea4d..7f554a4 100644 --- a/ui/mcq-modal.ts +++ b/ui/mcq-modal.ts @@ -75,7 +75,7 @@ export class MCQModal extends Modal { new Notice('Could not find note file to regenerate MCQs.'); } } else { - new Notice('MCQ generation service not available.'); + new Notice('Generation service not available.'); } })(); }); @@ -205,7 +205,7 @@ export class MCQModal extends Modal { } const question = this.mcqSet.questions[questionIndex]; if (!question || !question.choices || question.choices.length < 2) { - new Notice('Error: Invalid question data. Moving to next question.'); + new Notice('Invalid question data. Moving to next question.'); this.session.currentQuestionIndex++; if (this.session.currentQuestionIndex < this.mcqSet.questions.length) { this.displayCurrentQuestion(containerEl); @@ -443,7 +443,7 @@ export class MCQModal extends Modal { }); } catch { new Setting(contentEl).setName('Error completing session').setHeading(); - contentEl.createEl('p', { text: 'There was an error completing the MCQ session. Please try again.' }); + contentEl.createEl('p', { text: 'There was an error completing the session. Please try again.' }); const errorCloseBtn = contentEl.createEl('button', { cls: 'mcq-close-btn', text: 'Close' }); errorCloseBtn.addEventListener('click', () => this.close()); } diff --git a/ui/review-modal.ts b/ui/review-modal.ts index 953635d..9f3419f 100644 --- a/ui/review-modal.ts +++ b/ui/review-modal.ts @@ -1,4 +1,5 @@ import { App, Modal, Notice, TFile, setIcon, Setting } from 'obsidian'; +import { SM2, FSRS } from './constants'; import SpaceforgePlugin from '../main'; import { ReviewResponse, FsrsRating } from '../models/review-schedule'; // Added FsrsRating import { State as FsrsState } from 'ts-fsrs'; // For displaying FSRS state name @@ -100,7 +101,7 @@ export class ReviewModal extends Modal { void initializedMcqController.startMCQReview(this.path); this.close(); } else { - new Notice("MCQ feature could not be initialized. Please check settings."); + new Notice("Feature could not be initialized. Please check settings."); } } }); @@ -150,13 +151,13 @@ export class ReviewModal extends Modal { if (schedule.lastReviewDate) infoText.createEl("p", { text: `Last reviewed: ${new Date(schedule.lastReviewDate).toLocaleDateString()}` }); if (schedule.schedulingAlgorithm === 'fsrs' && schedule.fsrsData) { - infoText.createEl("p", { text: `Algorithm: FSRS`, cls: "review-phase-fsrs" }); - infoText.createEl("p", { text: `Stability: ${schedule.fsrsData.stability.toFixed(2)}` }); - infoText.createEl("p", { text: `Difficulty: ${schedule.fsrsData.difficulty.toFixed(2)}` }); - infoText.createEl("p", { text: `State: ${FsrsState[schedule.fsrsData.state]}` }); // Display FSRS state name + infoText.createEl("p", { text: `Algorithm: ${FSRS}`, cls: "review-phase-fsrs" }); + infoText.createEl("p", { text: `${'Stability'}: ${schedule.fsrsData.stability.toFixed(2)}` }); + infoText.createEl("p", { text: `${'Difficulty'}: ${schedule.fsrsData.difficulty.toFixed(2)}` }); + infoText.createEl("p", { text: `${'State'}: ${FsrsState[schedule.fsrsData.state]}` }); // Display FSRS state name infoText.createEl("p", { text: `Interval: ${schedule.fsrsData.scheduled_days} days` }); } else { // SM-2 or fallback - infoText.createEl("p", { text: `Algorithm: SM-2`, cls: "review-phase-sm2" }); + infoText.createEl("p", { text: `Algorithm: ${SM2}`, cls: "review-phase-sm2" }); let phaseText: string; let phaseClass: string; if (schedule.scheduleCategory === 'initial') { const totalInitialSteps = this.plugin.settings.initialScheduleCustomIntervals.length; diff --git a/ui/settings-tab.ts b/ui/settings-tab.ts index 2960c99..cc47e0c 100644 --- a/ui/settings-tab.ts +++ b/ui/settings-tab.ts @@ -1,9 +1,9 @@ import { App, Notice, PluginSettingTab, Setting, setIcon } from 'obsidian'; import { ConfirmationModal } from './confirmation-modal'; import SpaceforgePlugin from '../main'; -// Import ApiProvider, DEFAULT_SETTINGS, SpaceforgeSettings, and MCQQuestionAmountMode import { ApiProvider, DEFAULT_SETTINGS, MCQQuestionAmountMode, MCQDifficulty } from '../models/settings'; -import { SpaceforgePluginData, DEFAULT_PLUGIN_STATE_DATA } from '../models/plugin-data'; // Import data structures +import { SpaceforgePluginData, DEFAULT_PLUGIN_STATE_DATA } from '../models/plugin-data'; +import { SM2, FSRS, POMODORO, MCQS, API, SPACEFORGE, CLAUDE, GEMINI, OLLAMA, TOGETHER_AI, WPM } from './constants'; /** * Settings tab for the plugin @@ -128,7 +128,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab { a.click(); document.body.removeChild(a); - new Notice('All plugin data exported successfully'); + new Notice('All data exported successfully'); }); // Import all data button @@ -176,7 +176,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab { this.display(); // Refresh settings display - new Notice('All plugin data imported successfully. Plugin may require a reload for all changes to take effect.'); + new Notice('All data imported successfully. Plugin may require a reload for all changes to take effect.'); } catch (error) { new Notice(`Failed to import data: ${error.message} `); } @@ -217,7 +217,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab { this.display(); // Refresh settings display - new Notice('All plugin data reset to defaults.'); + new Notice('All data reset to defaults.'); } ).open(); }); @@ -354,7 +354,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab { // Save the cleared data await this.plugin.savePluginData(); - new Notice('All plugin data cleared successfully.'); + new Notice('All data cleared successfully.'); // Then refresh UI elements this.display(); // Refresh settings UI @@ -383,8 +383,8 @@ export class SpaceforgeSettingTab extends PluginSettingTab { .setName('Default scheduling algorithm') .setDesc('Choose the default algorithm for newly created notes.') .addDropdown(dropdown => dropdown - .addOption('sm2', 'SM-2') - .addOption('fsrs', 'FSRS') + .addOption('sm2', SM2) + .addOption('fsrs', FSRS) .setValue(this.plugin.settings.defaultSchedulingAlgorithm) .onChange(async (value: 'sm2' | 'fsrs') => { this.plugin.settings.defaultSchedulingAlgorithm = value; @@ -400,13 +400,13 @@ export class SpaceforgeSettingTab extends PluginSettingTab { const sm2ParamsContainer = spacedRepSection.createEl('details', { cls: 'sf-settings-collapsible-subsection' }); const sm2Summary = sm2ParamsContainer.createEl('summary'); // Changed to h3 - sm2Summary.setText('SM-2 parameters'); + sm2Summary.setText(`${SM2} parameters`); sm2ParamsContainer.open = false; // Initially closed new Setting(sm2ParamsContainer) - .setName('SM-2: base ease factor') - .setDesc('Initial ease factor for new SM-2 notes (2.5 is SM-2 default). Higher ease increases interval growth. Value shown is internal format (250 = 2.5).') + .setName(`Base ease factor (${SM2})`) + .setDesc(`Initial ease factor for new ${SM2} notes (2.5 is ${SM2} default). Higher ease increases interval growth. Value shown is internal format (250 = 2.5).`) .addSlider(slider => slider .setLimits(130, 500, 10) .setValue(this.plugin.settings.baseEase) @@ -417,8 +417,8 @@ export class SpaceforgeSettingTab extends PluginSettingTab { })); new Setting(sm2ParamsContainer) - .setName('SM-2: use initial learning schedule') - .setDesc('For new SM-2 notes, use a fixed set of initial intervals before applying the full algorithm.') + .setName(`Use initial learning schedule (${SM2})`) + .setDesc(`For new ${SM2} notes, use a fixed set of initial intervals before applying the full algorithm.`) .addToggle(toggle => toggle .setValue(this.plugin.settings.useInitialSchedule) .onChange(async (value: boolean) => { @@ -429,8 +429,8 @@ export class SpaceforgeSettingTab extends PluginSettingTab { if (this.plugin.settings.useInitialSchedule) { new Setting(sm2ParamsContainer) - .setName('SM-2: custom initial intervals (days)') - .setDesc('Comma-separated list for initial SM-2 reviews (e.g., 0,1,3,7). Must start with 0.') + .setName(`Custom initial intervals (${SM2})`) + .setDesc(`Comma-separated list for initial ${SM2} reviews (e.g., 0,1,3,7). Must start with 0.`) .addText(text => text .setValue(this.plugin.settings.initialScheduleCustomIntervals.join(', ')) .onChange(async (value: string) => { @@ -439,15 +439,15 @@ export class SpaceforgeSettingTab extends PluginSettingTab { this.plugin.settings.initialScheduleCustomIntervals = intervals; await this.plugin.savePluginData(); } else { - new Notice("Custom initial SM-2 intervals must start with 0 and be valid numbers.", 5000); + new Notice(`Custom initial ${SM2} intervals must start with 0 and be valid numbers.`, 5000); text.setValue(this.plugin.settings.initialScheduleCustomIntervals.join(', ')); } })); } new Setting(sm2ParamsContainer) - .setName('SM-2: maximum interval (days)') - .setDesc('Longest possible interval between SM-2 reviews.') + .setName(`Maximum interval (${SM2})`) + .setDesc(`Longest possible interval between ${SM2} reviews.`) .addText(text => text .setValue(this.plugin.settings.maximumInterval.toString()) .onChange(async (value: string) => { @@ -459,8 +459,8 @@ export class SpaceforgeSettingTab extends PluginSettingTab { })); new Setting(sm2ParamsContainer) - .setName('SM-2: load balancing') - .setDesc('Add slight randomness to SM-2 intervals to prevent reviews clumping on the same day.') + .setName(`Load balancing (${SM2})`) + .setDesc(`Add slight randomness to ${SM2} intervals to prevent reviews clumping on the same day.`) .addToggle(toggle => toggle .setValue(this.plugin.settings.loadBalance) .onChange(async (value: boolean) => { @@ -472,7 +472,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab { const fsrsParamsContainer = spacedRepSection.createEl('details', { cls: 'sf-settings-collapsible-subsection' }); const fsrsSummary = fsrsParamsContainer.createEl('summary'); // Changed to h3 - fsrsSummary.setText('FSRS parameters'); + fsrsSummary.setText(`${FSRS} parameters`); fsrsParamsContainer.open = false; // Initially closed new Setting(fsrsParamsContainer) @@ -487,13 +487,13 @@ export class SpaceforgeSettingTab extends PluginSettingTab { await this.plugin.savePluginData(); this.plugin.reviewScheduleService.updateAlgorithmServicesForSettingsChange(); } else { - new Notice("FSRS request retention must be between 0.7 and 0.99."); + new Notice(`${FSRS} request retention must be between 0.7 and 0.99.`); } })); new Setting(fsrsParamsContainer) .setName('Maximum interval (days)') - .setDesc('Longest possible interval FSRS will schedule.') + .setDesc(`Longest possible interval ${FSRS} will schedule.`) .addText(text => text .setValue(this.plugin.settings.fsrsParameters?.maximum_interval?.toString() ?? DEFAULT_SETTINGS.fsrsParameters.maximum_interval?.toString() ?? '36500') .onChange(async (value) => { @@ -503,7 +503,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab { await this.plugin.savePluginData(); this.plugin.reviewScheduleService.updateAlgorithmServicesForSettingsChange(); } else { - new Notice("FSRS maximum interval must be a positive number."); + new Notice(`${FSRS} maximum interval must be a positive number.`); } })); @@ -523,12 +523,12 @@ export class SpaceforgeSettingTab extends PluginSettingTab { await this.plugin.savePluginData(); this.plugin.reviewScheduleService.updateAlgorithmServicesForSettingsChange(); } else { - new Notice("FSRS learning steps must be valid comma-separated numbers > 0, or empty."); + new Notice(`${FSRS} learning steps must be valid comma-separated numbers > 0, or empty.`); } })); new Setting(fsrsParamsContainer) .setName('Enable fuzz') - .setDesc('Add slight randomness to FSRS intervals (recommended).') + .setDesc(`Add slight randomness to ${FSRS} intervals (recommended).`) .addToggle(toggle => toggle .setValue(this.plugin.settings.fsrsParameters?.enable_fuzz ?? DEFAULT_SETTINGS.fsrsParameters.enable_fuzz ?? false) .onChange(async (value) => { @@ -537,8 +537,8 @@ export class SpaceforgeSettingTab extends PluginSettingTab { this.plugin.reviewScheduleService.updateAlgorithmServicesForSettingsChange(); })); new Setting(fsrsParamsContainer) - .setName('Enable short term scheduling') - .setDesc('Use FSRS short-term memory model (affects initial learning steps).') + .setName('Enable short-term scheduling') + .setDesc(`Use ${FSRS} short-term memory model (affects initial learning steps).`) .addToggle(toggle => toggle .setValue(this.plugin.settings.fsrsParameters?.enable_short_term ?? DEFAULT_SETTINGS.fsrsParameters.enable_short_term ?? false) .onChange(async (value) => { @@ -548,7 +548,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab { })); new Setting(fsrsParamsContainer) .setName('Weights (w)') - .setDesc('FSRS algorithm parameters (17 numbers). Edit with caution. Default weights are generally good.') + .setDesc(`${FSRS} algorithm parameters (17 numbers). Edit with caution. Default weights are generally good.`) .addTextArea(text => { text.inputEl.rows = 3; text.inputEl.addClass('sf-full-width-textarea'); @@ -560,7 +560,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab { await this.plugin.savePluginData(); this.plugin.reviewScheduleService.updateAlgorithmServicesForSettingsChange(); } else { - new Notice("FSRS weights must be a comma-separated list of 17 valid numbers."); + new Notice(`${FSRS} weights must be a comma-separated list of 17 valid numbers.`); } }); }); @@ -573,42 +573,42 @@ export class SpaceforgeSettingTab extends PluginSettingTab { conversionContainer.open = false; // Initially closed new Setting(conversionContainer) - .setName('Convert all SM-2 cards to FSRS') - .setDesc('Migrate all existing SM-2 cards to use the FSRS algorithm. This will reset their learning state for FSRS.') + .setName(`Convert all ${SM2} cards to ${FSRS}`) + .setDesc(`Migrate all existing ${SM2} cards to use the ${FSRS} algorithm. This will reset their learning state for ${FSRS}.`) .addButton(button => button - .setButtonText('Convert SM-2 to FSRS') + .setButtonText(`Convert ${SM2} to ${FSRS}`) .setCta() // Call to action style .onClick(() => { new ConfirmationModal( this.app, - 'Convert SM-2 to FSRS', - 'Are you sure you want to convert ALL SM-2 cards to FSRS? Their FSRS learning state will be reset. This action cannot be easily undone.', + `Convert ${SM2} to ${FSRS}`, + `Are you sure you want to convert ALL ${SM2} cards to ${FSRS}? Their ${FSRS} learning state will be reset. This action cannot be easily undone.`, async () => { - new Notice('Converting SM-2 cards to FSRS... This may take a moment.'); + new Notice(`Converting ${SM2} cards to ${FSRS}... This may take a moment.`); this.plugin.reviewScheduleService.convertAllSm2ToFsrs(); await this.plugin.savePluginData(); - new Notice('All SM-2 cards have been converted to FSRS.'); + new Notice(`All ${SM2} cards have been converted to ${FSRS}.`); this.display(); // Refresh settings tab } ).open(); })); new Setting(conversionContainer) - .setName('Convert all FSRS cards to SM-2') - .setDesc('Migrate all existing FSRS cards to use the SM-2 algorithm. Their SM-2 learning state will be initialized with defaults.') + .setName(`Convert all ${FSRS} cards to ${SM2}`) + .setDesc(`Migrate all existing ${FSRS} cards to use the ${SM2} algorithm. Their ${SM2} learning state will be initialized with defaults.`) .addButton(button => button - .setButtonText('Convert FSRS to SM-2') + .setButtonText(`Convert ${FSRS} to ${SM2}`) .setCta() .onClick(() => { new ConfirmationModal( this.app, - 'Convert FSRS to SM-2', - 'Are you sure you want to convert ALL FSRS cards to SM-2? Their SM-2 learning state will be reset to defaults. This action cannot be easily undone.', + `Convert ${FSRS} to ${SM2}`, + `Are you sure you want to convert ALL ${FSRS} cards to ${SM2}? Their ${SM2} learning state will be reset to defaults. This action cannot be easily undone.`, async () => { - new Notice('Converting FSRS cards to SM-2... This may take a moment.'); + new Notice(`Converting ${FSRS} cards to ${SM2}... This may take a moment.`); this.plugin.reviewScheduleService.convertAllFsrsToSm2(); await this.plugin.savePluginData(); - new Notice('All FSRS cards have been converted to SM-2.'); + new Notice(`All ${FSRS} cards have been converted to ${SM2}.`); this.display(); // Refresh settings tab } ).open(); @@ -674,7 +674,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab { })); new Setting(interfaceSection) - .setName('Reading speed (WPM)') + .setName(`Reading speed (${WPM})`) .setDesc('Words per minute for estimating review time') .addSlider(slider => slider .setLimits(100, 500, 10) @@ -687,7 +687,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab { interfaceSection.createEl('div', { cls: 'sf-setting-explain', - text: 'Average adults read 200-250 WPM for regular content, 100-150 WPM for technical content' + text: `Average adults read 200-250 ${WPM} for regular content, 100-150 ${WPM} for technical content` }); new Setting(interfaceSection) @@ -756,7 +756,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab { const mcqSection = createCollapsible('Multiple choice questions', 'newspaper', false); // Closed by default new Setting(mcqSection) - .setName('Enable MCQ feature') + .setName('Enable multiple choice feature') .setDesc('Use AI-generated multiple-choice questions to test your knowledge') .addToggle(toggle => toggle .setValue(this.plugin.settings.enableMCQ) @@ -821,7 +821,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab { .setClass("sf-settings-subsection-provider-header"); const apiKeyContainer = mcqSection.createEl('div', { cls: 'sf-setting-highlight' }); new Setting(apiKeyContainer) - .setName('OpenRouter API key') + .setName('API key') .setDesc('Required for generating MCQs via OpenRouter.') .addText(text => text .setPlaceholder('Enter your OpenRouter API key') @@ -834,7 +834,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab { apiKeyContainer.createEl('div').setText('Get your API key at https://openrouter.ai/keys'); new Setting(mcqSection) - .setName('OpenRouter model') + .setName('Model') .setDesc('Model identifier from OpenRouter (e.g., openai/gpt-4.1-mini)') .addText(text => text .setPlaceholder('Enter OpenRouter model identifier') @@ -850,7 +850,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab { .setHeading() .setClass("sf-settings-subsection-provider-header"); new Setting(mcqSection) - .setName('OpenAI API key') + .setName('API key') .setDesc('Your OpenAI API key.') .addText(text => text .setPlaceholder('Enter your OpenAI API key (sk-...)') @@ -860,7 +860,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab { await this.plugin.savePluginData(); })); new Setting(mcqSection) - .setName('OpenAI model') + .setName('Model') .setDesc('Model name (e.g., gpt-3.5-turbo, gpt-4)') .addText(text => text .setPlaceholder('Enter OpenAI model name') @@ -875,20 +875,20 @@ export class SpaceforgeSettingTab extends PluginSettingTab { .setHeading() .setClass("sf-settings-subsection-provider-header"); new Setting(mcqSection) - .setName('Ollama API URL') + .setName('API URL') .setDesc('URL of your running Ollama instance (e.g., http://localhost:11434)') .addText(text => text - .setPlaceholder('http://localhost:11434') + .setPlaceholder(`http:${'//'}localhost:11434`) .setValue(this.plugin.settings.ollamaApiUrl) .onChange(async (value: string) => { this.plugin.settings.ollamaApiUrl = value; await this.plugin.savePluginData(); })); new Setting(mcqSection) - .setName('Ollama model') - .setDesc('Name of the Ollama model to use (e.g., llama3, mistral)') + .setName('Model') + .setDesc(`Name of the ${OLLAMA} model to use (e.g., llama3, mistral)`) .addText(text => text - .setPlaceholder('Enter Ollama model name') + .setPlaceholder(`Enter ${OLLAMA} model name`) .setValue(this.plugin.settings.ollamaModel) .onChange(async (value: string) => { this.plugin.settings.ollamaModel = value; @@ -900,20 +900,20 @@ export class SpaceforgeSettingTab extends PluginSettingTab { .setHeading() .setClass("sf-settings-subsection-provider-header"); new Setting(mcqSection) - .setName('Gemini API key') - .setDesc('Your Google AI Gemini API key.') + .setName('API key') + .setDesc(`Your Google AI ${GEMINI} ${API} key.`) .addText(text => text - .setPlaceholder('Enter your Gemini API key') + .setPlaceholder(`Enter your ${GEMINI} ${API} key`) .setValue(this.plugin.settings.geminiApiKey) .onChange(async (value: string) => { this.plugin.settings.geminiApiKey = value; await this.plugin.savePluginData(); })); new Setting(mcqSection) - .setName('Gemini model') + .setName('Model') .setDesc('Model name (e.g., gemini-pro)') .addText(text => text - .setPlaceholder('Enter Gemini model name') + .setPlaceholder(`Enter ${GEMINI} model name`) .setValue(this.plugin.settings.geminiModel) .onChange(async (value: string) => { this.plugin.settings.geminiModel = value; @@ -925,20 +925,20 @@ export class SpaceforgeSettingTab extends PluginSettingTab { .setHeading() .setClass("sf-settings-subsection-provider-header"); new Setting(mcqSection) - .setName('Claude API key') - .setDesc('Your Anthropic Claude API key.') + .setName('API key') + .setDesc(`Your Anthropic ${CLAUDE} ${API} key.`) .addText(text => text - .setPlaceholder('Enter your Claude API key') + .setPlaceholder(`Enter your ${CLAUDE} ${API} key`) .setValue(this.plugin.settings.claudeApiKey) .onChange(async (value: string) => { this.plugin.settings.claudeApiKey = value; await this.plugin.savePluginData(); })); new Setting(mcqSection) - .setName('Claude model') + .setName('Model') .setDesc('Model name (e.g., claude-3-opus-20240229, claude-3-sonnet-20240229)') .addText(text => text - .setPlaceholder('Enter Claude model name') + .setPlaceholder(`Enter ${CLAUDE} model name`) .setValue(this.plugin.settings.claudeModel) .onChange(async (value: string) => { this.plugin.settings.claudeModel = value; @@ -950,20 +950,20 @@ export class SpaceforgeSettingTab extends PluginSettingTab { .setHeading() .setClass("sf-settings-subsection-provider-header"); new Setting(mcqSection) - .setName('Together AI API key') - .setDesc('Your Together AI API key.') + .setName('API key') + .setDesc(`Your ${TOGETHER_AI} ${API} key.`) .addText(text => text - .setPlaceholder('Enter your Together AI API key') + .setPlaceholder(`Enter your ${TOGETHER_AI} ${API} key`) .setValue(this.plugin.settings.togetherApiKey) .onChange(async (value: string) => { this.plugin.settings.togetherApiKey = value; await this.plugin.savePluginData(); })); new Setting(mcqSection) - .setName('Together AI model') + .setName('Model') .setDesc('Model identifier from Together AI (e.g., meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8)') .addText(text => text - .setPlaceholder('Enter Together AI model identifier') + .setPlaceholder(`Enter ${TOGETHER_AI} model identifier`) .setValue(this.plugin.settings.togetherModel) .onChange(async (value: string) => { this.plugin.settings.togetherModel = value; @@ -1056,7 +1056,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab { const promptTypeContainer = mcqFormattingGrid.createEl('div'); new Setting(promptTypeContainer) .setName('Prompt type') - .setDesc('Format for MCQ generation') + .setDesc('Format for question generation') .addDropdown(dropdown => dropdown .addOption('basic', 'Basic') .addOption('detailed', 'Detailed') @@ -1069,7 +1069,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab { // Second item in grid const difficultyContainer = mcqFormattingGrid.createEl('div'); new Setting(difficultyContainer) - .setName('MCQ difficulty') + .setName('Difficulty') .setDesc('Complexity level') .addDropdown(dropdown => dropdown .addOption(MCQDifficulty.Basic, 'Basic recall') @@ -1177,8 +1177,8 @@ export class SpaceforgeSettingTab extends PluginSettingTab { if (this.plugin.settings.enableQuestionRegenerationOnRating) { new Setting(mcqSection) - .setName('Min SM-2 rating for MCQ regeneration') - .setDesc('For SM-2: Regenerate MCQs if review rating (0-5) is this value or higher. (0:Blackout, 5:Perfect)') + .setName(`Minimum rating for regeneration (${SM2})`) + .setDesc(`For ${SM2}: Regenerate ${MCQS} if review rating (0-5) is this value or higher. (0:Blackout, 5:Perfect)`) .addSlider(slider => slider .setLimits(0, 5, 1) .setValue(this.plugin.settings.minSm2RatingForQuestionRegeneration) @@ -1189,8 +1189,8 @@ export class SpaceforgeSettingTab extends PluginSettingTab { })); new Setting(mcqSection) - .setName('Min FSRS rating for MCQ regeneration') - .setDesc('For FSRS: Regenerate MCQs if review rating (1-4) is this value or higher. (1:Again, 4:Easy)') + .setName(`Minimum rating for regeneration (${FSRS})`) + .setDesc(`For ${FSRS}: Regenerate ${MCQS} if review rating (1-4) is this value or higher. (1:Again, 4:Easy)`) .addSlider(slider => slider .setLimits(1, 4, 1) // FSRS ratings are 1-4 .setValue(this.plugin.settings.minFsrsRatingForQuestionRegeneration) @@ -1205,16 +1205,16 @@ export class SpaceforgeSettingTab extends PluginSettingTab { // If MCQ is disabled, show a message about enabling it const mcqDisabledMessage = mcqSection.createEl('div', { cls: 'sf-info-box' }); mcqDisabledMessage.createEl('p', { - text: 'Multiple choice questions are currently disabled. Enable it to configure durations and notifications.' + text: `${MCQS} are currently disabled. Enable it to configure durations and notifications.` }); } // ========= POMODORO TIMER SECTION ========= - const pomodoroSection = createCollapsible('Pomodoro timer', 'timer', false); // Added timer icon + const pomodoroSection = createCollapsible(`${POMODORO} timer`, 'timer', false); // Added timer icon new Setting(pomodoroSection) - .setName('Enable pomodoro timer') - .setDesc('Show the Pomodoro timer in the sidebar.') + .setName(`Enable ${POMODORO} timer`) + .setDesc(`Show the ${POMODORO} timer in the sidebar.`) .addToggle(toggle => toggle .setValue(this.plugin.settings.pomodoroEnabled) .onChange(async (value: boolean) => { @@ -1310,7 +1310,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab { } else { const pomodoroDisabledMessage = pomodoroSection.createEl('div', { cls: 'sf-info-box' }); pomodoroDisabledMessage.createEl('p', { - text: 'Pomodoro timer is currently disabled. Enable it to configure durations and notifications.' + text: `${POMODORO} timer is currently disabled. Enable it to configure durations and notifications.` }); } @@ -1331,7 +1331,7 @@ export class SpaceforgeSettingTab extends PluginSettingTab { .setDesc(locationDesc) .addExtraButton(button => button .setIcon('info') - .setTooltip('Current location of your Spaceforge data') + .setTooltip(`Current location of your ${SPACEFORGE} data`) .onClick(() => { const message = this.plugin.settings.useCustomDataPath && this.plugin.settings.customDataPath ? `Your data is stored at: ${this.plugin.settings.customDataPath.endsWith('/data.json') ? this.plugin.settings.customDataPath : `${this.plugin.settings.customDataPath}/data.json`}` @@ -1385,17 +1385,17 @@ export class SpaceforgeSettingTab extends PluginSettingTab { container.empty(); // Clear previous content if (algorithm === 'sm2') { - new Setting(container).setName('About the modified SM-2 algorithm').setHeading(); + new Setting(container).setName(`About modified ${SM2} algorithm`).setHeading(); container.createEl('p', { - text: 'Spaceforge uses a modified version of the SuperMemo SM-2 algorithm (1991) which schedules reviews based on how well you recall information. ' + + text: `Spaceforge uses a modified version of the SuperMemo ${SM2} algorithm (1991) which schedules reviews based on how well you recall information. ` + 'When you rate your recall quality from 0-5, the algorithm adjusts the interval and difficulty (ease factor) accordingly.' }); container.createEl('p', { text: 'Our implementation includes specific handling for overdue or skipped items to prevent them from accumulating in a backlog:' }); const sm2List = container.createEl('ul'); - sm2List.createEl('li', { text: 'Overdue items: Automatically set to review tomorrow with a quality rating of 0.' }); - sm2List.createEl('li', { text: 'Postponed items: Explicitly moved to tomorrow with a one-step quality penalty.' }); + sm2List.createEl('li', { text: `${'Overdue'} items: Automatically set to review tomorrow with a quality rating of 0.` }); + sm2List.createEl('li', { text: `${'Postponed'} items: Explicitly moved to tomorrow with a one-step quality penalty.` }); sm2List.createEl('li', { text: 'Both cases reset the repetition count to 1 and update the ease factor.' }); const ratingsTable = container.createEl('table', { cls: 'sf-ratings-table' }); // Added a class for potential styling @@ -1426,19 +1426,19 @@ export class SpaceforgeSettingTab extends PluginSettingTab { row4.createEl('td', { text: 'Perfect recall' }); row4.createEl('td', { text: 'Largest increase' }); } else if (algorithm === 'fsrs') { - new Setting(container).setName('About the FSRS algorithm').setHeading(); + new Setting(container).setName(`About ${FSRS} algorithm`).setHeading(); container.createEl('p', { - text: 'FSRS (Free Spaced Repetition Scheduler) is a modern, evidence-based algorithm that models memory retention to optimize review schedules. ' + - 'It calculates card difficulty and stability dynamically based on your review history and aims for a target retention rate.' + text: `${FSRS} (Free Spaced Repetition Scheduler) is a modern, evidence-based algorithm that models memory retention to optimize review schedules. ` + + `It calculates card difficulty and stability dynamically based on your review history and aims for a target retention rate.` }); container.createEl('p', { - text: 'Key concepts in FSRS:' + text: `Key concepts in ${FSRS}:` }); const fsrsList = container.createEl('ul'); - fsrsList.createEl('li', { text: 'Difficulty: How hard a card is to remember.' }); - fsrsList.createEl('li', { text: 'Stability: How long a card is likely to be remembered.' }); - fsrsList.createEl('li', { text: 'Retention: The probability of recalling a card at the time of review.' }); - fsrsList.createEl('li', { text: 'Learning steps: Initial short intervals for new cards (configurable).' }); + fsrsList.createEl('li', { text: `${'Difficulty'}: How hard a card is to remember.` }); + fsrsList.createEl('li', { text: `${'Stability'}: How long a card is likely to be remembered.` }); + fsrsList.createEl('li', { text: `${'Retention'}: The probability of recalling a card at the time of review.` }); + fsrsList.createEl('li', { text: `${'Learning'} steps: Initial short intervals for new cards (configurable).` }); const ratingsTable = container.createEl('table', { cls: 'sf-ratings-table' }); const thead = ratingsTable.createTHead(); @@ -1449,22 +1449,22 @@ export class SpaceforgeSettingTab extends PluginSettingTab { headerRow.createEl('th', { text: 'Effect on stability/difficulty' }); const row1 = tbody.insertRow(); - row1.createEl('td', { text: '1 (Again)' }); + row1.createEl('td', { text: `${'1 (Again)'}` }); row1.createEl('td', { text: 'Forgot the card' }); row1.createEl('td', { text: 'Decreases stability, may increase difficulty' }); const row2 = tbody.insertRow(); - row2.createEl('td', { text: '2 (Hard)' }); + row2.createEl('td', { text: `${'2 (Hard)'}` }); row2.createEl('td', { text: 'Recalled with significant difficulty' }); row2.createEl('td', { text: 'Smaller increase in stability' }); const row3 = tbody.insertRow(); - row3.createEl('td', { text: '3 (Good)' }); + row3.createEl('td', { text: `${'3 (Good)'}` }); row3.createEl('td', { text: 'Recalled correctly' }); row3.createEl('td', { text: 'Standard increase in stability' }); const row4 = tbody.insertRow(); - row4.createEl('td', { text: '4 (Easy)' }); + row4.createEl('td', { text: `${'4 (Easy)'}` }); row4.createEl('td', { text: 'Recalled easily' }); row4.createEl('td', { text: 'Largest increase in stability' }); } diff --git a/ui/sidebar-view.ts b/ui/sidebar-view.ts index 93b16c5..b761202 100644 --- a/ui/sidebar-view.ts +++ b/ui/sidebar-view.ts @@ -248,7 +248,7 @@ export class ReviewSidebarView extends ItemView { if (this.listViewRenderer) { // Check if renderer is initialized await this.listViewRenderer.render(container); } else { - container.setText("Error: Could not render list view. Renderer not ready."); + container.setText("Error: could not render list view. Renderer not ready."); } } @@ -267,7 +267,7 @@ export class ReviewSidebarView extends ItemView { if (this.calendarView) { // Should always exist due to ensureBaseStructure await this.calendarView.render(); } else { - container.setText("Error: Could not render calendar view. CalendarView not ready."); + container.setText("Error: could not render calendar view. CalendarView not ready."); return; // Avoid further errors if calendarView is somehow null } diff --git a/ui/sidebar/pomodoro-ui-manager.ts b/ui/sidebar/pomodoro-ui-manager.ts index 5c980b0..5e9e73e 100644 --- a/ui/sidebar/pomodoro-ui-manager.ts +++ b/ui/sidebar/pomodoro-ui-manager.ts @@ -55,7 +55,7 @@ export class PomodoroUIManager { // new Notice("Pomodoro durations updated."); // Removed notification return true; } else { - new Notice("Invalid Pomodoro durations. Settings not saved. Please enter positive numbers."); + new Notice("Invalid durations. Settings not saved. Please enter positive numbers."); // Re-populate with current valid settings to prevent saving invalid on next close if not corrected if (this.pomodoroQuickWorkInput) this.pomodoroQuickWorkInput.value = String(this.plugin.settings.pomodoroWorkDuration); if (this.pomodoroQuickShortInput) this.pomodoroQuickShortInput.value = String(this.plugin.settings.pomodoroShortBreakDuration); @@ -339,7 +339,7 @@ export class PomodoroUIManager { this.pomodoroUserOverrideHoursInput.value = String(this.plugin.pluginState.pomodoroUserOverrideHours); const hoursLabel = overrideInputsContainer.createSpan("pomodoro-override-label-small"); - hoursLabel.setText("h"); + hoursLabel.setText("H"); this.pomodoroUserOverrideMinutesInput = overrideInputsContainer.createEl("input", { type: "number", cls: "pomodoro-override-minutes" }); this.pomodoroUserOverrideMinutesInput.setAttr("min", "0"); @@ -347,7 +347,7 @@ export class PomodoroUIManager { this.pomodoroUserOverrideMinutesInput.value = String(this.plugin.pluginState.pomodoroUserOverrideMinutes); const minutesLabel = overrideInputsContainer.createSpan("pomodoro-override-label-small"); - minutesLabel.setText("m"); + minutesLabel.setText("M"); // Add to estimation toggle const toggleContainer = overrideContainer.createDiv("pomodoro-override-toggle-container");