Replaces any Discord-specific reminder channel with a generic outbound
webhook channel. Users pick any saved Integration as the target and
supply a JSON payload template with {{title}} and {{message}}
placeholders — values are JSON-escaped before substitution. Works with
Discord, Slack, Teams, ntfy (JSON mode), or any service that accepts a
POST with a JSON body.
- `src/settings.py` — reminder_webhook_integration_id +
reminder_webhook_payload_template defaults
- `routes/note_routes.py` — webhook delivery block; Integration lookup,
template rendering, auth wiring; built-in preset defaults so
discord_webhook works out of the box without a configured template;
settings_override kwarg avoids test-button race condition
- `routes/auth_routes.py` — discord_webhook preset test handler
- `src/integrations.py` — discord_webhook preset with description +
example templates; hides auth/key fields in the Integration form
- `src/builtin_actions.py` — webhook_sent delivery check
- `src/tool_implementations.py` — webhook aliases + enum updated
- `static/index.html` — Webhook channel option; Integration picker +
payload template textarea
- `static/js/settings.js` — Integration list, populateWebhookIntegrations,
syncChannelRows, hints, load/save, auto-fill preset templates,
test-button override payload, hide auth/key for URL-auth presets
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(calendar): support multiple CalDAV accounts
Replaces the single CalDAV credential slot with a named account list so
users can sync both a personal and work calendar simultaneously.
- Add `account_id` column to `CalendarCal` + startup migration
- `_load_caldav_accounts()` in caldav_sync.py reads `caldav_accounts`
list from prefs, auto-migrating the legacy single `caldav` key on
first use (no user action required)
- `sync_caldav()` iterates all accounts and aggregates counts/errors
- `writeback_event()` resolves credentials via `CalendarCal.account_id`,
falling back to the first account for legacy rows
- New REST endpoints: GET/POST/PUT/DELETE `/api/calendar/config/accounts`
- Legacy GET/POST `/api/calendar/config` preserved for backward compat
- Settings UI: one card per account with Label, URL, Username, Password
fields; Test button works for both unsaved (inline creds) and saved
(by account_id) accounts; delete removes only that account
- Update test_caldav_url_hardening.py mock to include `_save_for_user`
and updated `_sync_blocking` signature
* fix(calendar): restore #2765 PK scoping and #2819 writeback URL validation
Two regressions introduced by the multi-account refactor:
1. PK collision (#2765): _stable_cal_id was back to hashing only the URL,
so two users — or one user with two accounts on the same server — would
collide on the primary key. Restore owner+account_id in the hash key
(format: "{owner}\n{account_id}\n{url}") and thread both values through
_sync_blocking → _writeback_blocking → push_event → find_remote_calendar
so the hash round-trips correctly on write-back.
2. URL validation dropped (#2819): _load_caldav_accounts imported
_save_for_user at function scope, causing an ImportError on test mocks
that only provide _load_for_user, which prevented writeback_event from
reaching the validate_caldav_url call. Move the import inside the
migration branch and wrap in try/except (best-effort save; next call
re-migrates from the still-present legacy key).
Update fake_writeback_blocking in test_caldav_writeback.py to accept the
new owner/account_id optional params.