summaryrefslogtreecommitdiffhomepage
path: root/.rules/plan/calendar.md
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-03-29 13:44:05 +0900
committerAdam Malczewski <[email protected]>2026-03-29 13:44:05 +0900
commite8d107e454b3804e089a33ce5fe7c931040d4647 (patch)
tree4f8787fae670e86515baed54e00087ce3ef28da6 /.rules/plan/calendar.md
parent67d7d50ee2b05f66de3ab6aea38ff5d7d56ce839 (diff)
downloadai-pulse-obsidian-plugin-e8d107e454b3804e089a33ce5fe7c931040d4647.tar.gz
ai-pulse-obsidian-plugin-e8d107e454b3804e089a33ce5fe7c931040d4647.zip
remove calendar and chat history
Diffstat (limited to '.rules/plan/calendar.md')
-rw-r--r--.rules/plan/calendar.md394
1 files changed, 359 insertions, 35 deletions
diff --git a/.rules/plan/calendar.md b/.rules/plan/calendar.md
index f135ae1..3a79846 100644
--- a/.rules/plan/calendar.md
+++ b/.rules/plan/calendar.md
@@ -6,40 +6,6 @@ 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)
@@ -139,6 +105,365 @@ 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`)
@@ -201,4 +526,3 @@ 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
-