fix: Add missing event routing for initiative status real-time refresh

7 of 12 initiative activity state transitions were broken due to missing
event routing at three layers: SSE event arrays, live-update prefix rules,
and mutation invalidation map.

- Add initiative:changes_requested to ALL_EVENT_TYPES and TASK_EVENT_TYPES
- Add initiative:/agent: prefix rules to initiatives list and detail pages
- Add approveInitiativeReview, requestInitiativeChanges, requestPhaseChanges
  to INVALIDATION_MAP; add listInitiatives to approvePhase
- Extract INITIATIVE_LIST_RULES constant for reuse
This commit is contained in:
Lukas May
2026-03-06 13:25:31 +01:00
parent 52e238924c
commit ebef093d3f
6 changed files with 23 additions and 8 deletions

View File

@@ -70,6 +70,7 @@ export const ALL_EVENT_TYPES: DomainEventType[] = [
'chat:session_closed',
'initiative:pending_review',
'initiative:review_approved',
'initiative:changes_requested',
];
/**
@@ -102,6 +103,7 @@ export const TASK_EVENT_TYPES: DomainEventType[] = [
'phase:merged',
'initiative:pending_review',
'initiative:review_approved',
'initiative:changes_requested',
];
/**

View File

@@ -7,7 +7,8 @@
export { useAutoSave } from './useAutoSave.js';
export { useDebounce, useDebounceWithImmediate } from './useDebounce.js';
export { useLiveUpdates } from './useLiveUpdates.js';
export { useLiveUpdates, INITIATIVE_LIST_RULES } from './useLiveUpdates.js';
export type { LiveUpdateRule } from './useLiveUpdates.js';
export { useRefineAgent } from './useRefineAgent.js';
export { useConflictAgent } from './useConflictAgent.js';
export { useSubscriptionWithErrorHandling } from './useSubscriptionWithErrorHandling.js';

View File

@@ -15,6 +15,17 @@ export interface LiveUpdateRule {
*
* Encapsulates error toast + reconnect config so pages don't duplicate boilerplate.
*/
/**
* Reusable rules for any page displaying initiative cards.
* Covers all event prefixes that can change derived initiative activity state.
*/
export const INITIATIVE_LIST_RULES: LiveUpdateRule[] = [
{ prefix: 'initiative:', invalidate: ['listInitiatives'] },
{ prefix: 'task:', invalidate: ['listInitiatives'] },
{ prefix: 'phase:', invalidate: ['listInitiatives'] },
{ prefix: 'agent:', invalidate: ['listInitiatives'] },
];
export function useLiveUpdates(rules: LiveUpdateRule[]) {
const utils = trpc.useUtils();

View File

@@ -49,12 +49,15 @@ const INVALIDATION_MAP: Partial<Record<MutationName, QueryName[]>> = {
createInitiative: ["listInitiatives"],
updateInitiative: ["listInitiatives", "getInitiative"],
updateInitiativeProjects: ["getInitiative"],
approveInitiativeReview: ["listInitiatives", "getInitiative"],
requestInitiativeChanges: ["listInitiatives", "getInitiative"],
// --- Phases ---
createPhase: ["listPhases", "listInitiativePhaseDependencies"],
deletePhase: ["listPhases", "listInitiativeTasks", "listInitiativePhaseDependencies", "listChangeSets"],
updatePhase: ["listPhases", "getPhase"],
approvePhase: ["listPhases", "listInitiativeTasks"],
approvePhase: ["listPhases", "listInitiativeTasks", "listInitiatives"],
requestPhaseChanges: ["listPhases", "listInitiativeTasks", "listPhaseTasks", "getInitiative"],
queuePhase: ["listPhases"],
createPhaseDependency: ["getPhaseDependencies", "listInitiativePhaseDependencies", "listPhaseTaskDependencies"],
removePhaseDependency: ["getPhaseDependencies", "listInitiativePhaseDependencies", "listPhaseTaskDependencies"],

View File

@@ -12,7 +12,7 @@ import { ExecutionTab } from "@/components/ExecutionTab";
import { ReviewTab } from "@/components/review";
import { PipelineTab } from "@/components/pipeline";
import { useLiveUpdates } from "@/hooks";
import type { LiveUpdateRule } from "@/hooks/useLiveUpdates";
import type { LiveUpdateRule } from "@/hooks";
type Tab = "content" | "plan" | "execution" | "review";
const TABS: Tab[] = ["content", "plan", "execution", "review"];
@@ -31,6 +31,7 @@ function InitiativeDetailPage() {
// Single SSE stream for all live updates — memoized to avoid re-subscribe on render
const liveUpdateRules = useMemo<LiveUpdateRule[]>(() => [
{ prefix: 'initiative:', invalidate: ['getInitiative'] },
{ prefix: 'task:', invalidate: ['listPhases', 'listTasks', 'listInitiativeTasks', 'getPhaseDependencies', 'listPhaseTaskDependencies'] },
{ prefix: 'phase:', invalidate: ['listPhases', 'listTasks', 'listInitiativePhaseDependencies', 'getPhaseDependencies'] },
{ prefix: 'agent:', invalidate: ['listAgents', 'getActiveRefineAgent'] },

View File

@@ -5,7 +5,7 @@ import { Plus } from "lucide-react";
import { Button } from "@/components/ui/button";
import { InitiativeList } from "@/components/InitiativeList";
import { CreateInitiativeDialog } from "@/components/CreateInitiativeDialog";
import { useLiveUpdates } from "@/hooks";
import { useLiveUpdates, INITIATIVE_LIST_RULES } from "@/hooks";
import { trpc } from "@/lib/trpc";
export const Route = createFileRoute("/initiatives/")({
@@ -29,10 +29,7 @@ function DashboardPage() {
const projectsQuery = trpc.listProjects.useQuery();
// Single SSE stream for live updates
useLiveUpdates([
{ prefix: 'task:', invalidate: ['listInitiatives'] },
{ prefix: 'phase:', invalidate: ['listInitiatives'] },
]);
useLiveUpdates(INITIATIVE_LIST_RULES);
return (
<motion.div