feat(dashboard): VEVENTs (meetings) alongside VTODOs #4
Reference in New Issue
Block a user
No description provided.
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Carried over from the mgmt.msbls.de teardown plan §3 Gap 1.
Gap
The
/dashboardTasks card surfaces VTODOs only. The mgmt cockpit (now slated for teardown) also surfaces VEVENTs from the sameWork+Plancalendars, ordered by start-time, with location + categories. "Meetings today" is a real feature projax doesn't yet replicate.Once this lands, the last functional reason to keep mgmt.msbls.de alive disappears and the §4 teardown sequence can run end-to-end.
Scope
Small. The CalDAV client already lists VEVENTs (
caldav.ListVEVENTs); the dashboard handler needs either:Today's meetings), orToday's schedulecard that combines VTODOs + VEVENTs ordered by due/start-time.Sort: chronological — overdue VTODOs first, then today's events by start, then no-due VTODOs at the end.
Reuse the existing 4-worker pool the Tasks card uses (don't hammer the DAV server). Same 60s TTL cache as the rest of the dashboard.
Out of scope
cmd/projaxCalDAV writeback path is VTODO-only).References
docs/plans/mgmt-teardown.mdonmai/knuth/phase-3k-mgmt-survey.m/msbls.de#5(closed).caldav/caldav.go—ListVEVENTsalready exists.Already shipped — but functionally inert. The real blocker is data, not code.
tl;dr: the code this issue asks for landed in d49ad21 on 2026-05-16, four days before this issue was filed (2026-05-20). But m still sees "No upcoming events" — because the calendars with his meetings (
Work,Plan) aren't linked to any projax item. One/admin/caldavaction away from done.Why the issue premise was stale
Carried over from
mgmt-teardown.md§3 Gap 1, whose gap list was already out of date when the issue was cut. Two details in the body are wrong:caldav.ListVEVENTsdoesn't exist — the real function isListEvents(caldav/caldav.go:258), a server-side time-range-filtered REPORT.What's on main today:
dashboardEvent+projectEvents(web/dashboard.go:887),aggregate.Events(internal/aggregate/aggregator.go:253), rendered viadashboard_section.tmpl, grouped by day over a next-7-day window (a superset of "meetings today").Every constraint in this issue is already satisfied: 4-worker pool (
aggregate.defaultWorkers, shared with the Todos path — no new fan-out), 60s TTL, no RRULE expansion (flaggedRecurring=true, never expanded), read-only (no VEVENT writeback).The actual gap — verified end-to-end
I ran the real binary locally against m's live DAV server (auth off, migrations off) and probed the calendars directly:
PlanWorkdefaultmhomeThe only
caldav-listlink in the DB points atmhome, which has zero events. So the Events card correctly renders "No events in the next 7 days" — it's working exactly as designed, on an empty input.The pipeline is provably healthy:
mhomehas 15 VTODOs / 8 completed = 7 open, and the Tasks card renders exactly 7 rows. Reads work; nothing is linked.What closes this
The teardown plan already prescribes the answer:
A
workroot project already exists. So: linkhttps://dav.msbls.de/dav/calendars/m/Work/and.../Plan/to it via/admin/caldav. That surfaces 11 meetings immediately.Side effect m should know about before clicking: linking also pulls those calendars' VTODOs into the Tasks card —
Workadds 7 open tasks,Planadds 0 (all 15 completed). Modest, and reversible via the unlink button. Flagging it rather than deciding m's data model for him — that's why I didn't link it myself.This commit
50d0ac5 —
test(dashboard): pin VEVENT sort + day-grouping order (#4)No card was built; that would have duplicated deployed code. The genuine gap was coverage:
projectEventshad zero unit tests — sort/grouping was only exercised indirectly by the render test. Now pinned: start-ascending with summary tiebreak, day-grouping preserving that order,ganztägigfor all-day, and RRULE staying one row at the literal DTSTART (so a future expansion attempt fails loudly instead of silently multiplying rows). Verified the assertions fail against an inverted sort — they have teeth.Shape decision: the issue offered a fourth card or a merged "Today's schedule". The shipped code is the fourth-card route, and that's also what the cost criterion picks: the Tasks card carries done/edit/delete VTODO writeback while VEVENTs are read-only, so merging would park that asymmetry inside one card. Keeping the split.
Teardown status
Per this issue's framing, Gap 1 was the last functional blocker for the mgmt.msbls.de teardown. The code side is done and deployed (healthz
bcf3412== HEAD). The §4 teardown sequence is unblocked onceWork+Planare linked — that link is what makes "meetings today" actually appear, and it's the thing to confirm before trusting the parity claim.Unrelated pre-existing failures (not from this work)
go build ./...+go vet ./...clean. Beyond the knownTestProjectFilter*/TestTimeline*route-drift, I hit two more and verified they fail on clean HEAD with my file removed — pre-existing DB drift, logged by kahn on 2026-06-01, worth filing separately:db:TestBackfillTagsFromArea— "12 live children still empty after 0012"store:TestParityListAll(store=73 vs mbrian=66) +TestParitySpotChecksNot closing — per protocol only m closes. Labelling
needs-review: the code ask is done, but m should decide theWork+Planlink before this counts as delivered.Small disclosure on the label above —
m/projaxhad zero labels defined (no issue in this repo has ever carried one), so there was noneeds-reviewto apply. Rather than skip the status, I created the standard mAi set perdocs/gitea-workflow.mdinm/otto:status:in-progress/status:done/status:needs-review/status:failed/status:partial. #4 now carriesstatus:needs-review.I used the
status:-prefixed form because that's whatm/ottoandm/mAiuse and what/clean-sessionexpects. Worth flagging that the convention has drifted across repos, though —m/msbls.deuses the bare form (done,needs-review),m/mAicarries both, andgitea-workflow.mditself is inconsistent (its table saysneeds-review, its lifecycle section saysstatus:needs-review). Not something I'm going to unilaterally standardise across your repos — flagging it for a separate decision.Revert is trivial if you'd rather projax stayed label-free (
DELETE /repos/m/projax/labels/{376..380}).