- notescraft-lint (rules, safe fixes, learned style) and notescraft-blocks (block model and edits); engine lint.* and block.* commands with CLI and MCP parity - Lint tab, underlines, slash menu with preview in Source and Visual, block grip and menu, Settings > Editor and lint, Vim block keys - Visual view: tables, note embeds, %% comments, wrapping property chips - Vault knowledge (Related tab, assistant sources, Settings) and calendar plugin - Sidebar top padding fixed, dot grid follows PaperCraft spacing, right-card tabs no longer cover the collapse button Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
13 KiB
Calendar plugin redesign
Problem
The bundled Calendar plugin is a sidebar month grid plus a task agenda. It marks days that have a daily note, opens or creates that day's note, and lists tasks that carry a due date. It cannot represent events with times, repeats or reminders. It has no sorting, filtering, multi-day selection or notifications, and it cannot read or write Google or Apple calendars. Users who keep their schedule in NotesCraft must switch to another app for anything beyond a due date. The owner also wants every feature reachable by Vim motions and by the CLI, which today's plugin does not offer. Evidence: owner request in the 2026-10-17 session; SPEC feature 26 describes only the month grid and three tools; designs for the new scope exist in PaperCraft (project NotesCraft, page "Plugins / Calendar").
Goal
A user can plan, find, edit and get reminded about events and dated tasks inside NotesCraft, with the same capabilities available by keyboard alone, from the CLI, and to the assistant.
Non-Goals
- Replacing Google Calendar or Apple Calendar as full clients (no invitations to other people, no RSVP, no room booking, no free/busy lookup of other people).
- Video-call hosting. A Join button only opens a link already stored in the event.
- Mobile or web builds.
- Task management beyond what shows on the calendar (no new task fields, no kanban).
- Light-theme and 4K polish of the newer screens in the first release.
- Colour-blind-safe event palette in the first release (tracked as a design gap).
- Plugin marketplace changes. The Calendar stays a bundled core plugin.
User Stories
- As a note taker, I want month, week, day, agenda and year views, so that I can see my schedule at the scale I need.
- As a busy user, I want to create an event by typing a natural-language phrase or by picking a date range, so that adding an event takes seconds.
- As a user with many events, I want to sort, filter, group and save filters, so that I can find what matters without scrolling.
- As a keyboard user, I want to navigate, select, create, move and delete events with Vim motions, so that I never need the mouse.
- As a user of Google or Apple calendars, I want to connect my accounts and see (optionally edit) those events next to my notes, so that I have one schedule.
- As a forgetful user, I want system notifications, snooze and a reminders inbox, so that I do not miss an event or overdue task.
- As a scripter or assistant user, I want the same calendar operations as CLI and MCP commands with JSON output, so that I can automate them.
Acceptance Criteria
Views and navigation
- Given a vault with events, when the user opens the Calendar, then month, week, day, agenda and year views are available and the chosen view is remembered after restart.
- Given any view, when the user presses
t, then the view shows today;h/lmove one day,j/kone week,H/Lone month,[/]one year;gdprompts for a date and jumps to it. - Given the year view, when it renders, then each day shows an intensity level derived from its event count.
- Given an event title longer than its cell, when it renders, then the title is cut to one line with an ellipsis and the full title shows on hover.
Events
- Given the quick-add field, when the user types a phrase naming a title, a date or weekday, and a time, then the preview shows the parsed event before it is saved, and nothing is saved on Escape.
- Given an input the parser cannot read, when the user confirms, then an error names the unparsed part and no event is created.
- Given a repeating event, when the user edits, moves or deletes one occurrence, then the app asks for the scope (this, this and following, all) and changes only that scope.
- Given two events that overlap in time, when the day view renders, then both show side by side and the conflict is listed by the conflicts command.
- Given an event with a time zone different from the system zone, when it renders, then both times are shown.
- Given an event, when the user runs "meeting note" on it (
gn), then a note linked to the event opens, created from the meeting template if absent, and the event lists it as a backlink.
Sort, filter, select
- Given the agenda, when the user filters by calendar, type, status, priority, tag, date range or has-reminder, then only matching items show and the active filters are visible and clearable.
- Given the agenda, when the user sorts by date, priority, title, linked note or created time and groups by day, week, calendar or tag, then the order and groups follow the choice.
- Given an active filter and sort, when the user saves it, then it appears in the saved list after restart and can be applied in one action.
- Given a multi-day range selected by mouse or by
v/V, when the user opens the bulk bar, then move, delete, tag and change-calendar apply to every selected item, and the count of affected items is shown before confirm. - Given an event selected by keyboard, when the user drags-equivalent keys (
v,h j k l,J Kto resize,y pto copy) then the event moves, resizes or copies exactly as the mouse drag would.
Notifications
- Given an event with a reminder and notification permission granted, when the reminder time arrives while the app runs, then one system notification shows with snooze options.
- Given notification permission denied, when the user opens notification settings, then the denied state is shown with how to fix it, and in-app reminders still work.
- Given quiet hours, when a reminder falls inside them, then no sound plays and the reminder appears in the inbox.
- Given a missed reminder (app was closed), when the app starts, then it appears in the inbox under "missed" and is not re-notified as new.
- Given the test-notification button, when pressed, then a notification shows or an error states why not.
Accounts and sync
- Given Connect Google, when the user completes the browser sign-in, then calendars are listed with per-calendar on/off toggles and events load; the default is view-only.
- Given two-way sync is off for an account, when the user edits an imported event, then the edit is refused with a message and the remote event is unchanged.
- Given Apple on macOS, when connected through the system calendar, then no password is requested; elsewhere iCloud via CalDAV requires an app-specific password.
- Given an ICS feed URL, when added, then its events show read-only and refresh at the chosen interval.
- Given an account credential, when stored, then it is in the system keychain and never in the vault, settings file, logs or sync log.
- Given the network is down, when the user opens the Calendar, then cached events show with an offline indicator, local edits queue, and nothing crashes.
- Given the same event changed locally and remotely, when sync runs, then the chosen conflict policy applies and the result is recorded in the sync log.
Commands, CLI, assistant
- Given each calendar capability (agenda, add, edit, move, delete, next, free, conflicts, year, review, note, sync, account add), when invoked as an engine command, from the CLI with
--json, and as an MCP tool, then all three return the same result. - Given a repeating event, when the CLI edit, move or delete runs without
--scope, then it fails with an error naming the allowed scopes; with--scope this|following|allit applies that scope. - Given the assistant requests a calendar change, when it calls a write tool, then the change appears as a pending change the user accepts or rejects before it is written.
- Given the per-plugin tool toggle is off, when the assistant lists tools, then no calendar tool appears.
- Given the week-review command (
gR), when run, then it produces a summary of past events, completed tasks and linked notes for that week.
Robustness
- Given a malformed event file, an out-of-range date or a hostile remote payload, when it is read, then it is skipped with a reported error and the app does not panic.
- Given a vault with 10,000 events, when the month view opens, then it renders within the performance budget below.
- Given every new user-visible string, when the i18n check runs, then it passes with no hardcoded strings.
Constraints
- Stack: Rust only. Native egui UI. No JavaScript, webview, Tauri or Electron. Follows the repo layer rules: logic in pure library crates, UI only in the top layer. Shared crates from craft-libs are unchanged.
- Everything is an engine command so UI, CLI, control channel, MCP and plugins share one surface (SPEC 10). Calendar commands become MCP tools named
plugin_calendar_<command>. - Never crash: no panics on any input from files, networks or accounts; invalid input returns an error and is never silently fixed.
- Performance: month view renders in under 16 ms per frame with 10,000 events loaded; agenda filter or sort over 10,000 events completes in under 100 ms at p99; first calendar open after start under 300 ms from cache; sync runs off the UI thread and never blocks input.
- Security: account tokens only in the system keychain. OAuth runs in the user's browser; the app never sees the Google password. Least-privilege scopes, read-only by default. Network access only to hosts of connected accounts. Sync logs and error messages carry no tokens or event bodies. Remote payloads are treated as hostile (size caps, depth caps). Writes to disk go through the shared atomic writer. Reserved vault folders are never written by public APIs.
- Third-party dependencies: any new crate (OAuth, CalDAV, ICS parsing, keychain, notifications, recurrence rules, natural-language dates) needs a written reason and owner approval first.
- Data: event storage format must stay Obsidian-compatible and readable as plain files; imported remote events must not pollute the vault unless the user chooses "create notes for imported events".
- Compat: macOS first (EventKit available); Linux and Windows must build and run, with Apple support through CalDAV only. Existing daily-note marks,
calendar.monthanddaily.openkeep working. (SPEC 26 listscalendar_agenda,calendar_open_day,calendar_add_task, but none exist in code yet; the due-date agenda is built new.) - Accessibility: all actions reachable by keyboard; event colour is never the only carrier of meaning (full colour-blind-safe palette deferred, see Non-Goals).
- Design reference: PaperCraft project NotesCraft, page "Plugins / Calendar" (21 artboards). Text overflow rule: one line, ellipsis, full text on hover.
Open Questions
- ASSUMED default: one Markdown file per event with frontmatter, in a vault calendar folder. Obsidian-readable, atomic writes, easy to sync and diff.
- ASSUMED default: daily, weekly, monthly, yearly with interval, weekday list, end date or count, and per-occurrence exceptions. Full RFC 5545 deferred; imported rules beyond this subset show read-only.
- RESOLVED (owner took proposal): first release ships ICS feeds and Google, view-only. Apple, Outlook and generic CalDAV follow later.
- RESOLVED (follows from 3): first release is view-only; two-way sync is deferred.
- ASSUMED default: reminders fire only while the app runs; missed ones go to the inbox on next start. Background agent deferred.
- ASSUMED default: English only for quick add in the first release.
- ASSUMED default: owner approval requested for each crate at plan time, one by one, with a written reason; prefer in-house code for ICS and recurrence subset. Nothing is added until approved.
- ASSUMED default: tasks with due dates and events share one agenda and one filter/sort model, but stay separate on disk (tasks remain in notes).
- ASSUMED default: Google stays behind a build-time client id supplied by the owner; Google release is gated on that registration. ICS ships regardless.
- RESOLVED (owner took proposal): build order is engine commands, then CLI and MCP, then egui views, then accounts and notifications.
- ASSUMED default: calendar keys apply only while the Calendar has focus; editor normal-mode keys win elsewhere. Clashes are resolved at plan time.
- ASSUMED default: yes, 10,000 events is the design target.
- RESOLVED at build time: an event in another time zone shows its own time and the zone name (on hover and in the day and week blocks' tooltip); converting to the system zone needs a time-zone database, which no approved dependency provides.
- RESOLVED at build time: a private ICS feed address is kept in
.notescraft/calendar-feeds.json, inside the vault, because an address is the only way to fetch it; every read, status, cache and log leaves it out. Google's refresh token is in the system keychain, never in the vault. - RESOLVED at build time: no new crate was needed. HTTP goes through the system
curl, notifications throughosascriptornotify-send, the keychain throughsecurityorsecret-tool. Windows has no notification or keychain path in this build.