Two deployment bugs that both present as "the client says done but the
markdown never changes, and nothing errors".
1. upload() diffed the client's VTODO against the cached in-memory task.
When that cache disagreed with disk the diff came out empty, apply()
returned early, and Radicale answered 2xx having written nothing.
The cache goes stale on NFS: refresh_if_stale() compares stat(), and a
`- [ ]` to `- [x]` toggle does not change the file's size, so mtime is
the only signal — which NFS attribute caching hides for the length of
the attr timeout. Add Index.refresh_file(), which compares the actual
bytes, and call it before diffing.
2. docker/mdcaldav.toml set `exclude`, which REPLACES the built-in list
rather than extending it, silently dropping the Syncthing patterns.
Against a real Syncthing vault this reindexes `*.sync-conflict-*` (and
`.stversions/`, which holds whole historical copies of every note): the
task list gains duplicates, and completing one can write to a conflict
file the user never opens while the note they are watching stays put.
Drop the override so the defaults apply.
Also switch the container to a polling observer. inotify reports nothing
for a network mount written by another host, so `watch = true` was a
silent no-op exactly where it was most wanted.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>