# GTD Calendar โ€” Project Specification (v1 Draft) A fork of [WeMakeMachines/joplin-plugin-event-calendar](https://github.com/WeMakeMachines/joplin-plugin-event-calendar) (MIT) that inverts the plugin's data model: instead of events living as YAML inside the calendar note, the calendar is **populated by real Joplin notes and todos** in the surrounding folder tree, with clickable drilldown to the source note. **Working name:** `joplin-plugin-gtd-calendar` **Proposed manifest id:** `com.victorwiebe.joplin.plugin.gtd-calendar` (must differ from upstream's id so both plugins can coexist during development) --- ## 1. Concept A note anywhere in a folder contains a ` ```gtd-calendar ` fenced block. That block renders as a Day/Week/Month calendar populated by notes and todos in scope. Items opt in (by default) via a ` ```gtd ` fenced block in their body. Clicking a calendar event opens the underlying note. Dateless items are not lost โ€” they collect in an **Unscheduled** section below the calendar grid, functioning as a GTD next-actions / someday-maybe bucket. --- ## 2. Schema Reference ### 2.1 The `gtd-calendar` block (lives in the calendar note) ``` ```gtd-calendar view: month # day | week | month | month-grid (default: day) title: Editorial Schedule scope: this-folder # this-folder | children | (default: this-folder) notes: gtd-only # gtd-only | all | none (default: gtd-only) todos: gtd-only # gtd-only | all | none (default: gtd-only) sort: asc # asc | desc (default: asc) sort-type: title # title | modified_date (default: title) ``` | Key | Values | Default | Notes | |---|---|---|---| | `view` | `day`, `week`, `month` (also `d`/`w`/`m`), `month-grid` (also `grid`) | `day` | Grouping granularity. `day`/`week`/`month` render the compact tile strip spanning all events; `month-grid` renders a classic 7-column grid of the entire *current* calendar month with weekday headers, dimmed adjacent-month days, today highlighted, and clickable event chips in each day cell. Events outside the displayed month are counted in a footnote. | | `title` | string | none | Rendered as a heading above the calendar. | | `scope` | `this-folder`, `children`, or an integer | `this-folder` | `children` = unlimited recursion into subfolders. An integer *n* = this folder plus *n* levels of descendants (`0` โ‰ก `this-folder`). Documentation will caution that broad scopes scan every note body in the tree. | | `notes` | `gtd-only`, `all`, `none` | `gtd-only` | Which plain notes participate. `all` includes notes without a `gtd` block (they receive default styling and are Unscheduled unless... see ยง3.2). | | `todos` | `gtd-only`, `all`, `none` | `gtd-only` | Same semantics for todos. | | `sort` | `asc`, `desc` | `asc` | Order of events **within** each day/week/month grouping and within Unscheduled. Groupings themselves always run chronologically. | | `sort-type` | `title`, `modified_date` | `title` | Field used by `sort`. `modified_date` maps to Joplin's `updated_time`. | Unknown keys are ignored with a console warning. A malformed block renders an inline error box rather than failing silently (improvement over upstream, which swallows parse errors into the console). ### 2.2 The `gtd` block (lives in participating notes/todos) ``` ```gtd date: 2026-07-04 # required for notes to be scheduled; optional override for todos bg-colour: teal # any CSS color value fg-colour: white icon: ๐Ÿฆด title: Override title # optional; defaults to the note's own title text: Hover text # optional; tooltip detail, as upstream ``` | Key | Applies to | Behavior | |---|---|---| | `date` | notes | Required for the item to appear **in the grid**; without it the note goes to Unscheduled. Accepts `yyyy-mm-dd` (and `mm-dd-yyyy` for upstream parity). | | `date` | todos | Optional. If present, **overrides** `todo_due` (explicit beats implicit). | | `bg-colour` / `bgColor` | both | Event tile background. Both spellings accepted; `bg-colour` is canonical for this fork. | | `fg-colour` | both | Event tile text color (new vs. upstream). | | `icon` | both | Emoji/short string, as upstream. Defaults: type indicator (see ยง4). | | `title` | both | Overrides the note title on the tile. | | `text` | both | Tooltip body. | An **empty** ` ```gtd ` block is valid and meaningful: it is the opt-in signal with all defaults. --- ## 3. Data Model Rules ### 3.1 Date resolution 1. **Todos:** `todo_due` from the Joplin API. A `date:` in the `gtd` block overrides it. 2. **Notes:** `date:` from the `gtd` block only. No fallback to created/updated time โ€” those are not event dates and would produce misleading calendars. 3. **No resolvable date** โ†’ item is rendered in the **Unscheduled** section below the grid. ### 3.2 Inclusion matrix An item appears on the calendar iff its type's mode admits it: | Mode | Has `gtd` block | No `gtd` block | |---|---|---| | `gtd-only` (default) | โœ… included | โŒ excluded | | `all` | โœ… included | โœ… included (default styling) | | `none` | โŒ excluded | โŒ excluded | The calendar note itself is always excluded from results, as is any note whose only `gtd-calendar` block makes it a calendar (a note may, however, be both an event and contain a calendar if it has both block types โ€” edge case, supported, documented). ### 3.3 Completed todos **v1 decision:** completed todos (`todo_completed > 0`) render with strikethrough + checked indicator, and a future `completed: show | hide` calendar option is reserved. (Logged as decision D3 below; cheap to change.) --- ## 4. Rendering - Reuses upstream's grouping/renderer architecture (`DayGrouping`/`WeekGrouping`/`MonthGrouping` + DOM renderers + CSS asset), including the empty-grouping placeholders and current-date highlight. - **Fixes upstream bug:** `Calendar/index.ts` imports `WeekGrouping` from `Month/MonthGrouping`, so the week view silently groups by month. Corrected in the fork. - **Type distinction:** todos render with a checkbox glyph (โ˜ / โ˜‘ when completed); notes render with a document glyph. Glyphs are suppressed if the item supplies its own `icon`. A subtle border-style difference (solid vs. dashed, TBD in CSS) keeps the distinction visible under custom colours. - **Unscheduled section:** pinned below the grid, with its own header, honoring `sort` / `sort-type`. - **Drilldown:** each tile is clickable and opens the source note. --- ## 5. Architecture Upstream is a single synchronous markdown-it content script with no data API access. The fork becomes a three-part plugin: ``` โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ 1. Markdown-it content โ”‚ Intercepts ```gtd-calendar fences. โ”‚ script (renderer) โ”‚ Parses YAML config, emits a placeholder โ”‚ โ”‚
carrying the config as data-attrs. โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ (rendered HTML) โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ 2. Webview asset script โ”‚ Finds placeholders, calls โ”‚ (runs in rendered note) โ”‚ webviewApi.postMessage(config). โ”‚ โ”‚ Receives event data, builds the โ”‚ โ”‚ calendar DOM via the (reused) โ”‚ โ”‚ grouping/renderer classes. โ”‚ โ”‚ Click โ†’ postMessage({open: noteId}). โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ postMessage / onMessage โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ 3. Main plugin process โ”‚ joplin.contentScripts.onMessage: โ”‚ (index.ts) โ”‚ โ€ข resolve current note's folder โ”‚ โ”‚ (joplin.workspace.selectedNote) โ”‚ โ”‚ โ€ข build folder tree, apply scope โ”‚ โ”‚ โ€ข fetch notes/todos (id, title, โ”‚ โ”‚ is_todo, todo_due, todo_completed, โ”‚ โ”‚ updated_time, body) โ”‚ โ”‚ โ€ข parse ```gtd blocks from bodies โ”‚ โ”‚ โ€ข apply inclusion matrix, return โ”‚ โ”‚ structured event list โ”‚ โ”‚ โ€ข handle open: joplin.commands โ”‚ โ”‚ .execute('openNote', noteId) โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ ``` ### Performance notes - Folder tree and note list fetched per render request; paginated API calls (`page`/`has_more`). - Body scanning is limited to items in scope. For large scopes, pre-filter with Joplin's search API (`body:"```gtd"`-style query) before fetching full bodies. Cache by `updated_time` if needed; not a v1 requirement. - Refresh model: re-render occurs on note switch (Joplin behavior). `joplin.workspace.onNoteChange` live refresh is a stretch goal, not v1. ### Platform caveats - Desktop is the target. Mobile plugin support is partial; `openNote` behavior there is unverified. The "embed search" pairing remains a documented fallback for mobile users. --- ## 6. Implementation Phases 1. **Phase 0 โ€” Scaffolding.** Fork, rename (manifest id, plugin name, file names), build pipeline verified (`npm install && npm run dist`), upstream week-view bug fixed, upstream tests green. 2. **Phase 1 โ€” Plumbing.** Placeholder rendering for `gtd-calendar` fences; webview โ‡„ plugin message round-trip proven with a hardcoded payload. 3. **Phase 2 โ€” Data layer.** Folder tree + scope resolution; note/todo fetching; `gtd` block parsing; inclusion matrix; date resolution; unit tests for all of it (the upstream Jest setup carries over). 4. **Phase 3 โ€” Rendering.** Wire real data into the reused renderers; Unscheduled section; type glyphs; fg-colour support; sort options. 5. **Phase 4 โ€” Drilldown & polish.** Click-to-open; error boxes for malformed YAML; docs/README; scope-performance warnings. --- ## 7. Decision Log / Open Items | # | Decision | Status | |---|---|---| | D1 | Defaults: `notes: gtd-only`, `todos: gtd-only` | โš ๏ธ **Confirm** โ€” earlier discussion said todos are *always* included (i.e. `todos: all` default); the final message specified `gtd-only` as default for both. Spec follows the final message. | | D2 | `gtd` `date:` overrides `todo_due` (vs. emitting a warning) | Adopted; revisit if confusing in practice. | | D3 | Completed todos: shown struck-through in v1; `completed:` option reserved | Proposed, unconfirmed. | | D4 | `sort`/`sort-type` act within groupings only; grid is always chronological | Confirmed. | | D5 | Colour key spelling: `bg-colour`/`fg-colour` canonical, `bgColor` accepted for upstream parity | Proposed. | | D6 | Future options parked: `completed:`, tag-based filtering, week-start day, live refresh | Backlog. | --- ## 8. Licensing & Attribution Upstream is MIT (ยฉ Franco Speziali / WeMakeMachines). The fork retains the MIT license, preserves the original copyright notice, and credits upstream in the README per standard practice.