Files
Marcos Paulo 15d7f5e873 feat: editor blocks and lint, vault knowledge, calendar plugin, macOS menu
- 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>
2026-10-18 12:36:00 -03:00

9.5 KiB

Plan: Calendar plugin redesign

Approach

Build bottom-up in the order the spec fixed: pure logic crate, then engine commands, then CLI/MCP exposure (free, both read the registry), then egui views, then ICS/Google sync and notifications. Events are one Markdown file each (spec Q1), so the vault stays Obsidian-readable and every write reuses Session note APIs and craft-atomic. A new L3 crate notescraft-calendar holds the model, recurrence subset, quick-add parser, filter/sort and ICS parser with no IO; the engine only wires it to the vault. Alternative rejected: a WASM plugin. The Calendar is a bundled core plugin and needs vault index access that the sandbox deliberately withholds.

Delegation: phases are split into small tasks (see tasks.md, next step). Mechanical tasks (command registration boilerplate, i18n keys, serde types, unit tests for pure functions, doc updates) go to many parallel Haiku 5.5 subagents, one task and one file set each, so they never touch the same file. Judgment-heavy tasks (recurrence, quick-add parser, sync conflict policy, keychain/OAuth, keyboard model, anything security-relevant) stay with the main model. Every subagent result is checked by cargo xtask ci plus a review pass before the next phase starts.

Architecture

  • Data flow: UI / CLI run / MCP tool / assistant -> Registry command calendar.* -> Session calendar methods -> notescraft-calendar (pure) -> Vault::write (event files) and Index (reads). Sync: calendar-sync fetches off the UI thread -> cache under .notescraft/ via write_private -> same read path as local events.
  • New modules:
    • crates/calendar (L3, notescraft-calendar): model (Event, Reminder, Calendar), recur (daily/weekly/monthly/yearly + interval, weekdays, until/count, exceptions), quickadd (English phrase to Event + error naming the unparsed part), query (filter, sort, group, saved filters), ics (read-only parser, caps on size and depth), free (free-time and conflict search), review (week summary).
    • crates/calendar-sync (L3, later phase): feed refresh, Google read-only client, offline cache, sync log.
    • crates/engine/src/calendar/: command specs and Session glue, grouped by concern (events, query, accounts, reminders).
    • crates/ui-egui/src/views/calendar_view/: month, week, day, agenda, year, quick-add popover, filter popover, bulk bar, reminders inbox, keymap.
  • Modified modules:
    • crates/engine/src/vault/daily.rs:34 — keep calendar.month and daily.open unchanged.
    • crates/engine/src/ai/assistant_gate.rs:55 and crates/mcp/src/calls/gate.rs:23 — add read-only calendar ids to the read lists; writes stay default-gated so assistant changes become pending reviews.
    • crates/engine/src/prefs/config.rs:54-71 — add calendar settings (events folder, default view, default reminders, quiet hours, refresh interval) next to week_start.
    • crates/ui-egui/src/panels/sidebar.rs:318-386 — mini month stays; add a link that opens the full Calendar view.
    • crates/ui-egui/src/views/settings_view/categories.rs:43-47 — Calendar settings pages.
    • crates/ui-egui/src/views/center.rs — route to calendar_view.
    • xtask/src/layers.rs TABLE — register both crates at L3.
    • crates/i18n/locales/*.toml — keys for every new string.

Files

Paths marked new do not exist yet.

  • Create:
    • crates/calendar/{Cargo.toml,src/lib.rs,model.rs,recur.rs,quickadd.rs,query.rs,ics.rs,free.rs,review.rs,error.rs} — new, pure logic, one Error enum, #![deny(...)] header like sibling crates.
    • crates/calendar/tests/ — recurrence, parser and hostile-input suites (new).
    • crates/engine/src/calendar/{mod.rs,events.rs,query.rs,reminders.rs,accounts.rs} — new commands.
    • crates/engine/tests/calendar_commands.rs — new; command parity and panic-hunt entries.
    • crates/ui-egui/src/views/calendar_view/{mod.rs,month.rs,week.rs,day.rs,agenda.rs,year.rs,quickadd.rs,filters.rs,bulk.rs,inbox.rs,keymap.rs} — new, each under 400 lines.
    • crates/calendar-sync/ — new, phase 5.
    • apps/notescraft-cli/src/ — no new file expected; run <id> [json] already reaches every command (args.rs:387). Add friendly calendar subcommands in args.rs/commands.rs only if run proves too raw (to decide at task time).
  • Modify: files listed under Modified modules above, plus root Cargo.toml [workspace.dependencies] (add the two crates), ROADMAP.md, docs/ARCHITECTURE.md, SPEC.md item 26 (rewrite with the real command list).
  • Delete: nothing. read of each Modify file is repeated at task start; line ranges above come from this session's reads and may shift.

Data

  • Schema changes: none to a database. Event file: Markdown with frontmatter keys (title, start, end, all_day, tz, repeat, exceptions, calendar, reminders, tags, priority, status, link, note) in a vault calendar folder. Frontmatter edits reuse notescraft-markdown property functions. Unknown keys are preserved on rewrite. No migration; absence of the folder means no events.
  • API shape (engine commands, JSON in/out, same schema for CLI --json, MCP, assistant; all names calendar.*):
    • read: agenda{from,to,filter,sort,group}, next, free{day,min_minutes}, conflicts{from,to}, year{year}, review{week}, parse{text} (quick-add preview, no write).
    • write: add{event|text}, edit{id,fields,scope}, move{id,start,scope}, delete{id,scope}, note{id}, bulk{ids,action}, filter_save, filter_delete.
    • scope is required for repeating events: this|following|all; missing gives invalid_params naming the options.
    • Errors are the existing CommandError{code,message}.
    • MCP naming (decided, task 1): calendar.agenda is the engine id; its tool is calendar_agenda (SPEC 26 names). The assistant list (--via-control) keeps only aliases, search and plugin_* (crates/mcp/src/calls/tools.rs:77), so task 9 adds alias entries calendar_* in crates/mcp/src/calls/alias.rs. No plugin_calendar_* rename.

Dependencies

  • Phases 1-4: none. ICS parsing, the recurrence subset and the quick-add parser are written in-house (spec Q7). notescraft-templates Date/DateTime is reused.
  • Phase 5 (each needs owner approval before use, one written reason each, nothing added yet): HTTPS client for feeds and Google; OAuth loopback flow helper or hand-written; system keychain access; desktop notifications. Candidates and alternatives are listed at that phase's start.
  • No breaking upgrades. craft-libs rev stays pinned.

Risks

  • Recurrence edge cases (month end, DST, zones, "following" splits) produce wrong or duplicated occurrences -> table-driven tests plus a property test that expansion never loops past a cap and every occurrence is inside the window.
  • Hostile or huge input (ICS, event files, remote payloads) causes panic or memory blowup -> caps on bytes, events, depth and expansion count; add the calendar commands to panic_hunt; no indexing with [i].
  • 10,000-event target missed because agenda scans the whole vault -> in-memory event index rebuilt from VaultEvents, not re-parsed per frame; benchmark #[ignore] test with the budget in the spec.
  • Vim keys clash with editor normal mode -> keymap is scoped to Calendar focus (spec Q11); keymap is data with a unit test listing every binding to catch duplicates.
  • Credentials leak into logs or vault -> tokens only through a keychain trait, never serialised; a test greps sync log and settings output for a canary token.
  • Google release blocked by OAuth client registration (spec Q9) -> ICS ships first; Google stays behind the client id build config.
  • Weak subagent output introduces bugs (reported with the previous model) -> small single-file tasks, failing test first, clippy -D warnings and cargo xtask ci per task, main model reviews the diff before merge; no subagent edits shared files such as Cargo.toml.
  • ui-egui/src/views already holds many loose files (xtask tree limit 12) -> calendar goes in its own sub-folder.
  • Reminders only fire while the app runs (Q5) -> inbox shows missed ones on start; documented, not hidden.

Out of Scope (Defer)

  • Two-way sync, Apple EventKit and CalDAV, Outlook, generic CalDAV (spec Q3/Q4).
  • Full RFC 5545 recurrence; imported rules outside the subset show read-only.
  • Background reminder agent.
  • Non-English quick add.
  • Light-theme and 4K variants, colour-blind-safe palette.
  • Invitations, RSVP, other people's free/busy.
  • Drag with the mouse can follow the keyboard move; polish deferred.

Test Strategy

  • Unit: recurrence expansion tables, quick-add phrases and failures, filter/sort/group, ICS parser on valid and hostile files, free-time and conflict search, keymap uniqueness, week review.
  • Integration: engine tests per command (round trip file on disk, scope handling, read-only session refusal); one test asserting engine command, CLI run --json and MCP tool return identical JSON; assistant gate test (writes become pending, toggle off hides tools); panic_hunt with the 22 hostile inputs; atomic-write check; sync with a local fixture feed and offline cache; canary-token leak test.
  • UI: egui_kittest render tests per view and per keyboard flow; snapshot PNGs compared to the PaperCraft designs with --open; truncation test (ellipsis plus hover text).
  • Perf: #[ignore] benchmarks for month render and agenda filter at 10,000 events.
  • Manual: connect a real ICS feed and Google account, fire a reminder with a real system notification, confirm token meter and assistant calendar tool call in the running app.
  • Gate per task: cargo test -p, clippy -D warnings, cargo fmt; per phase: cargo xtask ci, cargo xtask i18n, cargo xtask tree.