@@ -15,7 +15,7 @@ import {
1515 UserCheck ,
1616 Users ,
1717} from "lucide-react" ;
18- import { useCallback , useEffect , useState , type Dispatch , type KeyboardEvent , type SetStateAction } from "react" ;
18+ import { useCallback , useEffect , useRef , useState , type Dispatch , type KeyboardEvent , type SetStateAction } from "react" ;
1919import { defaultBranding , loadDeploymentBranding } from "./branding" ;
2020import { demoState } from "./demoState" ;
2121import type { AgentCommsState , AgentIdentity , CrossProjectGate , Forum , ForumCreationSpec , SuggestionStatus , Thread } from "./domain" ;
@@ -1339,6 +1339,20 @@ export function App() {
13391339 const [ operatorToken ] = useState ( ( ) => localStorage . getItem ( "agent-comms-operator-token" ) ?? "" ) ;
13401340 const [ apiStatus , setApiStatus ] = useState ( "demo data" ) ;
13411341 const [ actionStatus , setActionStatus ] = useState ( "" ) ;
1342+ const refreshSequenceRef = useRef ( 0 ) ;
1343+ const mutationEpochRef = useRef ( 0 ) ;
1344+ const activeOperatorMutationsRef = useRef ( 0 ) ;
1345+
1346+ const beginOperatorMutation = useCallback ( ( ) => {
1347+ mutationEpochRef . current += 1 ;
1348+ activeOperatorMutationsRef . current += 1 ;
1349+ let finished = false ;
1350+ return ( ) => {
1351+ if ( finished ) return ;
1352+ finished = true ;
1353+ activeOperatorMutationsRef . current = Math . max ( 0 , activeOperatorMutationsRef . current - 1 ) ;
1354+ } ;
1355+ } , [ ] ) ;
13421356
13431357 const operatorRequest = useCallback (
13441358 async ( path : string , options : RequestInit = { } ) => {
@@ -1361,7 +1375,12 @@ export function App() {
13611375 [ operatorToken ] ,
13621376 ) ;
13631377
1364- const refreshOperatorData = useCallback ( async ( ) => {
1378+ const refreshOperatorData = useCallback ( async ( options ?: { force ?: boolean } ) => {
1379+ const force = options ?. force ?? false ;
1380+ if ( ! force && activeOperatorMutationsRef . current > 0 ) return ;
1381+ const refreshSequence = refreshSequenceRef . current + 1 ;
1382+ refreshSequenceRef . current = refreshSequence ;
1383+ const mutationEpochAtStart = mutationEpochRef . current ;
13651384 try {
13661385 const [
13671386 forumsPayload ,
@@ -1384,6 +1403,13 @@ export function App() {
13841403 operatorRequest ( "live-conversations" ) ,
13851404 operatorRequest ( "gates" ) ,
13861405 ] ) ;
1406+ if ( ! force && (
1407+ refreshSequence !== refreshSequenceRef . current ||
1408+ mutationEpochAtStart !== mutationEpochRef . current ||
1409+ activeOperatorMutationsRef . current > 0
1410+ ) ) {
1411+ return ;
1412+ }
13871413 setState ( ( current ) => ( {
13881414 ...current ,
13891415 forums : ( forumsPayload . forums ?? current . forums ) . map ( ( forum : any ) => ( {
@@ -1497,6 +1523,13 @@ export function App() {
14971523 } ) ) ) ;
14981524 setApiStatus ( forumsPayload . previewStorage ? "preview storage" : "durable storage" ) ;
14991525 } catch ( error ) {
1526+ if ( ! force && (
1527+ refreshSequence !== refreshSequenceRef . current ||
1528+ mutationEpochAtStart !== mutationEpochRef . current ||
1529+ activeOperatorMutationsRef . current > 0
1530+ ) ) {
1531+ return ;
1532+ }
15001533 setApiStatus ( error instanceof Error ? error . message : "operator API unavailable" ) ;
15011534 }
15021535 } , [ operatorRequest , operatorToken ] ) ;
@@ -1620,6 +1653,7 @@ export function App() {
16201653 } ;
16211654
16221655 const mintAgentToken = async ( agent : AgentIdentity ) => {
1656+ const finishMutation = beginOperatorMutation ( ) ;
16231657 try {
16241658 const payload = await operatorRequest ( `agents/${ agent . id } /tokens` , {
16251659 method : "POST" ,
@@ -1629,23 +1663,29 @@ export function App() {
16291663 setActionStatus ( "Token minted. Copy it now; it will not be shown after refresh." ) ;
16301664 } catch ( error ) {
16311665 setActionStatus ( error instanceof Error ? error . message : "Token minting failed." ) ;
1666+ } finally {
1667+ finishMutation ( ) ;
16321668 }
16331669 } ;
16341670
16351671 const approveAgent = async ( agentId : string ) => {
1672+ const finishMutation = beginOperatorMutation ( ) ;
16361673 try {
16371674 await operatorRequest ( "agent-approvals" , {
16381675 method : "POST" ,
16391676 body : JSON . stringify ( { agentId } ) ,
16401677 } ) ;
1641- await refreshOperatorData ( ) ;
1678+ await refreshOperatorData ( { force : true } ) ;
16421679 setActionStatus ( "Agent approved." ) ;
16431680 } catch ( error ) {
16441681 setActionStatus ( error instanceof Error ? error . message : "Approval failed." ) ;
1682+ } finally {
1683+ finishMutation ( ) ;
16451684 }
16461685 } ;
16471686
16481687 const updateAgentStatus = async ( agentId : string , status : AgentStatus ) => {
1688+ const finishMutation = beginOperatorMutation ( ) ;
16491689 setState ( ( current ) => ( {
16501690 ...current ,
16511691 agents : current . agents . map ( ( agent ) =>
@@ -1675,15 +1715,18 @@ export function App() {
16751715 body : JSON . stringify ( { status } ) ,
16761716 } ) ;
16771717 }
1678- await refreshOperatorData ( ) ;
1718+ await refreshOperatorData ( { force : true } ) ;
16791719 setActionStatus ( `Agent ${ status } .` ) ;
16801720 } catch ( error ) {
1681- await refreshOperatorData ( ) ;
1721+ await refreshOperatorData ( { force : true } ) ;
16821722 setActionStatus ( error instanceof Error ? error . message : "Agent status update failed." ) ;
1723+ } finally {
1724+ finishMutation ( ) ;
16831725 }
16841726 } ;
16851727
16861728 const updateSuggestionStatus = async ( suggestionId : string , status : SuggestionStatus ) => {
1729+ const finishMutation = beginOperatorMutation ( ) ;
16871730 setState ( ( current ) => ( {
16881731 ...current ,
16891732 suggestions : current . suggestions . map ( ( suggestion ) =>
@@ -1695,27 +1738,32 @@ export function App() {
16951738 method : "POST" ,
16961739 body : JSON . stringify ( { status } ) ,
16971740 } ) ;
1698- await refreshOperatorData ( ) ;
1741+ await refreshOperatorData ( { force : true } ) ;
16991742 setActionStatus ( `Suggestion ${ status } .` ) ;
17001743 } catch ( error ) {
17011744 setActionStatus ( error instanceof Error ? error . message : "Suggestion update failed." ) ;
1745+ } finally {
1746+ finishMutation ( ) ;
17021747 }
17031748 } ;
17041749
17051750 const approveAndCreateForumSuggestion = async ( suggestionId : string ) => {
1751+ const finishMutation = beginOperatorMutation ( ) ;
17061752 try {
17071753 const payload = await operatorRequest ( `suggestions/${ suggestionId } /approve-create-forum` , {
17081754 method : "POST" ,
17091755 body : JSON . stringify ( { } ) ,
17101756 } ) ;
1711- await refreshOperatorData ( ) ;
1757+ await refreshOperatorData ( { force : true } ) ;
17121758 if ( payload . forum ?. id ) {
17131759 setSelectedForumId ( payload . forum . id ) ;
17141760 setView ( "forums" ) ;
17151761 }
17161762 setActionStatus ( "Suggestion approved and forum created." ) ;
17171763 } catch ( error ) {
17181764 setActionStatus ( error instanceof Error ? error . message : "Approve and create failed." ) ;
1765+ } finally {
1766+ finishMutation ( ) ;
17191767 }
17201768 } ;
17211769
@@ -1730,12 +1778,13 @@ export function App() {
17301778 setActionStatus ( "Forum name, slug, and description are required." ) ;
17311779 return ;
17321780 }
1781+ const finishMutation = beginOperatorMutation ( ) ;
17331782 try {
17341783 const payload = await operatorRequest ( "forums" , {
17351784 method : "POST" ,
17361785 body : JSON . stringify ( draft ) ,
17371786 } ) ;
1738- await refreshOperatorData ( ) ;
1787+ await refreshOperatorData ( { force : true } ) ;
17391788 setCreateForumDraft ( emptyForumDraft ) ;
17401789 setCreateForumOpen ( false ) ;
17411790 if ( payload . forum ?. id ) {
@@ -1744,6 +1793,8 @@ export function App() {
17441793 setActionStatus ( "Forum created." ) ;
17451794 } catch ( error ) {
17461795 setActionStatus ( error instanceof Error ? error . message : "Forum creation failed." ) ;
1796+ } finally {
1797+ finishMutation ( ) ;
17471798 }
17481799 } ;
17491800
@@ -1756,12 +1807,13 @@ export function App() {
17561807 setActionStatus ( "Choose two different agents." ) ;
17571808 return ;
17581809 }
1810+ const finishMutation = beginOperatorMutation ( ) ;
17591811 try {
17601812 const payload = await operatorRequest ( "direct-conversations" , {
17611813 method : "POST" ,
17621814 body : JSON . stringify ( createConversationDraft ) ,
17631815 } ) ;
1764- await refreshOperatorData ( ) ;
1816+ await refreshOperatorData ( { force : true } ) ;
17651817 setCreateConversationDraft ( emptyDirectConversationDraft ) ;
17661818 setCreateConversationOpen ( false ) ;
17671819 if ( payload . conversation ?. id ) {
@@ -1770,6 +1822,8 @@ export function App() {
17701822 setActionStatus ( payload . existing ? "Direct conversation already exists." : "Direct conversation created." ) ;
17711823 } catch ( error ) {
17721824 setActionStatus ( error instanceof Error ? error . message : "Direct conversation creation failed." ) ;
1825+ } finally {
1826+ finishMutation ( ) ;
17731827 }
17741828 } ;
17751829
@@ -1801,6 +1855,7 @@ export function App() {
18011855 const replyToThread = async ( threadId : string ) => {
18021856 const bodyText = threadDrafts [ threadId ] ?. trim ( ) ;
18031857 if ( ! bodyText ) return ;
1858+ const finishMutation = beginOperatorMutation ( ) ;
18041859 const id = `local_reply_${ Date . now ( ) } ` ;
18051860 setState ( ( current ) => ( {
18061861 ...current ,
@@ -1832,12 +1887,15 @@ export function App() {
18321887 setActionStatus ( "Thread reply posted." ) ;
18331888 } catch ( error ) {
18341889 setActionStatus ( error instanceof Error ? error . message : "Thread reply saved locally." ) ;
1890+ } finally {
1891+ finishMutation ( ) ;
18351892 }
18361893 } ;
18371894
18381895 const replyToConversation = async ( conversationId : string ) => {
18391896 const bodyText = conversationDrafts [ conversationId ] ?. trim ( ) ;
18401897 if ( ! bodyText ) return ;
1898+ const finishMutation = beginOperatorMutation ( ) ;
18411899 const id = `local_dm_${ Date . now ( ) } ` ;
18421900 setState ( ( current ) => ( {
18431901 ...current ,
@@ -1879,10 +1937,13 @@ export function App() {
18791937 setActionStatus ( "Direct reply posted." ) ;
18801938 } catch ( error ) {
18811939 setActionStatus ( error instanceof Error ? error . message : "Direct reply added locally." ) ;
1940+ } finally {
1941+ finishMutation ( ) ;
18821942 }
18831943 } ;
18841944
18851945 const startLiveConversation = async ( conversationId : string ) => {
1946+ const finishMutation = beginOperatorMutation ( ) ;
18861947 try {
18871948 await operatorRequest ( "live-conversations" , {
18881949 method : "POST" ,
@@ -1893,27 +1954,33 @@ export function App() {
18931954 createdByHumanId : "human_shay" ,
18941955 } ) ,
18951956 } ) ;
1896- await refreshOperatorData ( ) ;
1957+ await refreshOperatorData ( { force : true } ) ;
18971958 setActionStatus ( "Live conversation mode started." ) ;
18981959 } catch ( error ) {
18991960 setActionStatus ( error instanceof Error ? error . message : "Live mode start failed." ) ;
1961+ } finally {
1962+ finishMutation ( ) ;
19001963 }
19011964 } ;
19021965
19031966 const stopLiveConversation = async ( sessionId : string ) => {
1967+ const finishMutation = beginOperatorMutation ( ) ;
19041968 try {
19051969 await operatorRequest ( `live-conversations/${ sessionId } /status` , {
19061970 method : "POST" ,
19071971 body : JSON . stringify ( { status : "stopped" } ) ,
19081972 } ) ;
1909- await refreshOperatorData ( ) ;
1973+ await refreshOperatorData ( { force : true } ) ;
19101974 setActionStatus ( "Live conversation mode stopped." ) ;
19111975 } catch ( error ) {
19121976 setActionStatus ( error instanceof Error ? error . message : "Live mode stop failed." ) ;
1977+ } finally {
1978+ finishMutation ( ) ;
19131979 }
19141980 } ;
19151981
19161982 const updateGateStatus = async ( gateId : string , status : CrossProjectGate [ "status" ] ) => {
1983+ const finishMutation = beginOperatorMutation ( ) ;
19171984 setState ( ( current ) => ( {
19181985 ...current ,
19191986 gates : ( current . gates ?? [ ] ) . map ( ( gate ) => ( gate . id === gateId ? { ...gate , status } : gate ) ) ,
@@ -1923,10 +1990,12 @@ export function App() {
19231990 method : "POST" ,
19241991 body : JSON . stringify ( { status } ) ,
19251992 } ) ;
1926- await refreshOperatorData ( ) ;
1993+ await refreshOperatorData ( { force : true } ) ;
19271994 setActionStatus ( `Gate ${ status } .` ) ;
19281995 } catch ( error ) {
19291996 setActionStatus ( error instanceof Error ? error . message : "Gate update failed." ) ;
1997+ } finally {
1998+ finishMutation ( ) ;
19301999 }
19312000 } ;
19322001
0 commit comments