@@ -111,6 +111,10 @@ class ItemDetailStore {
111111 loading = $state ( true ) ;
112112 error = $state ( null ) ;
113113 saving = $state ( false ) ;
114+ // Latest requested save per field while another save is in flight; replayed
115+ // on completion so rapid edits are not dropped.
116+ #pendingSaves = new Map ( ) ;
117+ #drainingPendingSaves = false ;
114118 // The detail view closes when an SSE deletion or refresh 404 sets this.
115119 notFound = $state ( false ) ;
116120
@@ -852,7 +856,16 @@ class ItemDetailStore {
852856 }
853857
854858 async saveField ( field , directValue = null , assigneeName = null , iterationName = null ) {
855- if ( this . saving ) return ;
859+ if ( this . saving ) {
860+ // A save is in flight; remember the latest requested value per field
861+ // and replay it when that save finishes so rapid edits are not lost.
862+ this . #pendingSaves. set ( field , {
863+ directValue : this . #resolveSaveValue( field , directValue ) ,
864+ assigneeName,
865+ iterationName,
866+ } ) ;
867+ return ;
868+ }
856869
857870 try {
858871 this . saving = true ;
@@ -1041,10 +1054,43 @@ class ItemDetailStore {
10411054 throw err ;
10421055 } finally {
10431056 this . saving = false ;
1057+ await this . #drainPendingSaves( ) ;
10441058 this . #runPendingRefresh( ) ;
10451059 }
10461060 }
10471061
1062+ // Resolve an editing-state value into a direct value so a queued save keeps
1063+ // writing the same content after the editor closes.
1064+ #resolveSaveValue( field , directValue ) {
1065+ if ( directValue !== null || ! field . startsWith ( 'custom_field_' ) ) {
1066+ return directValue ;
1067+ }
1068+ const fieldId = field . replace ( 'custom_field_' , '' ) ;
1069+ const value = this . editing . customFields . values [ fieldId ] ;
1070+ return value !== undefined ? value : null ;
1071+ }
1072+
1073+ // Replay queued saves sequentially; the nested saveField call re-enters
1074+ // this drain in its own finally, so guard against double processing.
1075+ async #drainPendingSaves( ) {
1076+ if ( this . #drainingPendingSaves) return ;
1077+ this . #drainingPendingSaves = true ;
1078+ try {
1079+ while ( this . #pendingSaves. size > 0 ) {
1080+ const [ field , pending ] = this . #pendingSaves. entries ( ) . next ( ) . value ;
1081+ this . #pendingSaves. delete ( field ) ;
1082+ await this . saveField (
1083+ field ,
1084+ pending . directValue ,
1085+ pending . assigneeName ,
1086+ pending . iterationName
1087+ ) ;
1088+ }
1089+ } finally {
1090+ this . #drainingPendingSaves = false ;
1091+ }
1092+ }
1093+
10481094 // === Auto-sync editing values when item loads/changes ===
10491095
10501096 #syncEditingFromItem( ) {
@@ -1255,6 +1301,8 @@ class ItemDetailStore {
12551301 this . #loadToken += 1 ;
12561302 this . #refreshToken += 1 ;
12571303 this . #refreshPending = false ;
1304+ this . #pendingSaves. clear ( ) ;
1305+ this . #drainingPendingSaves = false ;
12581306 this . #loadController?. abort ( ) ;
12591307 this . #refreshController?. abort ( ) ;
12601308 this . #linksController?. abort ( ) ;
0 commit comments