feat(20-02): wire SSE subscription hooks into dashboard, detail, and inbox pages

Add useSubscription hooks to all three UI pages that invalidate React
Query caches on domain events:
- Dashboard: onTaskUpdate invalidates listInitiatives + listPhases
- Detail: onTaskUpdate invalidates phases/tasks/plans, onAgentUpdate
  invalidates listAgents
- Inbox: onAgentUpdate invalidates listWaitingAgents + listMessages

Subscription failures are silent (onError: () => {}) so pages degrade
gracefully to manual refresh when the backend is not running.
This commit is contained in:
Lukas May
2026-02-04 22:21:44 +01:00
parent eaf3f10722
commit 170ac55afd
3 changed files with 38 additions and 2 deletions

View File

@@ -13,6 +13,16 @@ export const Route = createFileRoute("/inbox")({
function InboxPage() {
const [selectedAgentId, setSelectedAgentId] = useState<string | null>(null);
// Live updates: invalidate inbox queries on agent events
const utils = trpc.useUtils();
trpc.onAgentUpdate.useSubscription(undefined, {
onData: () => {
void utils.listWaitingAgents.invalidate();
void utils.listMessages.invalidate();
},
onError: () => {},
});
// Data fetching
const agentsQuery = trpc.listWaitingAgents.useQuery();
const messagesQuery = trpc.listMessages.useQuery({});
@@ -22,8 +32,6 @@ function InboxPage() {
);
// Mutations
const utils = trpc.useUtils();
const resumeAgentMutation = trpc.resumeAgent.useMutation({
onSuccess: () => {
void utils.listWaitingAgents.invalidate();

View File

@@ -209,6 +209,23 @@ function InitiativeDetailPage() {
const { id } = Route.useParams();
const navigate = useNavigate();
// Live updates: invalidate detail queries on task/phase and agent events
const utils = trpc.useUtils();
trpc.onTaskUpdate.useSubscription(undefined, {
onData: () => {
void utils.listPhases.invalidate();
void utils.listTasks.invalidate();
void utils.listPlans.invalidate();
},
onError: () => {},
});
trpc.onAgentUpdate.useSubscription(undefined, {
onData: () => {
void utils.listAgents.invalidate();
},
onError: () => {},
});
// State
const [selectedTaskId, setSelectedTaskId] = useState<string | null>(null);
const [taskCountsByPhase, setTaskCountsByPhase] = useState<

View File

@@ -2,6 +2,7 @@ import { useState } from "react";
import { createFileRoute, useNavigate } from "@tanstack/react-router";
import { Plus } from "lucide-react";
import { Button } from "@/components/ui/button";
import { trpc } from "@/lib/trpc";
import { InitiativeList } from "@/components/InitiativeList";
import { CreateInitiativeDialog } from "@/components/CreateInitiativeDialog";
@@ -23,6 +24,16 @@ function DashboardPage() {
const [statusFilter, setStatusFilter] = useState<StatusFilter>("all");
const [createDialogOpen, setCreateDialogOpen] = useState(false);
// Live updates: invalidate dashboard queries on task/phase events
const utils = trpc.useUtils();
trpc.onTaskUpdate.useSubscription(undefined, {
onData: () => {
void utils.listInitiatives.invalidate();
void utils.listPhases.invalidate();
},
onError: () => {},
});
return (
<div className="space-y-6">
{/* Page header */}