feat: forward decky.*.service.* on per-topology SSE stream

The /topologies/{id}/events SSE proxy now subscribes to two bus
patterns concurrently and merges them through a bounded asyncio.Queue:

* topology.{id}.>  — lifecycle (status, mutation.*) — unchanged.
* decky.>          — per-decky events, filtered by payload.topology_id
                     so a fleet decky sharing a name with a topology
                     decky doesn't leak across.

_sse_name_for routes 'decky.<name>.service.added' to the SSE event
name 'decky.service.added' (kept the prefix so the frontend doesn't
collide with topology lifecycle events that share leaf names like
'status').

useTopologyStream surfaces the two new event names; MazeNET.tsx's
onStreamEvent optimistically patches the matching node's services
list so a second tab reflects shape changes without a refetch.
This commit is contained in:
2026-04-28 23:15:38 -04:00
parent e7d49d7237
commit 0e5484648f
3 changed files with 89 additions and 28 deletions

View File

@@ -16,7 +16,13 @@ export type TopologyStreamEventName =
| 'mutation.applying'
| 'mutation.applied'
| 'mutation.failed'
| 'status';
| 'status'
// Live per-decky service mutations forwarded by the SSE proxy on the
// server. The payload carries decky_name + service_name + the
// post-mutation services list, so a second tab can reconcile shape
// without a refetch.
| 'decky.service.added'
| 'decky.service.removed';
export interface TopologyStreamEvent {
name: TopologyStreamEventName | string;
@@ -40,6 +46,8 @@ const NAMED_EVENTS: TopologyStreamEventName[] = [
'mutation.applied',
'mutation.failed',
'status',
'decky.service.added',
'decky.service.removed',
];
export function useTopologyStream({