This document is a draft - it was generated by Claude Opus 5.0. As of 2026-08-25 significant parts of it has been reviewed and edited by Tobias Brox, but not all. In any case, an estimate is an estimate - the only way to know for sure how much time a task will take is to actually do it.
This project is funded through the NGI Zero Core fund, a fund established by NLnet with financial support from the European Commission's Next Generation Internet programme, under the aegis of DG Communications Networks, Content and Technology.
The funded scope is 248 hours. This was based on some initial human-made estimates on what it would take to create a working first version of the library. This document is partly a result of an AI-based review of the original plan. The subtasks have been slightly redefined and reorganized. Deliverables have been defined. The plan has been optimized to squeeze the highest-priority tasks into the funded hours.
An earlier draft of this document said 216 hours and listed 20 items. The signed MoU carries 22, and the two extra ones are 1.6 (time-tracking model and API, 12 h, moved here from the plann grant) and 4.5 (documentation review and improvements, 20 h, carried out by a contributor from the icalendar project). 216 + 12 + 20 = 248.
The goal is a general Python client library for calendaring and task data,
with a single clean API across many backends — CalDAV servers, JMAP servers,
local .ics files, read-only iCalendar feeds, and task/issue trackers.
- Tasks should be first-class, not an afterthought. iCalendar's
VTODOis a weak task model — there is no agreed way to express a time estimate or time spent. The data model must be designed with real task trackers in view, not retrofitted. 0.1 surveys that gap; 1.6 fills it. - One API, honestly documented. Backends differ enormously in capability. The library must not pretend otherwise. A published capability matrix (4.3) is a deliverable, not documentation garnish.
- Sync and async, from one source of truth. Shipping a 2026 library without async is not defensible; shipping async-only is equally wrong, because sync code is far easier to write and to debug. The architecture must serve both without duplicating the implementation (0.2).
- Reuse, don't reinvent.
caldav,icalendar,icalendar-searcherandrecurring_ical_eventsalready exist and are maintained by the same group of authors, andcalendaring-jmapis separately funded and under way. - One protocol, one package. Logic that is not CalDAV-protocol logic does not
belong in the
caldavlibrary. This principle decides both the JMAP question and the shape of this project (see 0.3).
- Priority: Critical — blocks 1.1
- Estimated effort: 20 hours
- Deliverable: a published analysis document, plus a proposed task data model
- Dependencies: none
The README's core complaint is that no good open standard for tasks exists. This item establishes what a good model would have to contain, by surveying what real systems actually do.
Systems to survey: Gitea, GitLab, Vikunja, Kanboard, EteSync, Taskwarrior, Focalboard, OpenProject, Request Tracker, Emacs org-mode, and plain Markdown task files.
For each system, record how it models — and whether it can round-trip:
Tasks:
- Identity: is there a stable, client-assignable ID, or a server-assigned integer?
- Time estimate and time spent — the gap the README names explicitly
- Status lifecycle: is it a fixed enum, or user-definable per project/board?
- Priority: numeric, ordinal, or a label?
- Dates: due, start, scheduled, deferred — and which of them exist at all
- Recurrence: supported, faked, or absent
- Dependencies and parent/child relations
- Assignment, ownership, and multi-user semantics
- Labels/tags/categories, and whether they are free-form
- Change detection: ETag, revision counter,
updatedtimestamp, or nothing - Search and filter capabilities offered server-side
- Auth model, pagination, and rate limiting (a real implementation cost)
- Check what iCalendar already offers: RFC 9253 relationship support, and
whether its
ESTIMATED-DURATION(or any registered extension) closes the estimate gap, or whether anX-extension / native model is needed - Survey the
X-properties already used in the wild for time tracking (Taskwarrior, org-mode exports, plann) - Publish the analysis, with a recommendation for the library's task model
- Recommend which single tracker to implement first (feeds 2.5), with reasons
Note on scope: this is research plus a model proposal. Implementing a backend is separately budgeted (2.5). Twenty hours across eleven systems is roughly 1.5 h each plus write-up; that is enough for an informed survey, not for deep expertise in any one of them.
- Priority: Critical — blocks 1.1 and 1.3
- Estimated effort: 16 hours
- Deliverable: a published design document with a prototype-backed recommendation
- Dependencies: none
The caldav library reached its dual-mode design by accretion, and
ASYNC_DESIGN_CRITIQUE.md
documents the resulting failure modes: a forgotten is_async_client check silently
discards a coroutine with only an easily missed RuntimeWarning, the return type annotations lie,
and every new I/O method is a latent bug requiring three separate guards to be
correct. That history is the reason this item exists — a greenfield library with
no backward-compatibility obligation and no sunken cost in existing tests gets one
chance to choose better.
The partial Sans-I/O work in caldav succeeded at the protocol layer (pure XML builders and parsers, testable without HTTP mocks) but was abandoned at the object layer, because too many places in the old code did I/O unexpectedly. Whether that was a property of Sans-I/O or a property of retrofitting Sans-I/O onto a fifteen-year-old codebase is precisely the open question.
Candidates to evaluate:
| Approach | One source of truth? | Build step? | Notes |
|---|---|---|---|
Runtime dual-mode (if self.is_async_client) |
No | No | caldav 3.x; documented failure modes above |
| Separate async classes (motor / aiohttp pattern) | No | No | Correct types, but the I/O methods are written twice |
Generator-based Sans-I/O (yield request, driver sends the response back) |
Yes | No | Two thin drivers (~20 lines each); the h11 / hyper-h2 pattern |
Async-first + code generation (unasync, AST transform) |
Yes, in source | Yes | httpcore/urllib3 do this; cost is debuggability of generated code and stack traces |
greenlet bridging (SQLAlchemy pattern) |
Yes | No | Adds a C dependency; tracebacks are hard to follow |
Tasks:
- Write up each candidate with its cost model
- Prototype the two or three most promising against a toy two-method backend
- Measure, for each prototype: implementation LoC, test LoC, whether one test suite can drive both modes, mypy/pyright correctness of the public signatures, traceback quality when a backend raises mid-operation
- Answer explicitly: can the object layer be Sans-I/O without an ugly public
API? A caller must be able to write
event.save()orawait event.save(), notclient.execute(event.build_save_request()) - Check how the chosen approach survives an HTTP-paginated backend (issue trackers) and a filesystem backend, not only a single-request-per-call one
- Publish the comparison
- The author's decision, recorded in the comparison's last section
Explicitly rejected without further study: wrapping async in asyncio.run() for
the sync path. Nested event loops are forbidden and it cannot be called from an
already-running loop.
Note: the author's stated position is that "write async, generate sync" is unattractive. The generator-based Sans-I/O and greenlet options are the two that give a single source of truth with no build step, and generator-based Sans-I/O is the one that matches the instinct behind this item. The research must still be allowed to reach a different conclusion.
It was given the chance: codegen (unasync) was prototyped too. Two candidates
hold up — generator-based Sans-I/O with typed façades (p2b) and async-first
source with a generated sync copy (p4) — and the prototypes do not settle which
serves the project better. The comparison sets out the trade without
recommending one; see §10 of the comparison.
The author chose p4, for its aesthetics and debuggability, despite the
reservation about generated code noted above; see
§11.
Note: We should in general be careful relying too much on the AI-generated code and documentation, but GenAI is great for rapid prototyping.
- Priority: High — blocks 1.1
- Estimated effort: 8 hours
- Deliverable: a short document recording each decision and its rationale —
PRIOR_ART_AND_DECISIONS.md, which records the author's decisions of 2026-10-05 - Dependencies: none
Tasks:
- Settle the name.
calendaring, the same on GitHub, PyPI and as the import name (D2). It does not signal task support by itself; the tagline carries that. Free on PyPI as of 2026-10-05. (this decision may need reversal, the rest of the team thinks it's not intuitive what a "calendaring"- package is doing) - Write up the packaging principle. (D1) Two questions a reviewer will ask are
already decided, and both follow from one principle: logic unrelated to the
CalDAV protocol does not belong in the
caldavlibrary. - This library is a separate package, not a backend layer inside a hypothetical caldav v4. A multi-backend abstraction is by definition not CalDAV-protocol logic. - JMAP lives incalendaring-jmap, a separate package, separately funded by NLnet and under way. It is not part of this project's 248 hours. Thecaldav/jmap/module currently inside the caldav library is by the same principle misplaced; migrating it is caldav's andcalendaring-jmap's business, not this roadmap's. The work here is to record the principle and its consequences, not to decide them. - Review
icalcli's pluggable-backend design (already researched in an earlier README; see PRIOR_ART_AND_DECISIONS §1.1) - Review how HomeAssistant currently handles calendar integrations, and whether this library is useful to them (README names them as a target user)
- Standards review: RFC 5545 (iCalendar), RFC 5546 (iTIP), RFC 7265 (jCal), RFC 9253 (relationships), JMAP Calendars (RFC 8984 / JSCalendar), ActivityPub, org-mode. Record which are in scope for the funded period.
- Record a Python version support floor and a typing strictness policy:
>=3.11(D4),mypy --strictpluspyright --verifytypes(D5)
- Priority: Critical
- Estimated effort: 20 hours
- Dependencies: 0.1, 0.2, 0.3
- Deliverable: a documented API specification, peer-reviewed — API_DESIGN.md (drafted 2026-10-08; peer-reviewed in PR 5 and in video calls, 2026-10-10 – 2026-10-13)
The API should feel familiar to caldav users without being a CalDAV API in disguise. The test of the design is not "does it fit CalDAV" but "does it fit CalDAV and a Gitea issue tracker without lying about either".
Tasks:
- Define the object model: workspace, backend, collection, calendar object, task, event, journal
- Define the search/filter interface, reusing
icalendar-searcherso that client-side and server-side filtering behave identically - Define the capability-declaration mechanism: how a caller asks a backend what it can do, and what happens when it asks for something unsupported (raise, degrade, or emulate client-side — and how the caller chooses)
- Define error taxonomy and the exception hierarchy
- Define change detection across backends with no ETag (mtime, revision counters, or nothing)
- Define the escape hatch: how a caller reaches backend-specific functionality without the abstraction pretending it does not exist
- Incorporate the task model from 0.1; fields with no standard property
are stored as
X-properties (0.3, D6) - Peer review, and revise. Identify and approach reviewers early — a review is a dependency on someone else's calendar, not a quantity of work.
- Priority: Critical
- Estimated effort: 14 hours
- Dependencies: 1.1
- Deliverable: ABCs plus an executable conformance suite —
calendaring.testing(AsyncConformance/SyncConformance), the ABCs insrc/calendaring/_async/, and the in-memory reference backendcalendaring.memory(drafted 2026-10-14)
The conformance suite is the load-bearing piece of this project: one test suite that every backend must pass, parameterised by declared capability. It is what makes "the same API regardless of backend" a checkable claim rather than a promise, and it is what makes each later backend cheap to add.
Tasks:
- Write the conformance tests first, against the API spec, before any ABC exists
- Confirm the tests fail (there is nothing to run them against yet)
- Define the abstract base classes the tests imply
- Make the suite capability-aware: a backend declares what it supports, and unsupported operations are asserted to fail in the specified way rather than being skipped silently
- Make the suite run in both sync and async mode, per 0.2's conclusion
- Build an in-memory reference backend so the suite has something to pass
Notes from doing it:
- The suite is written once, async, and the sync suite is generated from it
like the library code. That needed 1.3's generator early, so
tools/generate_sync.pyand its freshness test came with this item; the rest of 1.3 (type-checker gate, specimens, replacement-map hardening, contributor documentation) is still open. One finding for 1.3 already: unasync's output depends on the Python version. From 3.12 (PEP 701) an f-string is tokenised into parts and names inside its expressions are renamed; on 3.11 it is one token and they are not. A test now rejects a name to be renamed inside an f-string in_async/. - The reference backend takes a capability profile, and four profiles run
the suite:
FULL,TRACKER(tasks only, mapped status and priority, assigned UIDs),MINIMAL(writable, most optional features left out, no collections of its own, so the suite borrows one that already holds an item) andFEED(read-only). Three more in the test module (events only, tasks without a status, and a feed of tasks) reach the branches these four left out, which a review found (issue 21); CI fails below 95 % branch coverage of the suite. The supported branches ofrecurrence.edit-occurrence,recurrence.edit-this-and-futureandtask.complete-recurring(below) are still not run. Every skip says why: a write test on the read-only profile after the write was checked to raise, or a recurrence test where nothing recurs. - A backend implements a handful of primitives (
_query,_fetch,_create,_update,_delete); capability checks, loss policies, client-side filtering, change emulation,moveemulation,relativesandverifyare written once in the base class. - Two features were added to §5.1:
recurrence.edit-occurrence(saving one occurrence) andtask.complete-recurring. Every backend, CalDAV included, declares bothUNSUPPORTEDuntil edit-icalendar has them (API_DESIGN Q3). - Sorting reads
icalendar_searcher.Searcher._sort_keys, a private attribute;test_search_sortscatches a rename, and a public accessor belongs in icalendar-searcher. save()always writes, as Q13 decided on 2026-10-15. The trackedcomponentsubclass it had decided before would have waited for icalendar issue 1914.
- Priority: Critical
- Estimated effort: 10 hours
- Dependencies: 0.2, 1.1
- Deliverable: the chosen architecture, implemented and enforced
Tasks:
- Before implementing: a clean-context review of 0.2's
unasyncprototype (p4) and the claims made for it - Implement 0.2's decision (p4) as reusable scaffolding: async-first
source in
_async/, a recursive generator that writes_sync/and deletes orphans, and a freshness test that compares file sets as well as contents; per-mode code in a hand-written layer underneath - Decide between depending on
unasyncand vendoring an equivalent - Maintain the generator's replacement map for names that must not follow
the
Async*→Sync*rename, such as unprefixed public sync classes - Gate CI on pyright, or mypy with
check_untyped_defs, over both copies, plus ruff F841, with a test that the checker flags a committed specimen; run the conformance suite under-W error - Check that
_async/contains nothing a token rewrite breaks (asyncio, third-partyAsync*names,await(...)) - Make dev-only tools fail the build when missing in CI rather than skip
- Ensure the public type signatures are correct under mypy in both modes —
the caldav failure was annotations that lie — and gate CI on a test that
the checker catches a missing and a spurious
await(0.2's three cases) - Document the pattern for contributors adding a backend
Enforcement is not optional; it is the whole point of the item.
Notes from doing it:
- The generator, its freshness test and the per-mode layer
(
_concurrency.py) came with 1.2. This item added the enforcement around them:tests/test_enforcement.pyruns mypy and ruff, with the project's own configuration, over the planted mistakes intests/specimens/and asserts that exactly the marked lines are flagged, in both modes. The correct calls in the same files are 0.2's case 1, for the main entry points (open,collection,save,complete,tasks,iter_search,search). The shapes the checkers miss, such as a missingawaitin a comprehension, are planted unmarked, so a checker that starts or stops catching one fails the test. The plainmypyandruff check .runs exclude the specimens. - mypy
strictimpliescheck_untyped_defs. The specimen's unannotated method is the test that it is in effect. pyright is not run: mypy already flags every shape pyright does in 0.2's table. -W errorwas already infilterwarnings. A test now runs pytest with the project's configuration on an async test that drops a coroutine, and asserts that the test fails.- unasync stays a dependency, pinned exactly, rather than vendored.
Against it: four years between 0.5 and 0.6,
import setuptoolsat import time, and output that depends on the Python version for f-strings (1.2's note). Vendoring would remove the setuptools import and nothing else, at the price of owning a token rewrite. Each of its quirks now has a test (f-strings,aclose,await(...), third-partyAsync*names), the output is committed, so users never install it, and the pin makes every contributor generate the same bytes. Revisit if a new Python breaks it. The CI matrix runs the freshness test on every supported version. - The replacement map is checked from both ends. A generated class may
keep the
Syncprefix only if it is a bound item orSyncConformance, and an entry no_async/source uses any more fails the build. - A missing dev tool fails the tests that need it rather than skipping
them.
test_generated_sync.pyimports unasync, andtest_enforcement.pyrefuses a missing mypy or ruff. - CONTRIBUTING.md documents the rules for
_async/and the steps for adding a backend.
- Priority: High
- Estimated effort: 8 hours
- Dependencies: 1.1
Note: plann delegates credential storage to this library — its roadmap item 1.3
is deliberately the command-line half only. See
plann issue #49. This item
therefore has a committed downstream consumer and should not be trimmed.
Tasks:
- Config file format and schema, supporting multiple accounts and backends
- Config file discovery (XDG paths, explicit path, environment)
- Credential handling: keyring integration, and never requiring secrets in plaintext config
- Settle the shared file with the caldav and calendaring-jmap maintainers: see CONFIGURATION_PROPOSAL.md. The parser is now a package of its own, calendaring-config; caldav has yet to adopt it (caldav issue #786), and the file name is still open.
- Check existing conventions first —
caldav,plann,vdirsyncer,khal, etc — rather than inventing a fifth format (probably build on the caldav format) - Leave room for backends beyond the funded ones without format changes:
GitHub first, then any plugin backend. A section's
kindnames any registered backend; a GitHub section still needsurluntilopen()makes it optional, a gap the configuration proposal notes. - Document the format: calendaring-config's
docs/format.md
- Priority: High
- Estimated effort: 4 hours
- Dependencies: none
Modest, because the author has an established pattern to copy from
icalendar-searcher and caldav as well as other pycal-projects.
Tasks:
-
pyproject.toml, hatch with tag-derived versioning - ruff and pre-commit, matching the sibling projects
- CI matrix across supported Python versions
- PyPI publishing workflow (
publish.yml, registered as the trusted publisher on pypi.org) -
license-filesfor theCOPYING.*already in place (0.3, D3), SECURITY.md, a towncrier changelog (0.3, D7)
- Priority: High
- Estimated effort: 12 hours
- Dependencies: 0.1, 1.1
- Deliverable: a time-tracking data model and the public API over it
- Downstream consumer:
plannPhase 4 (18 h), which is budgeted on the assumption that this exists
This is not 0.1. 0.1 is the survey: it asks how eleven real systems represent a
time estimate and time spent, whether RFC 9253 or ESTIMATED-DURATION closes the
gap, and which X- properties are already used in the wild by Taskwarrior, org-mode
and plann. It produces a recommendation. This item builds the thing 0.1
recommends, and puts an API on it. The split is the same one as 0.1 → 2.5: research
in Phase 0, implementation in the phase that owns the code.
It is a separate item rather than part of 1.1 because it has a committed downstream
consumer with its own funded hours. plann's roadmap item 4.1 specifies
complete --spent/--log/--start/--end "writing through the library's time-tracking
API", and 4.2's sum_hours aggregates what this model stores. Those 18 hours are
funded in the plann grant on the assumption that this API exists; these 12 hours
were moved out of that grant for the same reason (see the note in the MoU). It
therefore must not be trimmed, and it must land before plann reaches its Phase 4.
Tasks:
- Implement the model 0.1 recommends: estimate, spent, and the relationship between them
- Decide and document the iCalendar representation, including whether an
X-property is used and under whose namespace — iCalendar has no standard answer, and whatever is chosen here is what other tools will have to read - Time spent is an append-only log with the total derived — settled by 0.1 (survey 3.2). What remains is what round-trips through a backend that stores only the total
- The API: start, stop, log a duration after the fact, amend, and read back
- Map it onto each backend's native notion where one exists (task trackers generally have one; CalDAV does not), and declare the capability honestly where it does not — the capability and the hooks are in, and CalDAV (2.1) declares it; a tracker's own log is 2.5's
- Extend the conformance suite (1.2) to cover it, including the lossy cases
- Aggregation primitives sufficient for
plann'ssum_hours— grouping a selection into budget lines with per-line subtotals, keeping committed and spent time separate - Coordinate the design with
plannbefore freezing it; a review of how the design affectsplannis cheap now and expensive after 1.0
Notes from doing it:
- The design is "Time tracking" in API_DESIGN §3.4.
It was reviewed by plann's author in three rounds. His comments
changed who did the work from
ORGANIZERto RFC 9073PARTICIPANT, and cut the plannedX-properties from five to two (X-PYCAL-TIME-ENTRY,X-PYCAL-TIMER-START). plann's earlier design notes (aVJOURNALper entry) were set aside: aVJOURNALholds no interval. - Time is logged against events as well as tasks (time spent in a meeting), so the methods are on every bound item.
- Research during the review overturned an assumption of the survey: the
trackers it took for total-only (GitLab, Request Tracker, Kanboard,
OpenProject, Gitea) mostly keep individual entries, and what they lack is
the start time. So
time-logisLOSSYwithdetails["lost"]naming the missing fields;total-onlyis the exception. - Two fake backends in
tests/_async/test_memory_conformance.py, a tracker's own log without start times and a total-only one, run the lossy branches until 2.5 brings real ones. - The 2.1 backend declares
time-logFULLon a server caldav has hints for (the conformance suite checks thePARTICIPANTcomponent on Xandikos and Radicale),UNKNOWNon another, andUNSUPPORTEDin a calendar holding no events, wheretime_log_collectionpoints elsewhere.
Each backend is "done" when it passes the conformance suite (1.2) in both sync and async mode, and its capability declaration is accurate.
- Priority: Critical
- Estimated effort: 16 hours
- Dependencies: 1.2, 1.3
Thin in principle — it wraps the caldav library — but the mapping work is real, and this is the backend that will expose every place the unified API and CalDAV disagree.
Tasks:
- Map the unified API onto
caldav3.x - Map caldav's compatibility-hint/quirk system onto the capability declaration
- Handle the sync/async impedance between caldav's dual-mode design and
p4 as 1.3 builds it — this is the interesting part, and the part most likely
to overrun. caldav's
Async*names are aliases of its dual-mode classes, so a thin, honestly typed shim is needed for type checking to work in this layer at all (see 0.2's comparison, What is not settled), and every caldav name needs a replacement-map entry so the generated sync layer imports names that exist (SyncDAVClientdoes not) - Settle API_DESIGN Q12 (
delete()on an occurrence, and conflicts between sibling occurrences) before mappingdelete()andsave()for occurrences - Tune
verify=True's comparison against what real servers rewrite (parameter quoting, splitCATEGORIES, TZID to UTC), and compare subcomponents such asVALARMtoo; it compares raw unfolded lines today (roadmap 1.2 review) - Decide what
get(uid)returns for a stored object holding several overrides and no master; it returns the first today (roadmap 1.2 review) - Pass the conformance suite
- Record, do not silently fix, bugs found in
caldavitself: those fixes are caldav's budget, not this project's (caldav #797, its test servers; the occurrence-deletion plan is on #598)
- Priority: High
- Estimated effort: 14 hours
- Dependencies: 1.2, 1.3
The README promises that the API works identically against local files and remote
servers. That promise is what makes this more than a glob() call.
Tasks:
- Support both layouts: one directory of
.icsfiles, and a single file (and a root directory of vdir collections, as vdirsyncer writes them) - UID-to-filename mapping, including collisions and unsafe characters
- Atomic writes and file locking (a lock only for the single file, whose writes are a read-modify-write of the whole file)
- An ETag surrogate (mtime and/or content hash) for collision detection
- A sync-token surrogate for change detection
- Search via
icalendar-searcher, so filtering behaves as it does server-side - Full CRUD, passing the conformance suite. Occurrence edits and
recurring-task completion stay
UNSUPPORTEDuntil edit-icalendar has them (API_DESIGN Q3)
- Priority: High
- Estimated effort: 6 hours
- Dependencies: 1.2, 1.3, 2.2
The best value-per-hour item in the roadmap: a long-standing user request that was
rejected as out of scope for the caldav library, and cheap here because
icalendar-searcher already provides the filtering and 2.2 already provides the
parsing.
Tasks:
- Fetch and parse a remote
.icsURL - Detect whether a URL is a feed or a CalDAV endpoint
- HTTP caching:
ETag/If-None-Match,Last-Modified, and honourREFRESH-INTERVAL(RFC 7986) where present - Declare read-only capability honestly, so writes fail as specified
- Pass the read-only subset of the conformance suite
- Priority: Medium
- Estimated effort: 14 hours
- Dependencies: 1.2, 1.3, and a usable
calendaring-jmap - External dependency:
calendaring-jmap, a separately NLnet-funded sibling project. See the scheduling risk below.
Fourteen hours buys the backend, not the JMAP protocol implementation — that
happens in calendaring-jmap, on its own budget. The estimate is unchanged from
when the package's location was still an open question, because the cost here is
dominated by model mapping and conformance work rather than by how clean the
wrapped API turns out to be.
Tasks:
- Wrap
calendaring-jmapbehind the unified API, as an optional extra: it is AGPL, so a program that includes it must be conveyed under AGPL terms as a whole, which the docs must say (0.3, D3) - Map JSCalendar objects to the unified model
- Pass the conformance suite against Stalwart
- Verify Cyrus's JMAP calendar coverage before promising compliance against it; if it is partial, document the gap rather than working around it
- Declare capabilities accurately, including anything JMAP does that CalDAV cannot
- Priority: High
- Estimated effort: 12 hours
- Dependencies: 0.1, 1.2, 1.3
- Deliverable: one working task backend, chosen in 0.1
This is where the abstraction is proved or disproved. If the unified API cannot express this backend without contortion, the finding is more valuable than the code.
Tasks:
- Implement the tracker recommended by 0.1
- Run Gitea as a CI service container (official image, SQLite) for the conformance suite; a permanent instance is optional, for dogfooding
- Auth, pagination and rate limiting for that tracker's API
- Map its task model to the library's, and record every lossy mapping
- Let the write primitives apply API_DESIGN §3.5's
native-value rule:
_create/_updateget only a calendar today, andcomplete()dropsnative_statusthroughas_data(); add a conformance test that an unchanged save keeps the native value (roadmap 1.2 review) - Report unknown IANA properties and subcomponents as losses where
properties.passthroughisUNSUPPORTED; onlyX-properties are today (roadmap 1.2 review) - Pass the conformance suite, with unsupported operations failing as specified
- Write up what did not fit — this feeds the capability matrix (4.3) and any later API revision
GitHub is the next tracker after this one, even though it is not funded (see Beyond the funded scope). 0.1 did not survey it.
- Priority: High
- Estimated effort: 10 hours
- Dependencies: all of Phase 2
Tasks:
- Run the full conformance suite against every backend, both modes, in CI
- Fix the divergences the suite exposes
- Add regression tests for every bug found during Phase 2
- Measure and report coverage
- Priority: Medium
- Estimated effort: 8 hours
- Dependencies: 2.1
The caldav project already maintains docker-compose test infrastructure for eleven servers (Baïkal, Bedework, CCS, Cyrus, DAViCal, Davis, Nextcloud, Open-Xchange, SOGo, Stalwart, Zimbra). This item reuses that infrastructure rather than rebuilding it, which is why 8 hours is plausible where a from-scratch effort would be four times that.
Tasks:
- Run this library's conformance suite through the existing docker test servers
(
pytest --caldav-servers=...), along with readable scenario tests; run on Baikal and SOGo so far - Confirm the capability declarations match observed behaviour per server
- File server-specific divergences as issues; fixing quirks in the caldav library is caldav's budget
- If the target is a specific server count, name the servers — the existing infrastructure covers eleven, and each addition costs real hours
- Priority: High
- Estimated effort: 6 hours
- Dependencies: 2.1, and ideally 2.5
The strongest available evidence that the API design works. plann is a real
consumer of the caldav library, written by the same author, and the README names
multi-backend support as something it wants.
Tasks:
- Port a meaningful subset of
plannonto this library - Record every place the unified API was harder to use than caldav's
- Feed the findings back into the API before 1.0
- Not budgeted: completing the port. Six hours buys evidence, not a migration.
- Priority: High
- Estimated effort: 10 hours
- Dependencies: Phase 2
Modest because the API reference should be generated from docstrings written as part of each implementation task, not written separately afterwards. Docstring effort is budgeted inside Phases 1 and 2.
Tasks:
- Sphinx or mkdocs setup with autodoc, building in CI
- Documentation site structure
- Verify every public API has a usable docstring — a CI check, not a read-through
- Publish (readthedocs or equivalent)
- Priority: High
- Estimated effort: 8 hours
- Dependencies: 4.1
Tasks:
- Getting started: open, list, read
- CRUD for events and for tasks
- Searching and filtering
- The same program run against three different backends — the project's whole selling point, demonstrated
- Sync and async side by side
- Configuration file walkthrough
- Every example executable and tested in CI, so the tutorial cannot rot
- Priority: Critical
- Estimated effort: 6 hours
- Dependencies: Phase 2, 3.1
The most-cited document this project will produce, and the mitigation for the central risk in the whole design (see Risks). It states plainly what each backend supports, what it emulates client-side, and what it cannot do at all.
Tasks:
- Generate the matrix from the backends' capability declarations, so it cannot drift from the code
- Annotate each "no" with the reason
- Distinguish "unsupported by the protocol" from "unsupported by this backend implementation" from "supported, but emulated client-side"
- Publish it prominently — this is a document other projects will link to
- Priority: Medium
- Estimated effort: 6 hours
- Dependencies: 4.1, 4.2, 4.3
Tasks:
- Recruit reviewers who are not the author, ideally from the target user groups
(HomeAssistant,
plannusers, CalDAV library users), more realistically from the pycal developer pool. - Revise accordingly
- Record what the reviewers found confusing — that list is itself useful output
- Priority: Medium
- Estimated effort: 20 hours
- Dependencies: 4.1, 4.2, 4.3
- Carried out by: a contributor from the
icalendarproject (@stevepiercy), not the maintainer
Distinct from 4.4. 4.4 is peer review: recruiting outside readers, collecting what confused them, and revising. This item is editorial work on the text itself by a technical writer who is doing the same job across the whole pycalendar ecosystem, so the ecosystem's documentation reads as one body of work rather than five. These hours are budgeted separately in the MoU and are mostly not the maintainer's time; they cannot be moved into Phases 0-3.
Tasks:
- Editorial pass over the structure, the tutorial and the capability matrix
- Consistency with the sibling projects' documentation: terminology, headings, code-sample conventions, cross-references
- Corrections and improvements arising from that pass, filed and applied
- Ensure the published site is navigable from the sibling projects and vice versa
| Release | Contents | After |
|---|---|---|
| 0.1.0 | CalDAV backend behind the unified API, config file, sync + async | 1.5, 2.1 |
| 0.2.0 | Local files and iCalendar feeds | 2.2, 2.3 |
| 0.3.0 | JMAP | 2.4 |
| 0.4.0 | First task backend, time tracking, capability matrix | 1.6, 2.5, 4.3 |
| 1.0.0 | Conformance across all backends, full documentation, API declared stable | Phases 3 and 4 |
The README describes 0.1.0 as "not much more than a wrapper for the CalDAV library". That remains accurate — but the framework beneath it (1.1–1.3) is what the remaining releases depend on, and is where the funded effort concentrates.
| Phase | Item | Hours |
|---|---|---|
| 0 | 0.1 Task and issue-tracker data model | 20 |
| 0 | 0.2 Sync/async architecture | 16 |
| 0 | 0.3 Prior art, standards and project decisions | 8 |
| 1 | 1.1 Unified API design and peer review | 20 |
| 1 | 1.2 ABCs and backend conformance suite | 14 |
| 1 | 1.3 Sync/async scaffolding | 10 |
| 1 | 1.4 Configuration and credentials | 8 |
| 1 | 1.5 Packaging, CI and release infrastructure | 4 |
| 1 | 1.6 Time-tracking model and API | 12 |
| 2 | 2.1 CalDAV backend | 16 |
| 2 | 2.2 Local iCalendar file backend | 14 |
| 2 | 2.3 iCalendar feed backend | 6 |
| 2 | 2.4 JMAP backend | 14 |
| 2 | 2.5 First task-tracker backend | 12 |
| 3 | 3.1 Cross-backend conformance | 10 |
| 3 | 3.2 CalDAV server compatibility | 8 |
| 3 | 3.3 Dogfooding: plann | 6 |
| 4 | 4.1 Documentation structure and API reference | 10 |
| 4 | 4.2 Tutorial | 8 |
| 4 | 4.3 Backend capability matrix | 6 |
| 4 | 4.4 Documentation peer review and revision | 6 |
| 4 | 4.5 Documentation review and improvements | 20 |
| Total | 248 |
| Phase | Hours | Share |
|---|---|---|
| 0 — Research and design | 44 | 18% |
| 1 — Framework | 68 | 27% |
| 2 — Backends | 62 | 25% |
| 3 — Verification | 24 | 10% |
| 4 — Documentation | 50 | 20% |
CalDAV, an .ics feed and a Gitea issue tracker have little in common. A unified
API over all three risks being either too thin to be useful — every real caller
reaching for a backend-specific escape hatch — or leaky enough that it lies about
what backends can do.
Mitigation: the capability declaration (1.1) and the capability matrix (4.3) are design constraints from day one, not documentation written at the end. The conformance suite (1.2) is capability-aware, so "this backend cannot do X" is an asserted, tested fact rather than a skipped test.
Mitigation: 0.1 runs before the API is designed, and 2.5 implements a real tracker before 1.0. If the model fails, it fails at hour ~175, not hour ~245.
If the chosen architecture turns out unworkable during Phase 2, the cost is a rewrite of the scaffolding and every backend's I/O path.
Mitigation: 0.2 prototypes rather than reasons in the abstract, and 2.1 — the backend most likely to expose the problem, because it must bridge to caldav's own dual-mode design — is the first one implemented.
calendaring-jmap is separately funded and under way, but this roadmap cannot
control its schedule. If it is not usable by the time Phase 2 reaches it, 2.4 stalls.
Mitigation: 2.4 sits late in Phase 2 and is one of the two designated release valves (with 2.5). The conformance suite (1.2) means the backend can be written against the sibling package's API as soon as that API is settled, rather than waiting for the package to be complete. If the slip is severe, the 14 hours move to 2.5 or to Phase 3 and JMAP lands in a later release — the release plan already isolates it in 0.3.0.
The primary maintainer has a lot of packages to juggle, both packages funded by NLNet and other packages - as well as other priorities and concerns in life. It's a real risk that time will run out before the allocated funds run out.
It's important to be aware that the number of calendaring packages is a deliberate consequence of the packaging principle rather than accidental sprawl. Theoretically we could have launched a pycal-package "doing everything calendar-related". Maintaining five packages instead of one monolithic package does involve some more overhead and worries - but we believe it's better for the community to have small and dedicated packages doing one job right, rather than a big monolithic monster-package.
Mitigation:
- There are more developers in the pyorg-pool, so other people may pick up bits and pieces if the primary maintainer won't have sufficient time for it.
- Getting this project out is a priority for the maintainer, if he has to priority it's more likely that the funding for the many of the less important features in the CalDAV project will discarded.
Two items (1.1, 4.4) require external reviewers, and a third (4.5) is carried out entirely by a named outside contributor.
Mitigation: approach reviewers during Phase 0, not when the work is ready.
Everything below is in the README's ambition but outside the 248 funded hours. The maintainer has a vision to continue maintenance and development of the project with or without funding, though the first priorities will always be to get food on the table.
| Item | Rough estimate | Notes |
|---|---|---|
| Mobilizon backend, bidirectional | 40–60 h | README calls this "quite high on my list"; federated event distribution, ActivityPub-based |
iTIP over email: sending .ics attachments |
16–24 h | RFC 5546; the sending half is much cheaper than the receiving half |
| Mailbox access via IMAP and/or JMAP mail | 32–48 h | Reading invitations out of a mailbox; a different problem from calendar access |
| GitHub issues backend | 12–16 h | First priority after 2.5, funded or not: the project's own issues live on GitHub. Proprietary and not self-hostable, so CI runs against recorded responses or a dedicated test repository. Issues are REST; Projects v2 fields (status, estimates) are GraphQL-only. No estimate, time log or priority on an issue, so it tests the capability declaration much as Gitea does |
| Additional task-tracker backends | 12–20 h each | After 2.5 the marginal cost drops, but auth and pagination are per-tracker |
| Emacs org-mode backend | 24–32 h | Parsing org is its own project; a subset may be tractable |
| Plain Markdown task files | 8–16 h | Depends entirely on how much convention is assumed |
| jCal (RFC 7265) and xCal (RFC 6321) representations | 16–24 h | Format work, not backend work |
| Proprietary systems | unbounded | README's position: pull requests welcome, no effort committed |
Completing the plann migration |
24–40 h | 3.3 buys evidence; this buys the migration |
vobject and ical representations |
unknown | Conversion helpers, and borrowing another library's object (lend it out, parse it back on exit; not the change tracking of API_DESIGN Q13). Raw data in and out (from_ical(), to_ical(), replace_ical()) is in the funded API; see API_DESIGN, Raw data and other libraries. Mentioned so that it is not added ad hoc |
| Save if edited | 4–8 h | save() skips an item nobody edited, sparing the round trip, the new etag that makes other clients download it again, and a needless ConflictError. Needs a check whether the item changed; the simplest compares to_ical() with the serialisation taken when the item was read, at one extra serialisation per read (API_DESIGN §12), or icalendar may offer one (icalendar#1922). Whether skipping becomes the default or an opt-in, and whether wire= and overwrite=True always write, is part of the work. The funded save() always writes (API_DESIGN Q13) |
| Implicit synchronization | unknown | Edits reach the backends without an explicit save(); write strategies and transactions, API_DESIGN §12. Agreed in the PR 5 video meeting: wanted, but not a priority. The funded API keeps save() and reload() explicit. Needs a check whether a component changed, which may need enhancements in icalendar; the discussion in icalendar#1922 now proposes a check against a deep-copied snapshot (was_modified_since(), not implemented yet) rather than change notification; a yes/no answer is enough |
- RFC 5545 — iCalendar
- RFC 5546 — iTIP
- RFC 4791 — CalDAV
- RFC 7265 — jCal
- RFC 7986 — New iCalendar Properties
- RFC 8984 — JSCalendar
- RFC 9253 — Support for iCalendar Relationships
- ActivityPub
- python-caldav — same author
- icalendar-searcher — same author
calendaring-jmap— same author, separately NLnet-funded; provides the JMAP protocol layer that 2.4 wraps- plann — same author, target consumer
- icalcli — prior art for pluggable backends
ASYNC_DESIGN_CRITIQUE.md(caldav) — why dual-mode is fragileASYNC_DUAL_MODE.md(caldav) — the rules the 3.x pattern requiresSANS_IO_DESIGN.md(caldav) — what Sans-I/O achieved and where it stopped
This roadmap was drafted with AI assistance (Claude Opus 5 via Claude Code) and edited by the author. Estimates are the author's responsibility.