@@ -48,6 +48,8 @@ export default function CodexPoolStrategySetting({
4848 const hydratedRef = useRef ( false ) ;
4949 const [ saving , setSaving ] = useState ( false ) ;
5050 const savingRef = useRef ( false ) ;
51+ /** Set when an /active read arrives mid-save; triggers one post-save refresh. */
52+ const deferredActiveRefreshRef = useRef ( false ) ;
5153 const revisionRef = useRef ( 0 ) ;
5254 const [ loadError , setLoadError ] = useState ( false ) ;
5355 const [ error , setError ] = useState < string | null > ( null ) ;
@@ -80,25 +82,47 @@ export default function CodexPoolStrategySetting({
8082 try {
8183 const res = await fetch ( `${ apiBase } /api/codex-auth/active` ) ;
8284 if ( ! res . ok ) throw new Error ( "load" ) ;
83- applyServer ( await res . json ( ) as {
85+ const payload = await res . json ( ) as {
8486 accountPoolStrategy ?: unknown ;
8587 accountPoolStickyLimit ?: unknown ;
86- } ) ;
88+ } ;
89+ // A save started while this GET was in flight — retry once after it settles.
90+ if ( savingRef . current ) {
91+ deferredActiveRefreshRef . current = true ;
92+ return ;
93+ }
94+ applyServer ( payload ) ;
8795 } catch {
88- setLoadError ( true ) ;
96+ if ( ! savingRef . current ) setLoadError ( true ) ;
8997 }
9098 } , [ apiBase , applyServer ] ) ;
9199
100+ const scheduleDeferredActiveRefresh = useCallback ( ( ) => {
101+ if ( ! deferredActiveRefreshRef . current ) return ;
102+ deferredActiveRefreshRef . current = false ;
103+ queueMicrotask ( ( ) => {
104+ if ( savingRef . current ) {
105+ deferredActiveRefreshRef . current = true ;
106+ return ;
107+ }
108+ void load ( ) ;
109+ } ) ;
110+ } , [ load ] ) ;
111+
92112 // Shared /active observer (preferred): same payload the pool already fetched.
93- // Ignore stale polls that started before a PUT bumped revision, and never apply while saving.
113+ // Ignore stale polls that started before a PUT bumped revision; mid-save reads
114+ // arm one post-save /active refresh instead of being dropped forever.
94115 // Replay readLastActive on subscribe so late mounts hydrate without waiting a poll.
95116 useEffect ( ( ) => {
96117 if ( ! subscribeLoadObserver ) return ;
97118 const observer : CodexAccountLoadObserver = {
98119 beginActiveRead : ( ) => revisionRef . current ,
99120 acceptActiveRead : ( value , startedRevision ) => {
100121 if ( startedRevision !== revisionRef . current ) return ;
101- if ( savingRef . current ) return ;
122+ if ( savingRef . current ) {
123+ deferredActiveRefreshRef . current = true ;
124+ return ;
125+ }
102126 applyActivePayload ( value ) ;
103127 } ,
104128 rejectActiveRead : ( ) => {
@@ -154,7 +178,8 @@ export default function CodexPoolStrategySetting({
154178 }
155179 savingRef . current = false ;
156180 setSaving ( false ) ;
157- } , [ apiBase , stickyLimit , strategy , t ] ) ;
181+ scheduleDeferredActiveRefresh ( ) ;
182+ } , [ apiBase , scheduleDeferredActiveRefresh , stickyLimit , strategy , t ] ) ;
158183
159184 // Block writes until /active confirms — defaults paint for CLS but must not overwrite server state.
160185 const controlsDisabled = saving || loadError || ! hydrated ;
0 commit comments