@@ -17,6 +17,7 @@ import type {
1717 FilterOp ,
1818 JsonValue ,
1919 Predicate ,
20+ PredicateNode ,
2021 TablePredicate ,
2122} from '@/lib/table/types'
2223
@@ -70,26 +71,37 @@ export function cellValueFilterConditions(
7071 return [ { field, op : 'eq' , value : value as JsonValue } ]
7172}
7273
74+ /** True when any leaf anywhere under `node` filters on `field`. */
75+ function mentionsField ( node : PredicateNode , field : string ) : boolean {
76+ if ( 'field' in node ) return node . field === field
77+ const members = 'all' in node ? node . all : node . any
78+ return members . some ( ( member ) => mentionsField ( member , field ) )
79+ }
80+
7381/**
7482 * Narrows `current` with one cell's conditions.
7583 *
76- * Existing top-level conditions on the same column are dropped first, so
77- * filtering twice on one column swaps the value instead of ANDing two
78- * equalities into a guaranteed-empty result. Conditions on other columns are
79- * kept — the action narrows what the user is looking at rather than replacing
80- * it.
84+ * Anything already constraining this column is dropped first, so filtering
85+ * twice on one column swaps the value instead of ANDing two conditions the
86+ * same row cannot satisfy — which would empty the table the user is looking at
87+ * and give them no clue why. Conditions on other columns are kept: the action
88+ * narrows the current view rather than replacing it.
89+ *
90+ * A whole nested group is dropped when it mentions the column ANYWHERE, not
91+ * just its top-level leaves. Reaching inside an `any` group to pull one leaf
92+ * out would silently WIDEN the user's disjunction — dropping the group loses
93+ * the other columns it mentioned, but it is visible in the panel afterwards
94+ * and never contradicts what was just asked for.
8195 */
8296export function withCellValueFilter (
8397 current : TablePredicate | null ,
8498 conditions : readonly Predicate [ ]
85- ) : TablePredicate {
99+ ) : TablePredicate | null {
100+ // Nothing to add leaves the filter exactly as it was — an `{ all: [] }` group
101+ // is not a valid predicate and the server rejects it.
102+ const field = conditions [ 0 ] ?. field
103+ if ( field === undefined ) return current
86104 if ( ! current ) return { all : [ ...conditions ] }
87- if ( 'all' in current ) {
88- const field = conditions [ 0 ] ?. field
89- const kept = current . all . filter ( ( node ) => ! ( 'field' in node && node . field === field ) )
90- return { all : [ ...kept , ...conditions ] }
91- }
92- // An `any` group is a whole disjunction — narrowing it means AND-ing the new
93- // conditions onto the group, not reaching inside it.
94- return { all : [ current , ...conditions ] }
105+ const members = 'all' in current ? current . all : [ current ]
106+ return { all : [ ...members . filter ( ( node ) => ! mentionsField ( node , field ) ) , ...conditions ] }
95107}
0 commit comments