From dfd003c4c21067b7ed1b1742d7b274e96e713a37 Mon Sep 17 00:00:00 2001 From: Victor Wiebe Date: Fri, 12 Jun 2026 15:55:07 +0200 Subject: [PATCH] Add SPEC.md --- SPEC.md | 180 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 SPEC.md diff --git a/SPEC.md b/SPEC.md new file mode 100644 index 0000000..7eca313 --- /dev/null +++ b/SPEC.md @@ -0,0 +1,180 @@ +# 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 (default: day, matching upstream) +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`, as upstream) | `day` | Grouping granularity of the grid. | +| `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. \ No newline at end of file