feat: wire MQTT → SSE bridge so the event timeline updates live (closes #1) #6
Reference in New Issue
Block a user
Delete Branch "fix/issue-1-sse-bridge"
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?
Closes #1. Stacked on top of #5 (test-isolation fixture).
Problem
The Flask event-timeline was dead.
broadcast_sse_eventexisted invigilar/web/blueprints/events.pybut had zero call sites. Clients subscribed to/events/stream, received the initial `{"type": "connected"}` message, and then only keepalives — a page refresh was required to see new events. (Web Push via VAPID was independent and already worked.)Root cause: a process-boundary gap. The events subsystem runs in its own OS process and emits classified events to MQTT. The Flask app runs in a separate process with no MQTT client of its own, so the classified events never reached any SSE queue.
Approach
Thin bridge, classification stays in one place:
EventProcessor._handle_eventnow publishes a classified summary (id,ts,type,severity,source_id,payload) to a new topicTopics.EVENTS_PUBLISHED = "vigilar/events/published"immediately afterinsert_event().vigilar/web/sse_bridge.pyprovides:forward_event(topic, payload)— the MQTT handler that callsbroadcast_sse_event(payload).start_sse_bridge(cfg)— creates aMessageBus, subscribesforward_eventtoEVENTS_PUBLISHED, connects, returns the bus.vigilar/main.py:_run_webstarts the bridge aftercreate_app(cfg)and disconnects it on shutdown. Bridge failure is logged but does not kill the web process — the UI still works without live updates.Design choices
create_appis deliberately NOT changed. Keeping the bridge out of the app factory means no existing test triggers a real MQTT connection, and the bridge stays a production-only concern wired by the supervisor process.app.js:74-99readstype,source_id,ts,severityoff the SSEdata) so no client-side changes are needed.start_sse_bridgeraises, the exception is logged and the web server still boots.Tests (TDD, RED verified before GREEN)
tests/unit/test_events.py::TestEventsPublishedBroadcasttest_handle_event_publishes_classified_payload— motion event goes toEVENTS_PUBLISHEDwith the right classified fields.test_unclassified_topic_does_not_publish— heartbeats are NOT forwarded (regression guard for theevent_type is Noneearly return).tests/unit/test_sse_bridge.pytest_forward_event_delivers_payload_to_sse_subscribers— forwarding reaches the real SSE queue layer viabroadcast_sse_event.test_start_sse_bridge_subscribes_to_events_published— verified against a fakeMessageBus, no real MQTT.Full suite: 364 passed (was 360; +2 processor tests, +2 bridge tests).
Docs
Updates
docs/operator-guide.md(drops the "event timeline is not live" known limitation) anddocs/architecture/subsystems/web.md(describes the new bridge).Stacking
This PR is stacked on
fix/issue-3-test-isolation. Merge #5 first; this one will auto-rebase onto main.