summaryrefslogtreecommitdiffhomepage
path: root/.rules/plan/calendar.md
diff options
context:
space:
mode:
Diffstat (limited to '.rules/plan/calendar.md')
-rw-r--r--.rules/plan/calendar.md394
1 files changed, 35 insertions, 359 deletions
diff --git a/.rules/plan/calendar.md b/.rules/plan/calendar.md
index 3a79846..f135ae1 100644
--- a/.rules/plan/calendar.md
+++ b/.rules/plan/calendar.md
@@ -6,6 +6,40 @@ Incorporate the functionality of `liamcain/obsidian-calendar-plugin` into AI Pul
---
+## Phase Files
+
+Each phase is documented in its own file for independent implementation:
+
+| Phase | File | Summary |
+|-------|------|---------|
+| 1 | [calendar-phase-1.md](calendar-phase-1.md) | Daily Note Manager — path computation, CRUD, indexing |
+| 2 | [calendar-phase-2.md](calendar-phase-2.md) | Calendar State — observable state container |
+| 3 | [calendar-phase-3.md](calendar-phase-3.md) | Calendar Renderer — pure DOM month grid |
+| 4 | [calendar-phase-4.md](calendar-phase-4.md) | Calendar View — ItemView subclass wiring |
+| 5 | [calendar-phase-5.md](calendar-phase-5.md) | Settings Integration — calendar settings UI |
+| 6 | [calendar-phase-6.md](calendar-phase-6.md) | Main Plugin Wiring — commands, ribbon, lifecycle |
+| 7 | [calendar-phase-7.md](calendar-phase-7.md) | AI Tools — read_daily_note, write_daily_note |
+| 8 | [calendar-phase-8.md](calendar-phase-8.md) | System Prompt Update — daily notes context for AI |
+| 9 | [calendar-phase-9.md](calendar-phase-9.md) | Calendar CSS — styles for the calendar grid |
+
+---
+
+## Implementation Order
+
+1. **Phase 1** — `daily-notes.ts` (core logic, testable in isolation)
+2. **Phase 2** — `calendar-state.ts` (state management)
+3. **Phase 3** — `calendar-renderer.ts` (DOM rendering)
+4. **Phase 4** — `calendar-view.ts` (ItemView wiring)
+5. **Phase 5** — Settings integration
+6. **Phase 6** — Main plugin wiring + commands
+7. **Phase 9** — CSS styles
+8. **Phase 7** — AI tools (read/write daily note)
+9. **Phase 8** — System prompt update
+
+Phases 1–6 and 9 deliver a fully working calendar view. Phases 7–8 add the AI integration.
+
+---
+
## Reference Plugin Analysis
### What It Does (Features to Keep)
@@ -105,365 +139,6 @@ Calendar/
---
-## Implementation Phases
-
-### Phase 1: Daily Note Manager (`src/calendar/daily-notes.ts`)
-
-Core module — no UI, just logic.
-
-```
-Functions:
- - getDailyNotePath(date: Moment, rootFolder: string): string
- Computes: `{rootFolder}/{YYYY}/{MM}/{DD}/{YYYY-MM-DD}.md`
-
- - getDailyNote(app: App, date: Moment, rootFolder: string): TFile | null
- Looks up vault file at the computed path.
-
- - createDailyNote(app: App, date: Moment, rootFolder: string, template?: string): Promise<TFile>
- Creates parent folders if needed, creates the file.
- Uses template content if configured, else empty with frontmatter:
- ---
- date: YYYY-MM-DD
- ---
-
- - openDailyNote(app: App, date: Moment, rootFolder: string, opts: { newLeaf: boolean }): Promise<void>
- Opens existing note or creates then opens.
-
- - indexDailyNotes(app: App, rootFolder: string): Map<string, TFile>
- Scans `{rootFolder}/` recursively, parses YYYY/MM/DD structure,
- returns Map<"YYYY-MM-DD", TFile>.
-
- - getDateFromDailyNote(file: TFile, rootFolder: string): Moment | null
- Reverse lookup: given a TFile, extract the date if it lives
- in the daily note folder structure.
-```
-
-### Phase 2: Calendar State (`src/calendar/calendar-state.ts`)
-
-Simple state container with change notifications — replaces Svelte stores.
-
-```
-class CalendarState:
- - displayedMonth: Moment (current month being viewed)
- - today: Moment (refreshed by heartbeat)
- - activeFileDate: string | null (date UID of active file, if daily note)
- - noteIndex: Map<string, TFile> (date string → file)
- - listeners: Set<() => void>
-
- Methods:
- - subscribe(cb): () => void (unsubscribe function)
- - setDisplayedMonth(m: Moment): void
- - setActiveFile(file: TFile | null): void
- - reindex(app: App, rootFolder: string): void
- - tick(): void (refresh today)
- - notify(): void (call all listeners)
-```
-
-### Phase 3: Calendar Renderer (`src/calendar/calendar-renderer.ts`)
-
-Pure DOM rendering — replaces `Calendar.svelte` and `obsidian-calendar-ui`.
-
-```
-class CalendarRenderer:
- constructor(containerEl: HTMLElement, state: CalendarState, callbacks: CalendarCallbacks)
-
- interface CalendarCallbacks:
- onClickDay(date: Moment, event: MouseEvent): void
- onClickWeek(date: Moment, event: MouseEvent): void
- onClickMonth(date: Moment, event: MouseEvent): void // from fork: click month label
- onClickYear(date: Moment, event: MouseEvent): void // from fork: click year label
- onClickQuarter(date: Moment, event: MouseEvent): void // from fork: click quarter label
- onContextMenuDay(date: Moment, event: MouseEvent): void
- onContextMenuWeek(date: Moment, event: MouseEvent): void
- onContextMenuMonth(date: Moment, event: MouseEvent): void // from fork
- onContextMenuYear(date: Moment, event: MouseEvent): void // from fork
- onContextMenuQuarter(date: Moment, event: MouseEvent): void // from fork
- onHoverDay(date: Moment, targetEl: EventTarget, isMetaPressed: boolean): void
- onHoverWeek(date: Moment, targetEl: EventTarget, isMetaPressed: boolean): void
- onHoverMonth(date: Moment, targetEl: EventTarget, isMetaPressed: boolean): void // from fork
- onHoverYear(date: Moment, targetEl: EventTarget, isMetaPressed: boolean): void // from fork
- onHoverQuarter(date: Moment, targetEl: EventTarget, isMetaPressed: boolean): void // from fork
-
- Methods:
- - render(): void
- Clears containerEl, builds:
- - Navigation bar: [<] [Month Year] [>] [Today]
- - Month and Year labels are clickable → callbacks.onClickMonth / onClickYear (from fork)
- - Quarter label (e.g. Q1) shown if calendarShowQuarter is true (from fork)
- - Weekday headers row (Mon, Tue, ...)
- - Optional week number column (left or right based on calendarShowWeekNumbersRight — from fork)
- - 6 rows × 7 day cells
- Each day cell:
- - CSS class: "today", "has-note", "active", "other-month"
- - Dots container (word count, tasks — Phase 5)
- - Click handler → callbacks.onClickDay
- - Context menu → callbacks.onContextMenuDay
-
- - destroy(): void
- Cleanup intervals, event listeners
-
- private:
- - renderNavBar(): HTMLElement
- - renderDayHeaders(): HTMLElement
- - renderWeeks(): HTMLElement
- - renderDay(date: Moment): HTMLElement
- - renderWeekNumber(date: Moment, position: "left" | "right"): HTMLElement // from fork: position option
- - renderQuarterLabel(date: Moment): HTMLElement // from fork
- - getDaysInMonthGrid(month: Moment): Moment[][]
- Returns 6 rows of 7 days, padding with prev/next month days
-```
-
-### Phase 4: Calendar View (`src/calendar/calendar-view.ts`)
-
-The `ItemView` subclass — wires everything together. Replaces `view.ts`.
-
-```
-VIEW_TYPE_CALENDAR = "ai-pulse-calendar"
-
-class CalendarView extends ItemView:
- - state: CalendarState
- - renderer: CalendarRenderer
-
- getViewType(): "ai-pulse-calendar"
- getDisplayText(): "Calendar"
- getIcon(): "calendar"
-
- onOpen():
- - Initialize state (today, reindex notes)
- - Create renderer with callbacks
- - Register vault events (create, delete, modify, rename) → reindex + re-render
- - Register workspace events (file-open) → update active file highlight
- - Start 60s heartbeat for day rollover
- - Initial render
-
- onClose():
- - renderer.destroy()
- - Clear intervals
-
- Callbacks:
- onClickDay(date, event):
- - If note exists → open it (respecting Ctrl+Click behavior setting — from fork)
- - If not → create (with optional confirmation modal) then open
-
- onClickWeek(date, event):
- - If weekly note exists → open it (future expansion)
- - Same Ctrl+Click behavior setting (from fork)
-
- onClickMonth(date, event): // from fork
- - If monthly note exists → open it
- - If not → create then open (future expansion)
-
- onClickYear(date, event): // from fork
- - If yearly note exists → open it
- - If not → create then open (future expansion)
-
- onClickQuarter(date, event): // from fork
- - If quarterly note exists → open it
- - If not → create then open (future expansion)
-
- onContextMenuDay(date, event):
- - Show file menu (delete, open in new tab, etc.) if note exists
-
- onContextMenuWeek/Month/Year/Quarter(date, event): // from fork
- - Show file menu if note exists for that period
-
- onHoverDay/Week/Month/Year/Quarter(date, targetEl, isMetaPressed): // from fork
- - Trigger link-hover for page preview when Ctrl/Cmd held
-
- revealActiveNote():
- - If active file is a daily note, set displayedMonth to that date
- - Also check weekly, monthly, quarterly, and yearly note formats (from fork)
-```
-
-### Phase 5: Settings Integration (`src/calendar/calendar-settings.ts` + `src/settings.ts`)
-
-Add calendar-specific settings to the plugin.
-
-```
-New settings fields in AIPulseSettings:
- - calendarRootFolder: string (default: "Calendar")
- - calendarConfirmBeforeCreate: boolean (default: true)
- - calendarWeekStart: "locale" | "sunday" | "monday" | ... (default: "locale")
- - calendarShowWeekNumbers: boolean (default: false)
- - calendarShowWeekNumbersRight: boolean (default: false) — from fork: option to display week numbers on the right side
- - calendarShowQuarter: boolean (default: false) — from fork: toggle quarter display (Q1–Q4)
- - calendarCtrlClickOpensInNewTab: boolean (default: false) — from fork: Ctrl+Click opens in new tab instead of new split
- - calendarShowWordCountDots: boolean (default: true)
- - calendarWordsPerDot: number (default: 250)
- - calendarDailyNoteTemplate: string (default: "")
-
-CalendarSettingsSection:
- - Adds a "Calendar" section to the settings modal
- - Root folder picker (text field)
- - Week start dropdown
- - Confirm before create toggle
- - Ctrl+Click behavior dropdown ("Open in new tab" vs "Open in new split") — from fork
- - Show week numbers toggle
- - Show week numbers on right side toggle — from fork
- - Show quarter toggle — from fork
- - Word count dots toggle + words per dot number
- - Daily note template path (text field)
-```
-
-### Phase 6: Main Plugin Wiring (`src/main.ts`)
-
-```
-In onload():
- - registerView(VIEW_TYPE_CALENDAR, (leaf) => new CalendarView(leaf, this))
- - addRibbonIcon("calendar", "Open Calendar", () => activateCalendarView())
- - addCommand("open-calendar", "Open Calendar View", ...)
- - addCommand("reveal-active-note", "Reveal active note in calendar", ...)
- - addCommand("open-today", "Open today's daily note", ...)
-
-In onunload():
- - detachLeavesOfType(VIEW_TYPE_CALENDAR)
-
-activateCalendarView():
- - Same pattern as activateView() for the chat — check for existing leaf first
-```
-
-### Phase 7: AI Tools for Date-Based Notes
-
-Two new tools the AI can use to interact with the calendar structure.
-
-#### `read_daily_note` tool
-```json
-{
- "id": "read_daily_note",
- "label": "Read Daily Note",
- "description": "Read the daily note for a specific date",
- "friendlyName": "Read Daily Note",
- "requiresApproval": false,
- "definition": {
- "type": "function",
- "function": {
- "name": "read_daily_note",
- "description": "Read the daily note for a given date. Use 'today' for the current date, or provide a date in YYYY-MM-DD format.",
- "parameters": {
- "type": "object",
- "required": ["date"],
- "properties": {
- "date": {
- "type": "string",
- "description": "The date to read. Use 'today', 'yesterday', 'tomorrow', or a YYYY-MM-DD date string."
- }
- }
- }
- }
- }
-}
-```
-
-**Execute logic:**
-1. Parse the `date` argument — handle `"today"`, `"yesterday"`, `"tomorrow"`, or parse `YYYY-MM-DD` with `moment()`
-2. Compute the path using `getDailyNotePath()`
-3. If file exists: read and return content (same format as `read_file`)
-4. If not: return `"No daily note exists for {date}."`
-
-#### `write_daily_note` tool
-```json
-{
- "id": "write_daily_note",
- "label": "Write Daily Note",
- "description": "Write or append to the daily note for a specific date",
- "friendlyName": "Write Daily Note",
- "requiresApproval": true,
- "definition": {
- "type": "function",
- "function": {
- "name": "write_daily_note",
- "description": "Write content to the daily note for a given date. Creates the note if it does not exist. Use mode 'append' to add to the end, or 'overwrite' to replace all content.",
- "parameters": {
- "type": "object",
- "required": ["date", "content"],
- "properties": {
- "date": {
- "type": "string",
- "description": "The date to write to. Use 'today', 'yesterday', 'tomorrow', or a YYYY-MM-DD date string."
- },
- "content": {
- "type": "string",
- "description": "The content to write."
- },
- "mode": {
- "type": "string",
- "description": "Write mode: 'append' (default) adds to the end, 'overwrite' replaces all content."
- }
- }
- }
- }
- }
-}
-```
-
-**Execute logic:**
-1. Parse date (same as read)
-2. If file does not exist: create it with content (using `createDailyNote()` then write)
-3. If file exists:
- - `"append"` (default): `app.vault.append(file, "\n" + content)`
- - `"overwrite"`: `app.vault.modify(file, content)`
-4. Return confirmation message
-
-### Phase 8: System Prompt Update
-
-Add to `system-prompt.json`:
-
-```
-"dailyNotes": {
- "header": "DAILY NOTES — DATE-BASED NOTE STRUCTURE:",
- "description": "The vault uses a calendar-based daily note system. Notes are stored at Calendar/{YYYY}/{MM}/{DD}/{YYYY-MM-DD}.md.",
- "tools": "Use read_daily_note and write_daily_note to interact with daily notes by date. These accept natural date references like 'today', 'yesterday', 'tomorrow', or explicit YYYY-MM-DD dates.",
- "rules": [
- "When the user refers to 'today's note', 'my daily note', or a specific date, use read_daily_note or write_daily_note.",
- "Do NOT use create_file or read_file for daily notes — always use the dedicated daily note tools.",
- "The daily note tools handle folder creation and path computation automatically.",
- "When appending to a daily note, the content is added at the end of the file."
- ]
-}
-```
-
-### Phase 9: Calendar CSS (`styles.css`)
-
-Append calendar styles. Use Obsidian CSS variables for theme compatibility.
-
-```
-Key classes:
- .ai-pulse-calendar — container
- .ai-pulse-calendar-nav — month navigation bar
- .ai-pulse-calendar-nav-title — "March 2026" (clickable → opens monthly note — from fork)
- .ai-pulse-calendar-nav-year — year label (clickable → opens yearly note — from fork)
- .ai-pulse-calendar-nav-quarter — quarter label, e.g. "Q1" (clickable → opens quarterly note — from fork)
- .ai-pulse-calendar-nav-btn — < > Today buttons
- .ai-pulse-calendar-grid — the 7-column grid (8-column when week numbers shown)
- .ai-pulse-calendar-weekday — header cells (Mon, Tue...)
- .ai-pulse-calendar-weeknum — week number cell (from fork: can be left or right column)
- .ai-pulse-calendar-day — individual day cell
- .ai-pulse-calendar-day.today — today highlight
- .ai-pulse-calendar-day.has-note — day with a note
- .ai-pulse-calendar-day.active — currently open note's date
- .ai-pulse-calendar-day.other-month — padding days from adjacent months
- .ai-pulse-calendar-dots — dot container within day cell
- .ai-pulse-calendar-dot — individual dot (word count)
-```
-
----
-
-## Implementation Order
-
-1. **Phase 1** — `daily-notes.ts` (core logic, testable in isolation)
-2. **Phase 2** — `calendar-state.ts` (state management)
-3. **Phase 3** — `calendar-renderer.ts` (DOM rendering)
-4. **Phase 4** — `calendar-view.ts` (ItemView wiring)
-5. **Phase 5** — Settings integration
-6. **Phase 6** — Main plugin wiring + commands
-7. **Phase 9** — CSS styles
-8. **Phase 7** — AI tools (read/write daily note)
-9. **Phase 8** — System prompt update
-
-Phases 1–7 and 9 deliver a fully working calendar view. Phases 7–8 add the AI integration.
-
----
-
## Considerations
- **No new dependencies** — everything is built with Obsidian API + DOM + `moment` (already available globally via `window.moment`)
@@ -526,3 +201,4 @@ The `Calendar.svelte` component passes additional props to `CalendarBase`:
### What We Defer
- **Monthly/quarterly/yearly note CRUD** — our storage structure is different; we'll add these later as the calendar matures
- **Monthly/quarterly/yearly stores and reindexing** — not needed until those note types are implemented
+