Skip to content

fix(row-model): accept the boolean filter operand the docs promise and the funnel sends - #361

Merged
blove merged 1 commit into
mainfrom
blove/boolean-filter-operand
Aug 13, 2026
Merged

blove merged 1 commit into
mainfrom
blove/boolean-filter-operand

Conversation

@blove

@blove blove commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Summary

Filtering a type: "boolean" column threw an uncaught error and took the whole page down:

CompiledQueryValidationError: Invalid compiled query at query.filters[0].value: boolean selection must contain only boolean values

validateFilterOperand required real JS booleans. Three things say it shouldn't:

  1. filter-operators.ts's BOOLEAN_OPTIONS emits string "true" / "false".
  2. docs/grid/filtering.mdx documents that contract explicitly — a boolean column's options may relabel the two states, but their values must remain "true" and "false".
  3. booleanValue(), the evaluator this validator guards, already coerces those strings.

The validator was stricter than the evaluator it protects and stricter than the documented contract. The docs and the React menu were right; the validator was the outlier.

Origin: 72e7d47d (#321) introduced compiled-query.ts whole-cloth and deleted grid-core/src/evaluate-filter.ts, which duck-typed booleans as strings with no strict validation. The React side was built against that permissive contract and never updated.

Not UI-only. A caller who never opens the funnel and hand-writes a query following the docs got the identical throw, synchronously, out of public @pretable/core setQuery().

Scope of the audit

The full column-type × operator matrix was executed before fixing anything: text, number, date, and enum all validate and evaluate correctly across every operator the menu can emit. Boolean isAnyOf / isNoneOf were the only failures, and there were no silently-wrong cases.

The real deliverable

No test anywhere crossed the react → row-model boundary for filters. filter-operators.test.ts tested toColumnFilter in isolation; row-model's tests never consumed its output. That gap is why this shipped.

packages/react/src/__tests__/filter-menu-row-model-boundary.test.ts now pipes the real toColumnFilter / operatorsForType output into the real compileQuery for all 31 (type, operator) pairs, asserting both that compilation succeeds and that matching is correct. A self-check keeps the case table in sync with operatorsForType, so the two can't drift apart silently.

It lives in packages/react because that package already depends on both sides; the reverse placement would invert the dependency graph.

A test pinned the bug

compiled-query.test.ts asserted the validator rejects ["true"] — encoding the broken behavior as intended. Replaced with positive tests proving ["true"] / ["false"] validate and match the right rows, plus a mixed [true, "false"] case. The rest of the rejection table is intact and still fails as expected.

Deliberately not accepted

1 / 0 / "1" / "0", which booleanValue() also coerces. The docs promise only the two string literals; accepting more would move the drift from "validator too strict" to "validator too loose".

Test Plan

  • row-model 315, react 862, core 7, root pnpm test — all green
  • typecheck, lint, format, build, api:check, typecheck:public — clean
  • Mutation-proven: reverting the validator fails 2 of 32 boundary cases with the exact CompiledQueryValidationError above

Follow-up

content/examples/column-filters/ dropped its boolean column because of this crash; it should be restored once this merges.

🤖 Generated with Claude Code

validateFilterOperand rejected string "true"/"false" for boolean
isAnyOf/isNoneOf filters, throwing CompiledQueryValidationError even
though: the React funnel's BOOLEAN_OPTIONS emits those exact string
literals, content/docs/grid/filtering.mdx documents them as the
required contract, and booleanValue() (the evaluator this validator
guards) already coerces them. The validator was stricter than the
contract it was meant to enforce.

Widen it to accept real booleans plus the string literals "true"/
"false" - not 1/0/"1"/"0", which booleanValue() also coerces but the
docs never promise, so accepting them would be validator-only drift.

Removes the test row that pinned the bug as intended behavior and
replaces it with positive tests that the accepted shapes validate and
evaluate correctly.

Adds a cross-boundary test (packages/react, which already depends on
both @pretable/core and @pretable-internal/row-model) that pipes the
real toColumnFilter()/operatorsForType() output into row-model's real
compileQuery() for every column type and operator the filter menu can
emit. Neither package's own tests exercised this seam, which is why
the mismatch shipped.
@vercel

vercel Bot commented Aug 13, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
pretable Ready Ready Preview Aug 13, 2026 3:54pm

@github-actions

Copy link
Copy Markdown
Contributor

Vercel preview ready

Preview: https://pretable-37e1epn44-cacheplane.vercel.app
Commit: 2e4132124ca159bb6d82f03f0d73dd56f3384969

Updated automatically by the deploy-preview job.

@blove
blove merged commit c64e9d9 into main Aug 13, 2026
17 checks passed
@blove
blove deleted the blove/boolean-filter-operand branch August 13, 2026 16:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant