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:
@@ -13,6 +13,16 @@ export const Route = createFileRoute("/inbox")({
|
|||||||
function InboxPage() {
|
function InboxPage() {
|
||||||
const [selectedAgentId, setSelectedAgentId] = useState<string | null>(null);
|
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
|
// Data fetching
|
||||||
const agentsQuery = trpc.listWaitingAgents.useQuery();
|
const agentsQuery = trpc.listWaitingAgents.useQuery();
|
||||||
const messagesQuery = trpc.listMessages.useQuery({});
|
const messagesQuery = trpc.listMessages.useQuery({});
|
||||||
@@ -22,8 +32,6 @@ function InboxPage() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Mutations
|
// Mutations
|
||||||
const utils = trpc.useUtils();
|
|
||||||
|
|
||||||
const resumeAgentMutation = trpc.resumeAgent.useMutation({
|
const resumeAgentMutation = trpc.resumeAgent.useMutation({
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
void utils.listWaitingAgents.invalidate();
|
void utils.listWaitingAgents.invalidate();
|
||||||
|
|||||||
@@ -209,6 +209,23 @@ function InitiativeDetailPage() {
|
|||||||
const { id } = Route.useParams();
|
const { id } = Route.useParams();
|
||||||
const navigate = useNavigate();
|
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
|
// State
|
||||||
const [selectedTaskId, setSelectedTaskId] = useState<string | null>(null);
|
const [selectedTaskId, setSelectedTaskId] = useState<string | null>(null);
|
||||||
const [taskCountsByPhase, setTaskCountsByPhase] = useState<
|
const [taskCountsByPhase, setTaskCountsByPhase] = useState<
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { useState } from "react";
|
|||||||
import { createFileRoute, useNavigate } from "@tanstack/react-router";
|
import { createFileRoute, useNavigate } from "@tanstack/react-router";
|
||||||
import { Plus } from "lucide-react";
|
import { Plus } from "lucide-react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { trpc } from "@/lib/trpc";
|
||||||
import { InitiativeList } from "@/components/InitiativeList";
|
import { InitiativeList } from "@/components/InitiativeList";
|
||||||
import { CreateInitiativeDialog } from "@/components/CreateInitiativeDialog";
|
import { CreateInitiativeDialog } from "@/components/CreateInitiativeDialog";
|
||||||
|
|
||||||
@@ -23,6 +24,16 @@ function DashboardPage() {
|
|||||||
const [statusFilter, setStatusFilter] = useState<StatusFilter>("all");
|
const [statusFilter, setStatusFilter] = useState<StatusFilter>("all");
|
||||||
const [createDialogOpen, setCreateDialogOpen] = useState(false);
|
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 (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* Page header */}
|
{/* Page header */}
|
||||||
|
|||||||
Reference in New Issue
Block a user