@@ -1252,6 +1252,9 @@ interface CancelRunsParams {
12521252 rowId ?: string
12531253 /** Scope-`all` only: cancel just the cells on rows matching this filter (filtered select-all Stop). */
12541254 filter ?: Filter
1255+ /** Active sort — with `filter` it identifies the exact rows query whose cells the optimistic
1256+ * cancel may flip (other cached views contain rows the server won't touch). */
1257+ sort ?: Sort | null
12551258 /** Scope-`all` only: deselected rows whose cells keep running. */
12561259 excludeRowIds ?: string [ ]
12571260}
@@ -1274,39 +1277,57 @@ export function useCancelTableRuns({ workspaceId, tableId }: RowMutationContext)
12741277 body : { workspaceId, scope, rowId, filter, excludeRowIds } ,
12751278 } )
12761279 } ,
1277- onMutate : async ( { scope, rowId, excludeRowIds } ) => {
1280+ onMutate : async ( { scope, rowId, filter , sort , excludeRowIds } ) => {
12781281 const excludedRowIds =
12791282 excludeRowIds && excludeRowIds . length > 0 ? new Set ( excludeRowIds ) : null
1280- const snapshots = await snapshotAndMutateRows ( queryClient , tableId , ( r ) => {
1281- if ( scope === 'row' && r . id !== rowId ) return null
1282- if ( excludedRowIds ?. has ( r . id ) ) return null
1283- const executions = ( r . executions ?? { } ) as RowExecutions
1284- let rowTouched = false
1285- const nextExecutions : RowExecutions = { ...executions }
1286- for ( const gid in executions ) {
1287- const exec = executions [ gid ]
1288- if ( ! isExecInFlight ( exec ) ) continue
1289- if ( exec . executionId == null ) {
1290- // Optimistic-only or dispatcher-pre-stamp pending — server has not
1291- // claimed the cell yet, so no SSE will arrive to reconcile a
1292- // `cancelled` stamp. Strip the entry instead and let the renderer
1293- // fall through to the cell's prior state (value / empty / etc.).
1294- delete nextExecutions [ gid ]
1283+ // A filtered stop only cancels matching rows server-side — flipping every cached view
1284+ // would show rows outside the filter as cancelled until refetch. Scope the optimistic
1285+ // flip to the active filtered view; onSettled's invalidation reconciles the rest.
1286+ const onlyKey = filter
1287+ ? tableKeys . infiniteRows (
1288+ tableId ,
1289+ tableRowsParamsKey ( {
1290+ pageSize : TABLE_LIMITS . MAX_QUERY_LIMIT ,
1291+ filter,
1292+ sort : sort ?? null ,
1293+ } )
1294+ )
1295+ : undefined
1296+ const snapshots = await snapshotAndMutateRows (
1297+ queryClient ,
1298+ tableId ,
1299+ ( r ) => {
1300+ if ( scope === 'row' && r . id !== rowId ) return null
1301+ if ( excludedRowIds ?. has ( r . id ) ) return null
1302+ const executions = ( r . executions ?? { } ) as RowExecutions
1303+ let rowTouched = false
1304+ const nextExecutions : RowExecutions = { ...executions }
1305+ for ( const gid in executions ) {
1306+ const exec = executions [ gid ]
1307+ if ( ! isExecInFlight ( exec ) ) continue
1308+ if ( exec . executionId == null ) {
1309+ // Optimistic-only or dispatcher-pre-stamp pending — server has not
1310+ // claimed the cell yet, so no SSE will arrive to reconcile a
1311+ // `cancelled` stamp. Strip the entry instead and let the renderer
1312+ // fall through to the cell's prior state (value / empty / etc.).
1313+ delete nextExecutions [ gid ]
1314+ rowTouched = true
1315+ continue
1316+ }
1317+ nextExecutions [ gid ] = {
1318+ status : 'cancelled' ,
1319+ executionId : exec . executionId ,
1320+ jobId : null ,
1321+ workflowId : exec . workflowId ,
1322+ error : 'Cancelled' ,
1323+ ...( exec . blockErrors ? { blockErrors : exec . blockErrors } : { } ) ,
1324+ }
12951325 rowTouched = true
1296- continue
1297- }
1298- nextExecutions [ gid ] = {
1299- status : 'cancelled' ,
1300- executionId : exec . executionId ,
1301- jobId : null ,
1302- workflowId : exec . workflowId ,
1303- error : 'Cancelled' ,
1304- ...( exec . blockErrors ? { blockErrors : exec . blockErrors } : { } ) ,
13051326 }
1306- rowTouched = true
1307- }
1308- return rowTouched ? { ... r , executions : nextExecutions } : null
1309- } )
1327+ return rowTouched ? { ... r , executions : nextExecutions } : null
1328+ } ,
1329+ { onlyKey }
1330+ )
13101331 return { snapshots }
13111332 } ,
13121333 onError : ( _err , _variables , context ) => {
@@ -1822,14 +1843,20 @@ export async function snapshotAndMutateRows(
18221843 queryClient : ReturnType < typeof useQueryClient > ,
18231844 tableId : string ,
18241845 transform : ( row : TableRow ) => TableRow | null ,
1825- options ?: { cancelInFlight ?: boolean }
1846+ options ?: {
1847+ cancelInFlight ?: boolean
1848+ /** Restrict the walk to one exact cached query (e.g. the active filtered
1849+ * view) when the mutation's server effect doesn't cover other views. */
1850+ onlyKey ?: readonly unknown [ ]
1851+ }
18261852) : Promise < RowsCacheSnapshots > {
1853+ const scope = options ?. onlyKey
1854+ ? ( { queryKey : options . onlyKey , exact : true } as const )
1855+ : ( { queryKey : tableKeys . rowsRoot ( tableId ) } as const )
18271856 if ( options ?. cancelInFlight !== false ) {
1828- await queryClient . cancelQueries ( { queryKey : tableKeys . rowsRoot ( tableId ) } )
1857+ await queryClient . cancelQueries ( scope )
18291858 }
1830- const matching = queryClient . getQueriesData < RowsCacheEntry > ( {
1831- queryKey : tableKeys . rowsRoot ( tableId ) ,
1832- } )
1859+ const matching = queryClient . getQueriesData < RowsCacheEntry > ( scope )
18331860 const snapshots : RowsCacheSnapshots = [ ]
18341861 for ( const [ key , data ] of matching ) {
18351862 if ( ! data ) continue
0 commit comments