- JavaScript 61.1%
- Python 32.1%
- CSS 4.8%
- HTML 1.7%
- Shell 0.3%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
Groups the calendar sidebar into collapsible groups and can merge a group into one local read-only busy calendar. Writes only to calendars it created itself (requireOwned), never to source calendars. Includes unit tests, headless Thunderbird end-to-end and restart tests, and a Forgejo Actions workflow (tests, xpi build, release on tags). |
||
| .forgejo/workflows | ||
| experiment | ||
| icons | ||
| options | ||
| screenshots | ||
| tests | ||
| .gitignore | ||
| background.js | ||
| build.sh | ||
| LICENSE | ||
| manifest.json | ||
| merge.js | ||
| README.md | ||
| sidebar.css | ||
Calendar Groups (Thunderbird add-on)
Sorts the calendar list in the sidebar into collapsible groups. Optionally a group merges its calendars into one local calendar " busy": one hatched bar per busy period instead of lots of grey "(Public event)" tiles.
Tested with Thunderbird 157 and 158 (release channel).
Install
The ready-made .xpi is attached to the releases.
To build it yourself:
./build.sh # → dist/calendar-groups-<version>.xpi
Thunderbird → Add-ons and Themes → gear icon → "Install Add-on From File…" → pick
the .xpi. The add-on is not signed; Thunderbird does not require that. It needs
experiment permissions (full access) because it has to work on the calendar list.
Use
- Create groups: right-click a calendar → "Calendar Groups settings…". Create and order groups there and assign calendars.
- Move a calendar: right-click → "Move to group".
- Collapse: click the group header.
- Eye on the group: shows or hides all calendars of the group.
- Merge icon on the group (appears on hover) or "Display: Merged" in the settings: the source calendars are hidden, the calendar " busy" is created and filled. Switching back to "Separate" deletes this calendar and shows the sources again.
Without groups the list looks as before. New calendars end up in "Ungrouped".
What the add-on changes (and what not)
The privileged interface (experiment/implementation.js) only offers:
| Function | Effect |
|---|---|
getCalendars, getBusy |
read only |
setVisible, hideSource, restoreSource |
local display setting calendar-main-in-composite (like the eye in Thunderbird); nothing goes to the server |
syncOwnCalendar, removeOwnCalendar |
write only to local calendars (storage) that the add-on created and marked itself (kalendergruppen-owned); requireOwned() refuses everything else |
There are no functions that create, change or delete other calendars' events. That
is on purpose: for some calendar types Thunderbird's removeCalendar() deletes the
data on the server, so it is only called through requireOwned().
The busy events have no attendees, no organizer and no reminders, so no invitations
are sent. The busy calendar is read-only. Their description names neither the title
nor the calendar of the source events, so a block copied into a shared calendar
gives nothing away. When syncing, the add-on only deletes blocks it created itself
(X-KALENDERGRUPPEN), and none from the past before the computed range.
Default calendar for new events: Thunderbird creates new events in the selected calendar, in day/week view by dragging even without a dialog. The add-on never leaves the selection on a hidden calendar or a "… busy" calendar: it switches to the default calendar set in the settings, otherwise to the writable one you clicked last, otherwise to the first local one. "source" and "busy" rows cannot be selected. If you select a read-only calendar, the selection therefore jumps back.
Busy blocks are marked private (CLASS:PRIVATE). If one is copied into a shared
calendar, servers that respect this only show "private".
Sources are only hidden once the "… busy" calendar exists. If a source cannot be read while merging, everything stays visible as before. Disabled calendars do not take part. The status of each group is shown in the settings.
Your own entries in a "… busy" calendar (e.g. after switching off read-only) are never deleted: when switching back or uninstalling, the calendar is kept as "… (own entries)".
Unreadable source (error state, disabled): the group's blocks stay unchanged instead of showing the time as free.
Context menu: delete/unsubscribe in the menu act on the selected calendar. The add-on only opens the menu when Thunderbird verifiably points at the clicked calendar.
Disabling/uninstalling resets everything: the sources are shown again, the "… busy" calendars removed. A normal Thunderbird shutdown keeps the state.
If a new Thunderbird version no longer fits (internal modules missing), the add-on pauses, says so once and restores the hidden calendars via the settings (visible after a restart); its "… busy" calendars are disabled.
When Thunderbird changes its UI
At startup the add-on checks whether the calendar list is still built as expected. If not, it does not rebuild anything, shows a notice (once per Thunderbird version) and leaves the default list in place. The "… busy" calendars keep working because they are normal calendars. The status is also shown in the settings.
Limits
- No drag & drop in the grouped list. The order within a group follows Thunderbird's sorting.
- Keyboard use of the list is limited (mouse and context menu work).
- The merged range is 31 days back to 366 days ahead. It is recomputed when a source changes and every 6 hours.
- "until HH:MM" in the title uses the system time zone.
- If you create an event in a hidden source, Thunderbird shows it again. The add-on does not hide it again (eye on the row).
- If you toggle "offline support" on a hidden source, Thunderbird recreates it with a new ID; it then stays hidden until you show it yourself.
- An add-on update rebuilds the "… busy" calendars.
- At Thunderbird startup the first recomputation waits 20 seconds until network calendars are loaded; until then the old blocks stay. After an add-on update the sources are visible until the blocks are recomputed.
- During a sync the "… busy" calendar is briefly writable.
- Blocks older than a good year are removed.
- The fallback path for incompatible Thunderbird versions is not tested automatically (it cannot be produced in a test).
Tests
node --test tests/ # merge logic
python3 tests/e2e.py /tmp/cg-test # full flow
./build.sh && python3 tests/restart_test.py /tmp/cg-test # .xpi, restart, disable
The end-to-end tests start their own headless Thunderbird with a throwaway profile
(-no-remote, Marionette on port 28291), never your real profile. They create local
calendars and a writable ICS calendar served by a local HTTP server, and check at
the end:
- no
addItem/modifyItem/deleteItemcalls on source calendars (spy on the provider classes, with a cross-check that it sees changes), - only display settings changed on source calendars,
- no
PUT/DELETEto the network calendar.
CI
.forgejo/workflows/ci.yml runs all tests on every push (the end-to-end tests in an
Arch container with the current Thunderbird) and builds the .xpi. A tag vX.Y.Z
matching the version in manifest.json creates a release with the .xpi attached.
License
MIT, see LICENSE.


