npm install --legacy-peer-deps
(The --legacy-peer-deps flag matches the older @types/node@^16 pin in package.json against newer vitest peer ranges. The CI workflow uses the same flag.)
# Development watch (esbuild with source maps)
npm run dev
# Production build (typecheck + minified bundle)
npm run build
Build artifacts:
main.js — bundled plugin codestyles.css — plugin styles (keep in sync with source changes)manifest.json — plugin metadataThese must be placed in <Vault>/.obsidian/plugins/chronote/ for Obsidian to load them.
npm run dev or npm run build).main.js, manifest.json, and styles.css into your vault’s .obsidian/plugins/chronote/ folder.npm run dev running and reload Obsidian after changes (Command Palette → Reload app without saving).Cmd+Option+I on macOS) to see console output from the plugin.src/
main.ts # Plugin lifecycle (onload/onunload); keep minimal
settings.ts # Settings interface, defaults, and Settings tab UI
settingsTypes.ts # Plain interfaces for the settings shape
commands.ts # SRS review command + test commands
services/
googleCalendarService.ts # OAuth token refresh + Calendar CRUD
geminiService.ts # Prompt building + function calling + REST client
testService.ts # In-memory test registry (backed by settings)
flashcardSaveService.ts # Persisting AI-proposed flashcards to a deck
flashcardStudyPostProcessor.ts # Post-processing of saved-card study sessions
indexRunner.ts # Background runner for the local vector index
ai/ # Adapters: Gemini, OpenAI-compat, Anthropic, listLocalModels
agent/
toolRegistry.ts # Tool name → implementation registry
tools/ # Notes, tests, flashcards, quizzes, calendar tools
vectorIndex/ # chunker, embeddings, in-memory index, persistence, knowledgeBase
views/
ChronoteDashboardView.ts # Custom ItemView: calendar, reviews, tests
modals/
ChronoteChatModal.ts # AI chat UI with schedule approval
CreateTestModal.ts # Test creation form
AddToTestModal.ts # Add current note to an existing test
MarkTestDoneModal.ts # Mark a test as completed
ReviewScoreModal.ts # 1–5 score picker for SRS review
ReviewFilterModal.ts # Filter/sort controls for due reviews
ReviewScoreInfoModal.ts # Score explanation helper
SaveFlashcardsModal.ts # Save proposed flashcards to a deck
StudyFlashcardsModal.ts # Re-open a saved deck as an interactive study session
ui/
chatMarkdown.ts # Obsidian MarkdownRenderer wrapper
FlashcardDeck.ts # Interactive flashcard UI inside chat
QuizComponent.ts # Interactive quiz UI inside chat
utils/
srsLogic.ts # Interval calculation (SM-2 inspired)
dateUtils.ts # Local-date helpers (start-of-day, formatting)
formatRelative.ts # Human-friendly "in 3 days" rendering
notice.ts # Styled Notice wrappers
settingsMigration.ts # Forward-compatibility for older data.json blobs
The plugin follows the convention that main.ts stays minimal; commands are registered in src/commands.ts via the Commands class.
Example:
plugin.addCommand({
id: "my-new-command",
name: "My New Command",
checkCallback: (checking) => {
const file = plugin.app.workspace.getActiveFile();
if (checking) return file !== null;
// execute...
},
});
Use checkCallback when the command should only appear in the palette under specific conditions (e.g., an active markdown file).
src/modals/ extending Modal from obsidian.onOpen() to build the DOM inside this.contentEl.onClose() to clean up this.contentEl.new MyModal(app, plugin).open();
Keep modals self-contained; pass callbacks for actions that need to trigger a parent re-render.
The agent layer (src/agent/toolRegistry.ts) maps a tool name to a typed implementation. To add a new tool:
src/agent/tools/ exporting a Tool object (name, description, JSON Schema, and execute(args, ctx)).ChronoteChatModal (or wherever the tool list lives) so it gets passed to the active AI adapter.src/agent/tools/__tests__/.dateUtils.ts patterns (or inline equivalents) that create dates at midnight local time:
const d = new Date();
const startOfDay = new Date(d.getFullYear(), d.getMonth(), d.getDate());
new Date("2026-06-12") — it parses as UTC midnight and shifts in non-UTC timezones.TestService owns the tests array in settings. GoogleCalendarService owns token refresh timers. The chat adapter owns chat history for its instance.saveSettingsCallback rather than holding a reference to the plugin.new GoogleCalendarService(...)) but shares the same underlying settings object.app.fileManager.processFrontMatter(file, (fm) => { ... }) for writes.app.metadataCache.getFileCache(file)?.frontmatter for reads.await this.plugin.saveData(this.plugin.settings).this.display()) after toggles so the UI reflects the real state if startup failed.Date.toISOString() produces UTC. Use it for API payloads and ISO date extraction (split("T")[0]), but never for user-facing display.new Date(2026, 5, 12) is local; new Date("2026-06-12") is UTC. The codebase prefers the former for internal date math.GoogleCalendarService starts a setInterval for preemptive token refresh. Always call .destroy() when the consumer (Dashboard, Chat Modal) is torn down.metadataCache.on("changed", ...) on every save while typing.main.js. esbuild bundles everything.