fix(agenda): show multi-day events on every day they span #83

Merged
makiolaj merged 7 commits from fix/agenda-multiday into release/v2.16.0 2026-07-19 10:16:57 +00:00
Owner

Multi-day events in the agenda

Fixes multi-day events showing up on only their first day in the agenda
(screen and home-screen widget). They now list under every day
they span, with a day-aware time line so each day reads on its own:

  • Timed span: Starts 14:00 on the first day, All day on the days
    in between, Ends 10:00 on the last day.
  • All-day span: All day on each day it covers.
  • Single-day events are unchanged (14:00 – 15:00 / All day).

Notable pieces

  • groupAgendaDays now expands each instance across the days it occupies
    (clamped to the visible window), shared by the screen and the agenda
    widget so both group identically.
  • Day-scoped LazyColumn keys — a spanning event's id is no longer
    unique across days (would crash the list otherwise).
  • All-day span days are resolved in UTC (matching the Week view /
    detail card), so a one-day all-day event can't leak onto the next day
    east of UTC.
  • The day-aware label lives in one pure agendaTimeLabel(event, day, zone) in the shared agenda layer; the screen and widget both resolve
    strings from it, so they stay consistent.

Strings

Adds agenda_span_starts / agenda_span_ends (English only — owes a
Weblate backfill for the other locales).

Tests

JVM tests for the grouping (incl. an eastern-zone all-day regression)
and for agendaTimeLabel's first/middle/last-day resolution.

On-device verified on a Pixel (screen + widget). Branched off, and
merged up to, the current release/v2.16.0 (per-event time zones).

🤖 Generated with Claude Code

## Multi-day events in the agenda Fixes multi-day events showing up on only their first day in the agenda (screen **and** home-screen widget). They now list under **every** day they span, with a day-aware time line so each day reads on its own: - **Timed span:** `Starts 14:00` on the first day, `All day` on the days in between, `Ends 10:00` on the last day. - **All-day span:** `All day` on each day it covers. - Single-day events are unchanged (`14:00 – 15:00` / `All day`). ### Notable pieces - `groupAgendaDays` now expands each instance across the days it occupies (clamped to the visible window), shared by the screen and the agenda widget so both group identically. - Day-scoped `LazyColumn` keys — a spanning event's id is no longer unique across days (would crash the list otherwise). - All-day span days are resolved in **UTC** (matching the Week view / detail card), so a one-day all-day event can't leak onto the next day east of UTC. - The day-aware label lives in one pure `agendaTimeLabel(event, day, zone)` in the shared agenda layer; the screen and widget both resolve strings from it, so they stay consistent. ### Strings Adds `agenda_span_starts` / `agenda_span_ends` (English only — owes a Weblate backfill for the other locales). ### Tests JVM tests for the grouping (incl. an eastern-zone all-day regression) and for `agendaTimeLabel`'s first/middle/last-day resolution. On-device verified on a Pixel (screen + widget). Branched off, and merged up to, the current `release/v2.16.0` (per-event time zones). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
makiolaj added 7 commits 2026-07-19 10:15:29 +00:00
groupAgendaDays keyed each instance by its start day alone, so a
multi-day event surfaced only on its first day and vanished from the
rest of its span in both the Agenda screen and the agenda widget.

Expand each instance across every day from its start (clamped to the
anchor for ongoing events) through its last occupied day, bounded by the
visible window end. An event ending exactly at midnight — including the
exclusive next-midnight all-day events end at — does not reach that
boundary day.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A multi-day event now appears under every day it spans, so keying its
row by instanceId alone repeated the key across days and crashed the
LazyColumn ("Key already used") on scroll. Scope the key by day.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A multi-day event repeated the same "start – end" on every day it spans
("14:00 – 14:00"), which reads as meaningless. Show only the part
relevant to each day, with a "→" marking that it carries past the day's
boundary: the first day shows the start ("14:00 →"), the last day the
end ("→ 10:00"), and whole days in between an all-day span arriving from
and continuing into their neighbours ("→ All day →"). Single-day rows are
unchanged.

Factors the span first/last-day resolution into shared EventInstance
helpers reused by groupAgendaDays and the label.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The "→" arrow convention read as unclear. Spell each day out instead:
the first day names the start ("Starts 14:00"), the last day the end
("Ends 10:00"), and whole days in between read as "All day". All-day
multi-day events stay "All day" on every day. Single-day rows unchanged.

Adds agenda_span_starts / agenda_span_ends (owes Weblate backfill).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Review follow-up. spanFirstDay/spanLastDay resolved every event in the
device zone, but all-day events live at UTC midnights with an exclusive
end — east of UTC (e.g. Europe/Berlin) that pushed spanLastDay onto the
next day, so a single-day all-day event reported spansMultipleDays and
leaked onto a second agenda day. Resolve all-day dates in UTC, matching
the Week view and detail card. Adds eastern-zone regression tests that
the prior UTC-only tests could not catch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fix(agenda): share the day-aware time label with the widget
All checks were successful
Translations / check (pull_request) Successful in 6s
CI / ci (pull_request) Successful in 5m45s
7de9b2f81b
Code-review follow-up.

- The multi-day expansion in groupAgendaDays is shared with the agenda
  widget, but only the screen's summary was made day-aware — so the
  widget rendered the raw "start – end" on every spanned day, the very
  bug the screen fix cured. Hoist a pure agendaTimeLabel(event, day,
  zone) into the shared agenda layer and resolve strings from it in both
  the screen and the widget, so they label identically. (findings 1, 2)

- groupAgendaDays could silently drop an instance whose clamped span was
  empty (firstDay > lastDay); floor lastDay at firstDay so a returned
  instance always surfaces on at least its first visible day. (finding 3)

- agendaTimeLabel resolves the span days once instead of the summary
  recomputing them 2–3× per row. (finding 5)

Finding 4 (within-day sort) needs no change: sorting by absolute start
already places a still-running multi-day event at the top of each day it
continues into, which is chronologically correct (it is ongoing from
that day's midnight), and at its real start time on its first day.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
makiolaj merged commit fd3bcdf2f7 into release/v2.16.0 2026-07-19 10:16:57 +00:00
makiolaj deleted branch fix/agenda-multiday 2026-07-19 10:16:57 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: makiolaj/calendula#83