chore: release v0.10.0
Release / test-backend (push) Successful in 2m17s
Release / release (push) Successful in 1m0s

This commit is contained in:
2026-06-05 21:04:37 +03:00
parent d01e519925
commit 6877c4d272
5 changed files with 22 additions and 27 deletions
+17 -22
View File
@@ -1,52 +1,47 @@
# v0.9.0 (2026-05-28)
# v0.10.0 (2026-06-05)
A feature + observability release. The headline additions are per-receiver Telegram options (silent send and forum-topic routing), an oversized-video fallback that bypasses Telegram's 50 MB `sendVideo` cap by switching to `sendDocument`, partial-failure visibility on the dashboard via a new `dispatch_summary` block on every `EventLog` row, and an admin diagnostic-mode panel for temporary per-module DEBUG logging with auto-revert. End-to-end correlation IDs (`dispatch_id`, `X-Request-Id`) now tie log lines to the database rows they produced. No breaking changes; no migrations required.
Multi-time-point scheduling for Immich. The single comma-separated "times" text box is replaced with an add/remove list of native HH:MM pickers for the **scheduled assets**, **periodic summary**, and **memory** slots — entering several fire times per day is now discoverable, and the read/write path is hardened end to end. This release also pins `aiohttp<3.14` in the backend test environment so the suite stays green against the current `aioresponses`. No breaking changes; no migrations required.
## User-facing changes
### Features
- **Per-receiver Telegram options.** Each Telegram chat receiver can now be configured to send silently (`disable_notification` — no sound or vibration on the recipient) and to route into a specific forum topic (`message_thread_id`) on supergroups with topics enabled. A new cog-icon button on the receiver row opens an inline editor; active options surface as a bell-off icon and a `#thread-id` chip on the receiver header. The plumbing uses a `ContextVar` bound at the public send entry points, so every internal payload builder (`sendMessage`, `sendPhoto`/`Video`/`Document`, `sendMediaGroup`, cache-hit POST) picks them up without a signature change ([6a8f374](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/6a8f374))
- **Send oversized videos as documents.** A new per-target toggle, `send_large_videos_as_documents`, falls back to `sendDocument` when a video would exceed Telegram's 50 MB `sendVideo` limit. Useful for archival use cases (Immich library shares with users on free Telegram accounts) where the video would otherwise be silently dropped or noisily 413'd. Pairs with the existing `send_large_photos_as_documents` toggle. Translated copy lives under `targets.sendLargeVideosAsDocuments` ([6a8f374](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/6a8f374))
- **Diagnostic mode for temporary DEBUG logging.** A new Diagnostics cassette on the Settings page lets an admin flip one module to DEBUG for a bounded window (1 minute to 4 hours) with an automatic revert. Useful for investigating a specific dispatch failure without flooding stderr; the revert reads the current DB-configured `log_levels` at expiry so a setting change made *during* the window is honored. State is in-memory only — a restart wipes overrides, and `setup_logging()` re-applies the DB baseline at boot, so a forgotten override cannot silently survive a deploy ([6a8f374](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/6a8f374))
- **Partial-delivery visibility in the dashboard.** Every event-, watcher-, scheduled-, deferred-, action-, and command-dispatch path now writes a `dispatch_summary` block onto `EventLog.details`: per-target succeeded/failed counts, Telegram media `delivered_count` / `skipped_count` / `failed_count`, and a truncated list of error strings. Partial outcomes (one target out of three failed, two of ten assets dropped) are no longer indistinguishable from a clean success ([6a8f374](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/6a8f374))
- **Inbound request correlation IDs.** A new middleware accepts `X-Request-Id` from the inbound request (so an upstream proxy with its own correlation system can propagate its id) and echoes the value back on the response. Values are sanitised to a bounded charset to prevent CR/LF injection into log lines. The id is bound into log context for every request and copied onto any `EventLog` row written during the same request, so the SPA can surface it for bug reports ([6a8f374](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/6a8f374))
- **Immich multi-time scheduling UI.** A new `TimeListEditor` replaces the comma-separated text box with an add/remove list of native HH:MM pickers for the scheduled-assets, periodic-summary, and memory slots. It dedupes and sorts on save, enforces a per-day cap, collapses on-screen duplicates, and gives each row an aria-label. Keyboard entry is no longer clobbered mid-edit — it syncs from the value prop only on external changes via an `untrack` guard ([8065e6e](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/8065e6e))
- **Per-section save guard.** An enabled feature section (scheduled / periodic / memory) must now have at least one time before it can be saved, enforced in the provider descriptor ([8065e6e](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/8065e6e))
- **Refreshed copy.** New en/ru i18n keys and updated help text for the editor; the dead `invalidTimeList` string was removed ([8065e6e](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/8065e6e))
### Bug Fixes
- **sendMediaGroup byte-budget enforcement.** Telegram's `sendMediaGroup` envelope tops out near 50 MB total (multipart bytes including form overhead). Previously the per-item budget admitted items that, when summed, busted Telegram's request cap and 413'd. A new 45 MB total-bytes budget (`TELEGRAM_MAX_GROUP_TOTAL_BYTES`) splits groups before the overhead pushes us over, with safety margin ([6a8f374](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/6a8f374))
- **Per-item fallback inside `sendMediaGroup`.** A stale `file_id` reference (cache poisoning by Telegram's GC, or a video that's no longer downloadable from the cached URL) used to silently lose the cached asset because the failed chunk had no re-download path. Each cached item now retains its `source_url` + `download_headers` so the per-item fallback can re-fetch and retry as a single send when its `file_id` POST returns transient errors ([6a8f374](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/6a8f374))
- **Server-side `*_times` normalization.** The `tracking_configs` API now normalizes `scheduled_times` / `periodic_times` / `memory_times` on every write (validate, dedup, sort, cap at 24), returns **422** on malformed input, and refuses to enable a slot whose times list is empty ([8065e6e](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/8065e6e))
- **Restored scheduler observability.** The scheduler now warns when an enabled slot has zero or dropped fire times, restoring the visibility that was lost when the old per-call warning was removed ([8065e6e](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/8065e6e))
---
## Development / Internal
### Observability
### CI/Build
- **Shared `dispatch_id` across log lines and `EventLog` rows.** A `disp:<12 hex>` correlation id is bound at the top of every dispatch entry point (`dispatch_provider_event`, `check_tracker`, `dispatch_scheduled_for_tracker`, `_process_row` in deferred dispatch, `run_action`, command handler, HA status logger) via `ContextVar`. Nested dispatcher calls reuse the bound id instead of generating their own, so a single dispatch's log lines and the `EventLog.details.dispatch_id` field share one searchable id ([6a8f374](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/6a8f374))
- **`enrich_details_with_correlation()` helper.** New helper in `notify_bridge_core.log_context` merges the bound `dispatch_id` and `request_id` onto `EventLog.details` dicts at write time without overwriting caller-provided keys. Every `EventLog` insertion site has been migrated to use it ([6a8f374](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/6a8f374))
- **Pin `aiohttp<3.14` in the backend test install.** `aioresponses` 0.7.8 (latest) does not pass the `stream_writer` keyword argument that aiohttp 3.14 made required on `ClientResponse`, so every aioresponses-mocked HTTP test failed to construct a response. `notify-bridge-core` declares `aiohttp>=3.9` with no upper bound, so CI floated to 3.14.0 — the pin keeps the test environment reproducible in both `build.yml` and `release.yml` until aioresponses ships an aiohttp-3.14-compatible release ([11593ea](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/11593ea))
### Architecture
### Refactoring
- **`split_media_by_upload_size` retired.** Per-item upload accounting moved onto the new `_MediaItem` dataclass (`upload_bytes` property) and the splitter logic moved into `_send_media_group`, where the byte budget and per-item fallback live ([6a8f374](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/6a8f374))
- **API endpoints for diagnostic mode.** New routes under `/api/app-settings/diagnostic-mode` (`GET` list, `POST` activate, `DELETE /{module:path}` revert) with admin-auth requirement and a curated module allowlist that blocks the root logger ([6a8f374](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/6a8f374))
- **Shared time-list service.** A new `services/time_list.py` exposes `normalize_time_list` (raising `TimeListError`) and a lenient `parse_hhmm_list`; the scheduler drops its private parser copy in favour of the shared one ([8065e6e](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/8065e6e))
### Tests
- **New suites:** `test_diagnostic_mode.py`, `test_dispatch_summary.py`, `test_request_correlation.py`, `test_telegram_media_group_partial.py`, `test_telegram_per_send_options.py`. Total: 294 tests passing (up from 283 in v0.8.2) ([6a8f374](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/6a8f374))
- **Test scaffolding fix:** `_reset_state()` in `test_diagnostic_mode.py` now also clears the `_bg_tasks` set so a long-window schedule from one test doesn't pollute `len(_bg_tasks)` in the next test's assertion. Cross-loop `.cancel()` is intentionally skipped — the prior test's loop is closed and cancelling there raises `RuntimeError` on the next test's setup ([9aada75](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/9aada75))
- **Time-list coverage.** New tests for time-list normalization and parsing (including non-ASCII and odd-shaped input) and for the "enabled implies at least one time" validation ([8065e6e](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/8065e6e))
### Documentation
### Chores
- **Six-axis production-readiness review** (`/.claude/reviews/`) covering backend, frontend, security, performance/DB, UI/UX, and bugs+features. Snapshots the v0.8.1 codebase; informed several items shipped in v0.8.2 and this release ([6a8f374](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/6a8f374))
- **Functional review of Telegram / Immich / Logging subsystems** (`.claude/docs/functional-review-2026-05-28.md`). Captures what's in place, in-flight work, and ranked gaps for each subsystem; pairs with the existing feature backlog ([6a8f374](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/6a8f374))
- **Checked-in vex config.** A `.vex.toml` enabling semantic embeddings, call-graph, BM25, and auto-update for the vex code-search index ([d01e519](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/d01e519))
---
<details>
<summary>All Commits</summary>
- [6a8f374](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/6a8f374) — `feat: observability, per-receiver Telegram options, oversized-video fallback` (alexei.dolgolyov)
- [9aada75](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/9aada75) — `fix(tests): clear diagnostic_mode _bg_tasks between cases` (alexei.dolgolyov)
- [8065e6e](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/8065e6e) — `feat(immich): multi-time-point scheduling for scheduled/periodic/memory` (alexei.dolgolyov)
- [11593ea](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/11593ea) — `fix(ci): pin aiohttp<3.14 in backend test deps` (alexei.dolgolyov)
- [d01e519](https://git.dolgolyov-family.by/alexei.dolgolyov/notify-bridge/commit/d01e519) — `chore: add vex semantic-search config` (alexei.dolgolyov)
</details>
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "notify-bridge-frontend",
"version": "0.9.0",
"version": "0.10.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "notify-bridge-frontend",
"version": "0.9.0",
"version": "0.10.0",
"dependencies": {
"@codemirror/autocomplete": "^6.18.0",
"@codemirror/lang-html": "^6.4.11",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "notify-bridge-frontend",
"private": true,
"version": "0.9.0",
"version": "0.10.0",
"type": "module",
"scripts": {
"dev": "vite dev",
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "notify-bridge-core"
version = "0.9.0"
version = "0.10.0"
description = "Core library for Notify Bridge — service provider abstractions, models, notifications, and templates"
requires-python = ">=3.12"
dependencies = [
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "notify-bridge-server"
version = "0.9.0"
version = "0.10.0"
description = "Standalone Notify Bridge server — FastAPI REST API with SQLite database"
requires-python = ">=3.12"
dependencies = [