Skip to content

Lazyplanner v1.5.0

Latest

Choose a tag to compare

@littekge littekge released this 27 Jul 03:48

LazyPlanner v1.5.0 — polishing & auditing

The final planned release of the feature line. v1.5.0 adds no major features by design — it is a
consolidation release: an exhaustive spec-vs-code audit, a UI/keymap consistency sweep, six deep
hardening passes, and a full rework of how recurring items handle time zones.

133 commits since v1.4.0 — 6 features, ~60 fixes, 4 performance fixes, 1 owner-directed revert.


⚠️ Security fix

A malicious or compromised CalDAV server could steal your app password. A server-supplied resource
href of the form //other.host/x.ics is a protocol-relative URL, not a path, so it replaced the
configured endpoint's host. An authenticated PUT then delivered your Basic-auth app password and the
full calendar body
to that host, returned success, and the app marked the resource as cleanly synced
while your real server never saw the write. DELETE and MKCALENDAR were affected the same way, and a
poisoned href persisted locally so every later write re-targeted that host.

LazyPlanner now refuses any href that resolves off the configured endpoint's origin, and drops such an
href at ingest so it is never stored.

Note for reverse-proxy setups: if your server advertises hrefs on a different origin than the URL
in your config (e.g. it returns a public hostname while you connect to an internal one), those requests
now fail with an error naming both origins. Point url at the advertised name to fix it.


Highlights

Recurring items now keep the right day and time

A recurring item's anchor is written as local time with a TZID plus a generated VTIMEZONE, instead
of UTC. RFC 5545 evaluates a rule's BY* parts in the anchor's own zone, so a UTC anchor made locally
authored rules fire on the wrong day. Fixed:

  • Opening a recurring item's edit form and pressing Save without touching Repeat no longer rewrites
    the rule (it used to shift the series by a day and drop per-occurrence edits).
  • Creating a "Weekly on Tue" event no longer produces a series that fires every Monday.
  • A weekly 8pm meeting stays at 8pm across a DST change instead of drifting to 7pm.
  • A recurring item no longer loses the day its zone's DST change starts on. This affected 11 zones
    (Havana, Santiago, Coyhaique, Punta Arenas, Azores, Scoresbysund, São Paulo, Asunción and others) at
    every frequency, and a recurring task would skip that day permanently and push the gap to the server.
  • "Ends on date D" now includes D, for both all-day and timed items.
  • FREQ=YEARLY;BYMONTHDAY=n is read correctly (it fires 12×/year, and a grab used to make the event
    vanish).

One time zone, everywhere

The app now resolves one zone at startup and uses it for display, authoring and decoding. Previously
nine decode sites used the system zone while the UI used the app's resolved zone; when those disagreed an
all-day event could render on two days and "today" could land on the wrong day. $TZ is now honoured in
every form Go itself accepts (TZ=, TZ=:Zone, and absolute zoneinfo paths).

UI

  • Agenda board is clickable — single click selects, double click edits the row under the cursor.
  • +/- accordion now collapses the Detail pane along with the overview column.
  • Task form has a Location field. Previously a task's location was silently erased by a quick-set
    (sp/sd) or by opening the form and saving without changing anything.
  • sd on a recurring task re-anchors its repeat rule — moving a Monday-pinned weekly task to
    Wednesday no longer snaps back to Monday on the next completion.
  • j/k work in the Conflicts list and the account picker; q closes the account and colour pickers.
  • The bottom hint bar is mode-adaptive, and u (undo) keeps your place in the calendar instead of
    kicking you back to day navigation.

Sync reliability

  • A deleted item no longer wedges forever when it was already gone on the server — the delete used to
    retry every sync and disable the full-download short-circuit for that calendar permanently.
  • Numerous lost-update fixes across concurrent edit-vs-sync paths, conflict resolution, undo of a synced
    delete, and cross-list subtree moves.
  • A corrupt sync sidecar salvages its state instead of discarding it (which used to resurrect deletions).

Performance

Four hot paths de-quadratified — calendar redraw budgeting, LayoutDay overlap packing, time-grid
selection range, and month-grid navigation — so large calendars stay responsive, including on a
Raspberry Pi.


Quality work behind this release

  • 962 documented behaviours verified against the code; 21 divergences found and fixed.
  • 593 key×context cells checked across every mode, view and modal; 24 divergences fixed.
  • Hardening passes 19–24, each fix landing repro-first with a permanent regression test.
  • Full test suite runs green across ten time-zone configurations, plus fuzzing, -race, and
    display-stress sweeps.

Known issues

  1. A timed recurring task due inside a DST "spring forward" gap drifts by an hour, permanently.
    Needs a timed recurring task whose due time falls in the missing hour (02:00–02:59 in most of the
    US/EU/AU, 00:00–00:59 in Havana/Santiago/Azores, 01:00–01:59 in the UK). All-day tasks and all events
    are unaffected, as are non-DST zones. Pre-existing, not new in this release.
  2. Day bucketing is not gap-safe in the 11 midnight-transition zones — day boundaries can be an hour
    off on the transition day, and "tomorrow" in quick-add can resolve to today.
  3. Changing your system time zone requires restarting the app. The zone is resolved once at startup;
    :config reload does not refresh it. Switching accounts refreshes the display zone but not the decode
    zone, so restart after a zone change rather than switching accounts.

Full detail: docs/audit/COVERAGE.md and docs/audit/passes/PASS-24.md.


Install

Download the binary for your platform, verify it, and put it on your PATH:

sha256sum -c sha256sums.txt --ignore-missing
chmod +x lazyplanner_linux_amd64
sudo mv lazyplanner_linux_amd64 /usr/local/bin/lazyplanner
lazyplanner version

Raspberry Pi builds are included (arm64, armv7, armv6).

First run writes a commented starter config to ~/.config/lazyplanner/config.toml — fill in one
[[account]] block with your CalDAV URL, username and a NextCloud app password, then run
lazyplanner again.

Upgrading from v1.4.0: no config or cache migration needed. Existing recurring items keep their
current behaviour until their rule is next edited, at which point they gain the corrected zone anchoring.

Full Changelog: v1.4.0...v1.5.0