@@ -1224,6 +1224,9 @@ interface CancelRunsParams {
12241224 rowId ?: string
12251225 /** Scope-`all` only: cancel just the cells on rows matching this filter (filtered select-all Stop). */
12261226 filter ?: Filter
1227+ /** Active sort — with `filter` it identifies the exact rows query whose cells the optimistic
1228+ * cancel may flip (other cached views contain rows the server won't touch). */
1229+ sort ?: Sort | null
12271230 /** Scope-`all` only: deselected rows whose cells keep running. */
12281231 excludeRowIds ?: string [ ]
12291232}
@@ -1246,39 +1249,57 @@ export function useCancelTableRuns({ workspaceId, tableId }: RowMutationContext)
12461249 body : { workspaceId, scope, rowId, filter, excludeRowIds } ,
12471250 } )
12481251 } ,
1249- onMutate : async ( { scope, rowId, excludeRowIds } ) => {
1252+ onMutate : async ( { scope, rowId, filter , sort , excludeRowIds } ) => {
12501253 const excludedRowIds =
12511254 excludeRowIds && excludeRowIds . length > 0 ? new Set ( excludeRowIds ) : null
1252- const snapshots = await snapshotAndMutateRows ( queryClient , tableId , ( r ) => {
1253- if ( scope === 'row' && r . id !== rowId ) return null
1254- if ( excludedRowIds ?. has ( r . id ) ) return null
1255- const executions = ( r . executions ?? { } ) as RowExecutions
1256- let rowTouched = false
1257- const nextExecutions : RowExecutions = { ...executions }
1258- for ( const gid in executions ) {
1259- const exec = executions [ gid ]
1260- if ( ! isExecInFlight ( exec ) ) continue
1261- if ( exec . executionId == null ) {
1262- // Optimistic-only or dispatcher-pre-stamp pending — server has not
1263- // claimed the cell yet, so no SSE will arrive to reconcile a
1264- // `cancelled` stamp. Strip the entry instead and let the renderer
1265- // fall through to the cell's prior state (value / empty / etc.).
1266- delete nextExecutions [ gid ]
1255+ // A filtered stop only cancels matching rows server-side — flipping every cached view
1256+ // would show rows outside the filter as cancelled until refetch. Scope the optimistic
1257+ // flip to the active filtered view; onSettled's invalidation reconciles the rest.
1258+ const onlyKey = filter
1259+ ? tableKeys . infiniteRows (
1260+ tableId ,
1261+ tableRowsParamsKey ( {
1262+ pageSize : TABLE_LIMITS . MAX_QUERY_LIMIT ,
1263+ filter,
1264+ sort : sort ?? null ,
1265+ } )
1266+ )
1267+ : undefined
1268+ const snapshots = await snapshotAndMutateRows (
1269+ queryClient ,
1270+ tableId ,
1271+ ( r ) => {
1272+ if ( scope === 'row' && r . id !== rowId ) return null
1273+ if ( excludedRowIds ?. has ( r . id ) ) return null
1274+ const executions = ( r . executions ?? { } ) as RowExecutions
1275+ let rowTouched = false
1276+ const nextExecutions : RowExecutions = { ...executions }
1277+ for ( const gid in executions ) {
1278+ const exec = executions [ gid ]
1279+ if ( ! isExecInFlight ( exec ) ) continue
1280+ if ( exec . executionId == null ) {
1281+ // Optimistic-only or dispatcher-pre-stamp pending — server has not
1282+ // claimed the cell yet, so no SSE will arrive to reconcile a
1283+ // `cancelled` stamp. Strip the entry instead and let the renderer
1284+ // fall through to the cell's prior state (value / empty / etc.).
1285+ delete nextExecutions [ gid ]
1286+ rowTouched = true
1287+ continue
1288+ }
1289+ nextExecutions [ gid ] = {
1290+ status : 'cancelled' ,
1291+ executionId : exec . executionId ,
1292+ jobId : null ,
1293+ workflowId : exec . workflowId ,
1294+ error : 'Cancelled' ,
1295+ ...( exec . blockErrors ? { blockErrors : exec . blockErrors } : { } ) ,
1296+ }
12671297 rowTouched = true
1268- continue
1269- }
1270- nextExecutions [ gid ] = {
1271- status : 'cancelled' ,
1272- executionId : exec . executionId ,
1273- jobId : null ,
1274- workflowId : exec . workflowId ,
1275- error : 'Cancelled' ,
1276- ...( exec . blockErrors ? { blockErrors : exec . blockErrors } : { } ) ,
12771298 }
1278- rowTouched = true
1279- }
1280- return rowTouched ? { ... r , executions : nextExecutions } : null
1281- } )
1299+ return rowTouched ? { ... r , executions : nextExecutions } : null
1300+ } ,
1301+ { onlyKey }
1302+ )
12821303 return { snapshots }
12831304 } ,
12841305 onError : ( _err , _variables , context ) => {
@@ -1794,14 +1815,20 @@ export async function snapshotAndMutateRows(
17941815 queryClient : ReturnType < typeof useQueryClient > ,
17951816 tableId : string ,
17961817 transform : ( row : TableRow ) => TableRow | null ,
1797- options ?: { cancelInFlight ?: boolean }
1818+ options ?: {
1819+ cancelInFlight ?: boolean
1820+ /** Restrict the walk to one exact cached query (e.g. the active filtered
1821+ * view) when the mutation's server effect doesn't cover other views. */
1822+ onlyKey ?: readonly unknown [ ]
1823+ }
17981824) : Promise < RowsCacheSnapshots > {
1825+ const scope = options ?. onlyKey
1826+ ? ( { queryKey : options . onlyKey , exact : true } as const )
1827+ : ( { queryKey : tableKeys . rowsRoot ( tableId ) } as const )
17991828 if ( options ?. cancelInFlight !== false ) {
1800- await queryClient . cancelQueries ( { queryKey : tableKeys . rowsRoot ( tableId ) } )
1829+ await queryClient . cancelQueries ( scope )
18011830 }
1802- const matching = queryClient . getQueriesData < RowsCacheEntry > ( {
1803- queryKey : tableKeys . rowsRoot ( tableId ) ,
1804- } )
1831+ const matching = queryClient . getQueriesData < RowsCacheEntry > ( scope )
18051832 const snapshots : RowsCacheSnapshots = [ ]
18061833 for ( const [ key , data ] of matching ) {
18071834 if ( ! data ) continue
0 commit comments