Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
26dc550
refactor: delegate config parsing to caldav lib
tobixen Jun 11, 2026
45c5c22
fix: make the time tracking config integration work
tobixen Jun 12, 2026
8151086
feat: port --separator and interactive 'start' from archived branch
tobixen Jun 12, 2026
a59ada0
docs: code review by fable/opus
tobixen Jun 13, 2026
e002d47
fix: address 14 of 15 correctness bugs from 2026-06-12 code review
tobixen Jun 13, 2026
7592739
refactor: eliminate duplication and drift (code review C1-C4)
tobixen Jun 13, 2026
bdad729
refactor: centralise duration grammar (code review C5) + natural-lang…
tobixen Jun 13, 2026
4011aa0
refactor: address code review C6, C10, D1, D2
tobixen Jun 14, 2026
7433fe7
refactor: centralise category/categories edit handling (code review C7)
tobixen Jun 14, 2026
5b2e7b5
fix: re-wire and fix interactive_config (code review C8)
tobixen Jun 14, 2026
669a6c5
refactor: delegate VCALENDAR splitting + defer calendar discovery (C1…
tobixen Jun 14, 2026
ad6104f
perf: compile sort-key template once, not per comparison (code review…
tobixen Jun 14, 2026
7dcd59d
perf: cache relative fetches in hierarchical list (code review E3)
tobixen Jun 14, 2026
b013024
perf: fetch tasks once in set-task-attribs, filter client-side (E4)
tobixen Jun 14, 2026
f50da8a
docs: mark E5 (--uid queries all calendars) as won't-fix by design
tobixen Jun 14, 2026
e9b4c91
fix: report deleted items + empty selection in select ... delete
tobixen Jun 15, 2026
151f744
feat: --warn-on-missing-uid (default) to flag uids that match nothing
tobixen Jun 15, 2026
3187d20
fix: require caldav>=3.3.0 (URL resolution from auto-connect.url feat…
tobixen Jun 15, 2026
d091017
ci: make lychee link checking robust and consistent
tobixen Jun 15, 2026
b2bd1e1
docs: commit the panic algorithm design notes
tobixen Aug 16, 2026
4d6cf01
fix: remove stray breakpoint() from _procrastinate (code review #13)
tobixen Aug 16, 2026
a272f81
docs: changelog entries for the v1.1.1-era bugfixes
tobixen Aug 16, 2026
71e6ad8
docs: roadmap for the NLnet-funded work
tobixen Aug 26, 2026
739bd52
docs: move library work to sibling grant
tobixen Aug 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 35 additions & 12 deletions .github/workflows/linkcheck.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,20 @@ name: Link check

on:
push:
branches:
- master
pull_request:
workflow_dispatch:
schedule:
- cron: "03 22 * * *"

# Serialise runs that share a ref so two link-check runs never race to create
# the "Link Checker Report" issue. (The old setup let a push and the matching
# pull_request run both file an issue at the same second - cf. issues #44/#45.)
concurrency:
group: linkcheck-${{ github.ref }}
cancel-in-progress: false

jobs:
linkcheck:
runs-on: ubuntu-latest
Expand All @@ -26,29 +35,43 @@ jobs:
with:
fail: false
args: >-
--config .lychee.toml
--timeout 20
--max-retries 3
--config lychee.toml
--timeout 30
--max-retries 6
--retry-wait-time 2
--cache
--max-cache-age 14d
.

# Issue management only happens on the default branch (scheduled run, push
# to master, or manual dispatch). PR and feature-branch runs only surface
# the lychee output in the job log: this avoids issue noise from transient
# branch states and removes the duplicate-issue race entirely, since only
# master-ref runs touch issues and the concurrency group serialises those.
- name: Create or update Link Checker issue
if: steps.lychee.outputs.exit_code != 0
if: steps.lychee.outputs.exit_code != 0 && github.ref == 'refs/heads/master'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
EXISTING=$(gh issue list --label "report" --state open --json number --jq '.[0].number' 2>/dev/null || echo "")
if [ -n "$EXISTING" ]; then
gh issue edit "$EXISTING" --body-file ./lychee/out.md
# Keep the oldest open report issue as the canonical one and fold any
# duplicates into it, so accumulated dupes self-heal over time.
ISSUES=$(gh issue list --label "report" --state open --json number --jq '.[].number' | sort -n)
CANON=$(printf '%s\n' "$ISSUES" | head -1)
for n in $(printf '%s\n' "$ISSUES" | tail -n +2); do
gh issue close "$n" --comment "Duplicate of #${CANON} - auto-closed by the link checker."
done
if [ -n "$CANON" ]; then
gh issue edit "$CANON" --body-file ./lychee/out.md
else
gh issue create --title "Link Checker Report" --body-file ./lychee/out.md --label "report"
fi
- name: Close Link Checker issue if all links are healthy
if: steps.lychee.outputs.exit_code == 0
if: steps.lychee.outputs.exit_code == 0 && github.ref == 'refs/heads/master'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
EXISTING=$(gh issue list --label "report" --state open --json number --jq '.[0].number' 2>/dev/null || echo "")
if [ -n "$EXISTING" ]; then
gh issue close "$EXISTING" --comment "All links are now healthy."
fi
# Close every open report issue, not just the first, so stragglers from
# earlier races also get cleaned up.
for n in $(gh issue list --label "report" --state open --json number --jq '.[].number'); do
gh issue close "$n" --comment "All links are now healthy."
done
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ docs/build/
tmp/
*.log

# lychee link checker local cache
.lycheecache

# OS
.DS_Store
Thumbs.db
Expand Down
13 changes: 9 additions & 4 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,13 @@ repos:
stages: [commit-msg]

- repo: https://github.com/lycheeverse/lychee
rev: lychee-v0.23.0
rev: lychee-v0.24.2
hooks:
- id: lychee-docker
args: ["--no-progress", "--timeout", "10", "--config", ".lychee.toml"]
types: [markdown, rst]
- id: lychee
# Check links across the whole working tree (all file types, not just
# md/rst). pass_filenames: false -> pre-commit passes only '.', so the
# whole repo is scanned rather than just the changed files. lychee
# auto-discovers lychee.toml, so no --config needed.
args: ["--no-progress", "--timeout", "10", "--cache", "--max-cache-age", "1d", "."]
pass_filenames: false
stages: [pre-push]
43 changes: 43 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,49 @@
The format of this file is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and I do try to adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added

* `edit --add-categories`, `--add-resource` and `--add-resources` options (alongside the existing `--add-category`). A single value containing commas is split by the plural forms (`--add-categories`/`--add-resources`) and kept literal by the singular forms (`--add-category`/`--add-resource`).
* `select ... list --separator=...` to join the listed items with something other than a newline. (Ported from the archived development branch.)
* The interactive edit prompt now advertises the `start` command (kicks off time tracking for the task) and re-prompts afterwards so a follow-up command can be given for the same task. (Ported from the archived development branch.)
* `select --warn-on-missing-uid/--no-warn-on-missing-uid` (default: warn). A `--uid` that matches nothing in any calendar now prints a warning naming the missing uid(s) to stderr, rather than being silently ignored. `--abort-on-missing-uid` still takes precedence, and `--no-warn-on-missing-uid` restores the old fully-silent behaviour. Ref https://github.com/tobixen/plann/issues/42
* `plann configure`: an interactive configuration mode (EXPERIMENTAL/under-tested) that prompts for connection parameters and writes them to the config file. The underlying code existed but had been orphaned in the argparse→click migration; it is now wired up again, and the prompted keys match what the caldav library actually reads (so e.g. `ssl_verify_cert` is no longer written under a key that is silently ignored at connect time).

### Fixed

* `add ical` with several concatenated VCALENDAR objects no longer silently ignores them all when the data uses CRLF line endings (the RFC 5545 canonical form): the split is now done by the icalendar library instead of a hand-rolled LF-only string scan.
* Showing help for a subcommand (e.g. `plann select --help`) no longer connects to every configured calendar; calendar discovery is deferred until a command actually needs it.
* A config section carrying `features` but no `caldav_url` crashed with `KeyError: 'url'`. The caldav library resolves the URL from the server profile given in `features`, so no URL is needed. (This also requires a caldav release newer than 3.2.1 - with older caldav versions such a section is silently skipped instead of crashing.)
* The time tracking integration (`"extra_config": {"time_tracking": ["timewarrior"]}` in a config section) did not work: the configuration was attached to the calendar objects under a different attribute name than the time tracking code read, and only the value `timew` was accepted - not `timewarrior` as the error message suggested. (Fixes ported from the archived development branch.)
* Exporting an event/task to timewarrior no longer removes the categories from the object.
* `select ... delete` now reports what it did: it names each item as it is deleted, and says "No items selected for deletion" on an empty selection instead of silently producing no output regardless of whether anything matched. Ref https://github.com/tobixen/plann/issues/42
* `select ... add-time-tracking` could not be run at all - it aborted with an "Invalid start character for option" error before doing anything.
* Durations were computed wrongly in several ways: `1y` came out as roughly 15 days rather than a year, and a compound duration such as `1h30m` kept only its last component (30 minutes). Adding a year to a plain date, or to February 29, raised an error.
* `interactive check-due --limit N` crashed instead of limiting the number of tasks shown.
* `select --no-pinned-tasks` crashed when tasks were included in the selection.
* `interactive split` did not postpone the task when asked to - the prompt was inverted, so answering with a duration did nothing and declining postponed it.
* `postpone <duration> with parent` in interactive editing silently did nothing.
* The relationship overview showed only the first kind of relation, so e.g. children were listed but parents were not.
* `list --ics` exported the first matching object without applying the filter to it.
* `dismiss-panic` built a malformed timestamp (`++60d`) and failed.
* Checking due dates no longer crashes on all-day events that have relations.
* Reporting an inconsistent relationship crashed instead of logging what was wrong.
* A `time_tracking` setting written as a plain string rather than a list was read one character at a time.
* Postponing a task with parents could drop the user into the Python debugger.
* `edit` now reports clearly that no editor could be found, rather than failing in a confusing way further down.

### Changed

* `edit --set-resources` now splits its value on comma, the same way `--set-categories` always has; `--set-resources a,b` now sets two resources rather than one resource literally named `a,b`.
* `edit --set-category` is now flagged as deprecated in its `--help`: it *appends* rather than replaces (the name does not convey this). Use `--add-category` to append or `--set-categories` to replace.
* (internal) The `category`/`categories` (and now `resources`) handling on the edit path is driven by a single `COMMA_LIST_ATTRS` registry rather than being special-cased in several places.
* (internal) A template sort key (`--sort-key`) is now compiled once per key instead of being rebuilt on every comparison while sorting.
* (internal) A hierarchical `list --top-down`/`--bottom-up` now caches related tasks for the duration of the traversal instead of re-fetching the same task from the server once per relationship edge.
* (internal) `set-task-attribs` now fetches the task list once and filters client-side per attribute (via the `icalendar_searcher` library) instead of issuing a separate server query for each of category/due/priority/duration. As a side effect, on calendar servers that do not filter properly it now finds tasks missing an attribute that it previously could miss.
* Config file parsing, connection parameter extraction and calendar lookup are now delegated to the caldav library instead of being duplicated in plann. (The caldav library adopted this code from plann a while back; plann was still carrying its own copy.) Visible side effects: environment variable references like `${SOME_VAR}` or `${SOME_VAR:-default}` in config values are now expanded, and a `features` key is resolved through the caldav library's profile lookup.

## [v1.1.1] - 2026-05-28

### Added
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ The caldav URL should be something like i.e. http://some.davical.server/caldav.p
The list may not be complete. `--help` should give a more complete overview.

* list-calendars: lists the calendars that plann can see
* configure: interactive configuration mode (EXPERIMENTAL/under-tested) - prompts for connection parameters and writes them to the config file
* agenda: Convenience command, lists upcoming events and tasks
* add: adds new events/items to todo lists/calendars
* select: select/search/filter tasks/events to list/modify/mark complete and so forth
Expand Down Expand Up @@ -142,6 +143,11 @@ The file may look like this:
}
}
```

The file can be written by hand, or generated interactively with `plann configure` (experimental/under-tested - here be dragons).

A `features` key may be given to enable server-specific compatibility workarounds, referring to a server profile in the caldav library's `compatibility_hints` module (e.g. `"features": "ecloud"`). For servers with a known profile, `caldav_url` may even be omitted - the caldav library will derive the URL from the profile.

A configuration with multiple sections may look like this:

```json
Expand Down
4 changes: 2 additions & 2 deletions TASK_MANAGEMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ While the categories field is a freetext field, it's important that the same cat

My usage of categories may be slightly superceded by "concept", "link" and "refid", as defined in RFC9253. I should look into that and consider if it's useful for plann.

After some thinking, I've considered that quite much of what I use "categories" for would possibly be more appropriate to put in the "resources"-field. "Good weather" may be considered as a resource rather than a category, "keyboard" may be considered a resource, "supermarked" may be considered to be a resource. When having a certain set of resources available it makes sense to do as many tasks as possible with the given set of resources. Resources may be missing, then the alternatives are to find the missing resources (or travel to them ... or try to make without them) or to postpone the task until the resources are available. Plann has no specific support for resources, but I should consider it.
After some thinking, I've considered that quite much of what I use "categories" for would possibly be more appropriate to put in the "resources"-field. "Good weather" may be considered as a resource rather than a category, "keyboard" may be considered a resource, "supermarked" may be considered to be a resource. When having a certain set of resources available it makes sense to do as many tasks as possible with the given set of resources. Resources may be missing, then the alternatives are to find the missing resources (or travel to them ... or try to make without them) or to postpone the task until the resources are available. Resources can now be edited just like categories (`edit --add-resource`/`--add-resources`/`--set-resources`), but plann has no resource-aware planning logic yet.

RFC9073 also defines vresource, which is a more structured way of specifying resources.

Expand Down Expand Up @@ -313,7 +313,7 @@ Those are not much relevant wrg of task handling in plann

## Daily task management and interactive mode

This is specifically directed towards using plann for daily task management. See also the [USER GUIDE](USER_GUIDE.md) for more generic user guide. I will assume that your calendar server needs to support advanced CalDAV queries (see [CALENDAR SERVER RECOMMENDATIONS](docs/ALENDAR_SERVER_RECOMMENDATIONS.md)).
This is specifically directed towards using plann for daily task management. See also the [USER GUIDE](USER_GUIDE.md) for more generic user guide. I will assume that your calendar server needs to support advanced CalDAV queries (see [CALENDAR SERVER RECOMMENDATIONS](docs/CALENDAR_SERVER_RECOMMENDATIONS.md)).

### Adding tasks

Expand Down
2 changes: 1 addition & 1 deletion USER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ Those commands are made mostly for making `plann` more convenient to use for the
* interactive manage-tasks - go through your tasks and make suggestions
* interactive update-config - (TODO: NOT IMPLEMENTED YET). This one is not used by the primary author and is probably under-tested. Its primary intention is to make it easy for others to use the tool.

Note that many of those commands have only been tested on DAViCal (see the [`docs/ALENDAR_SERVER_RECOMMENDATIONS.md`](CALENDAR_SERVER_RECOMMENDATIONS.md) file)
Note that many of those commands have only been tested on DAViCal (see the [`docs/CALENDAR_SERVER_RECOMMENDATIONS.md`](docs/CALENDAR_SERVER_RECOMMENDATIONS.md) file)


## Global options
Expand Down
Loading
Loading