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:
@@ -70,6 +70,7 @@ export const ALL_EVENT_TYPES: DomainEventType[] = [
|
|||||||
'chat:session_closed',
|
'chat:session_closed',
|
||||||
'initiative:pending_review',
|
'initiative:pending_review',
|
||||||
'initiative:review_approved',
|
'initiative:review_approved',
|
||||||
|
'initiative:changes_requested',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -102,6 +103,7 @@ export const TASK_EVENT_TYPES: DomainEventType[] = [
|
|||||||
'phase:merged',
|
'phase:merged',
|
||||||
'initiative:pending_review',
|
'initiative:pending_review',
|
||||||
'initiative:review_approved',
|
'initiative:review_approved',
|
||||||
|
'initiative:changes_requested',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -7,7 +7,8 @@
|
|||||||
|
|
||||||
export { useAutoSave } from './useAutoSave.js';
|
export { useAutoSave } from './useAutoSave.js';
|
||||||
export { useDebounce, useDebounceWithImmediate } from './useDebounce.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 { useRefineAgent } from './useRefineAgent.js';
|
||||||
export { useConflictAgent } from './useConflictAgent.js';
|
export { useConflictAgent } from './useConflictAgent.js';
|
||||||
export { useSubscriptionWithErrorHandling } from './useSubscriptionWithErrorHandling.js';
|
export { useSubscriptionWithErrorHandling } from './useSubscriptionWithErrorHandling.js';
|
||||||
|
|||||||
@@ -15,6 +15,17 @@ export interface LiveUpdateRule {
|
|||||||
*
|
*
|
||||||
* Encapsulates error toast + reconnect config so pages don't duplicate boilerplate.
|
* 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[]) {
|
export function useLiveUpdates(rules: LiveUpdateRule[]) {
|
||||||
const utils = trpc.useUtils();
|
const utils = trpc.useUtils();
|
||||||
|
|
||||||
|
|||||||
@@ -49,12 +49,15 @@ const INVALIDATION_MAP: Partial<Record<MutationName, QueryName[]>> = {
|
|||||||
createInitiative: ["listInitiatives"],
|
createInitiative: ["listInitiatives"],
|
||||||
updateInitiative: ["listInitiatives", "getInitiative"],
|
updateInitiative: ["listInitiatives", "getInitiative"],
|
||||||
updateInitiativeProjects: ["getInitiative"],
|
updateInitiativeProjects: ["getInitiative"],
|
||||||
|
approveInitiativeReview: ["listInitiatives", "getInitiative"],
|
||||||
|
requestInitiativeChanges: ["listInitiatives", "getInitiative"],
|
||||||
|
|
||||||
// --- Phases ---
|
// --- Phases ---
|
||||||
createPhase: ["listPhases", "listInitiativePhaseDependencies"],
|
createPhase: ["listPhases", "listInitiativePhaseDependencies"],
|
||||||
deletePhase: ["listPhases", "listInitiativeTasks", "listInitiativePhaseDependencies", "listChangeSets"],
|
deletePhase: ["listPhases", "listInitiativeTasks", "listInitiativePhaseDependencies", "listChangeSets"],
|
||||||
updatePhase: ["listPhases", "getPhase"],
|
updatePhase: ["listPhases", "getPhase"],
|
||||||
approvePhase: ["listPhases", "listInitiativeTasks"],
|
approvePhase: ["listPhases", "listInitiativeTasks", "listInitiatives"],
|
||||||
|
requestPhaseChanges: ["listPhases", "listInitiativeTasks", "listPhaseTasks", "getInitiative"],
|
||||||
queuePhase: ["listPhases"],
|
queuePhase: ["listPhases"],
|
||||||
createPhaseDependency: ["getPhaseDependencies", "listInitiativePhaseDependencies", "listPhaseTaskDependencies"],
|
createPhaseDependency: ["getPhaseDependencies", "listInitiativePhaseDependencies", "listPhaseTaskDependencies"],
|
||||||
removePhaseDependency: ["getPhaseDependencies", "listInitiativePhaseDependencies", "listPhaseTaskDependencies"],
|
removePhaseDependency: ["getPhaseDependencies", "listInitiativePhaseDependencies", "listPhaseTaskDependencies"],
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { ExecutionTab } from "@/components/ExecutionTab";
|
|||||||
import { ReviewTab } from "@/components/review";
|
import { ReviewTab } from "@/components/review";
|
||||||
import { PipelineTab } from "@/components/pipeline";
|
import { PipelineTab } from "@/components/pipeline";
|
||||||
import { useLiveUpdates } from "@/hooks";
|
import { useLiveUpdates } from "@/hooks";
|
||||||
import type { LiveUpdateRule } from "@/hooks/useLiveUpdates";
|
import type { LiveUpdateRule } from "@/hooks";
|
||||||
|
|
||||||
type Tab = "content" | "plan" | "execution" | "review";
|
type Tab = "content" | "plan" | "execution" | "review";
|
||||||
const TABS: 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
|
// Single SSE stream for all live updates — memoized to avoid re-subscribe on render
|
||||||
const liveUpdateRules = useMemo<LiveUpdateRule[]>(() => [
|
const liveUpdateRules = useMemo<LiveUpdateRule[]>(() => [
|
||||||
|
{ prefix: 'initiative:', invalidate: ['getInitiative'] },
|
||||||
{ prefix: 'task:', invalidate: ['listPhases', 'listTasks', 'listInitiativeTasks', 'getPhaseDependencies', 'listPhaseTaskDependencies'] },
|
{ prefix: 'task:', invalidate: ['listPhases', 'listTasks', 'listInitiativeTasks', 'getPhaseDependencies', 'listPhaseTaskDependencies'] },
|
||||||
{ prefix: 'phase:', invalidate: ['listPhases', 'listTasks', 'listInitiativePhaseDependencies', 'getPhaseDependencies'] },
|
{ prefix: 'phase:', invalidate: ['listPhases', 'listTasks', 'listInitiativePhaseDependencies', 'getPhaseDependencies'] },
|
||||||
{ prefix: 'agent:', invalidate: ['listAgents', 'getActiveRefineAgent'] },
|
{ prefix: 'agent:', invalidate: ['listAgents', 'getActiveRefineAgent'] },
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { Plus } from "lucide-react";
|
|||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { InitiativeList } from "@/components/InitiativeList";
|
import { InitiativeList } from "@/components/InitiativeList";
|
||||||
import { CreateInitiativeDialog } from "@/components/CreateInitiativeDialog";
|
import { CreateInitiativeDialog } from "@/components/CreateInitiativeDialog";
|
||||||
import { useLiveUpdates } from "@/hooks";
|
import { useLiveUpdates, INITIATIVE_LIST_RULES } from "@/hooks";
|
||||||
import { trpc } from "@/lib/trpc";
|
import { trpc } from "@/lib/trpc";
|
||||||
|
|
||||||
export const Route = createFileRoute("/initiatives/")({
|
export const Route = createFileRoute("/initiatives/")({
|
||||||
@@ -29,10 +29,7 @@ function DashboardPage() {
|
|||||||
const projectsQuery = trpc.listProjects.useQuery();
|
const projectsQuery = trpc.listProjects.useQuery();
|
||||||
|
|
||||||
// Single SSE stream for live updates
|
// Single SSE stream for live updates
|
||||||
useLiveUpdates([
|
useLiveUpdates(INITIATIVE_LIST_RULES);
|
||||||
{ prefix: 'task:', invalidate: ['listInitiatives'] },
|
|
||||||
{ prefix: 'phase:', invalidate: ['listInitiatives'] },
|
|
||||||
]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<motion.div
|
<motion.div
|
||||||
|
|||||||
Reference in New Issue
Block a user