Skip to content

feat(data-blocks): per-property AND/OR mode + in-operator collapse - #1748

Closed
jwalkingjew wants to merge 1 commit into
masterfrom
worktree-data-block-filter-mode
Closed

jwalkingjew wants to merge 1 commit into
masterfrom
worktree-data-block-filter-mode

Conversation

@jwalkingjew

Copy link
Copy Markdown
Collaborator

Summary

  • Replaces the global filter mode with per-property modes (one AND/OR per column).
  • Collapses multi-value selections into single GraphQL in clauses: RELATION → toEntity.id.in, TEXT → text.inInsensitive, numeric/date filters route to the right scalar via a new dataType discriminator on ValueCondition.
  • Adds dataType-aware scalar routing in the converter so future numeric / date / datetime UI lands without converter changes.
  • Migrates old persisted filters in-memory on read (legacy mode: 'OR' fans out to every property group); next save writes the new shape only — no compat shim long-term.

Why

  • The global 'AND' | 'OR' toggle was the wrong granularity. Users want "type is Person OR Author, AND status is Active" — per-property OR with implicit AND between columns. The global mode couldn't express that.
  • N values for the same property used to expand to N nested OR clauses on the wire (each its own relations.some {...} / values.some {...}). The schema has in everywhere relevant, so a multi-value selection collapses to one clause — smaller payload, simpler filter, less work on the API.

Architecture notes

  • New buildGroupWhere(filters, groupMode) in use-data-block.tsx replaces the prior buildSingleFilterWhere / buildOrWhere / buildAndWhere trio.
  • WhereCondition (DSL) gains ValueCondition.dataType and NumberCondition.in. The local EntityQuery engine doesn't need updates for the dataType hint — the converter is the only consumer that uses it.
  • Persisted format adds modes: Record<columnId, 'AND' | 'OR'> and stops emitting the legacy top-level mode. Schema keeps mode optional for read-time compat.
  • The per-group default mode is 'OR' (collapses cleanly to in); the persisted modes map only stores entries that diverge from default — keeps wire noise down.

Out of scope (separate PRs)

  • Backlinks in the filter creation UI. The engine handles them already (isBacklink round-trips through persistence + WHERE-builder + converter), but the picker has no path to add one. Tracked as PR B.
  • Multi-value chips for numeric / date / boolean. Converter routes by dataType when given multi-value, but the UI doesn't surface those pickers today. Future PR adds the pickers; the engine is ready.
  • Per-group AND for spaces. Kept OR-only (matches the top-level spaceIds.in API shape).

Test plan

  • Single relation column, multi-value, OR mode (default). Network tab: filter payload uses relations.some.toEntity.id.in: [...] (one clause), not nested OR.
  • Same column, click toggle to AND. Payload becomes and: [{...}, {...}] (one per chip). Results narrow to entities matching all values.
  • Two different columns, both multi-value. Each column collapses to its own in clause; the two clauses are AND'd between groups regardless of per-group toggle.
  • TEXT column, multi-value. No mode toggle visible. Payload uses text.inInsensitive: [...].
  • Open a data block in legacy format (persisted with mode: 'OR', multiple property groups). Renders correctly. Edit any chip → save. Reopen the entity values raw and confirm the persisted filter string now uses modes and the legacy mode key is gone.
  • Add → remove a relation chip in a group, then add again. No stale modes[columnId] entries persist when the group goes empty.
  • Power tools panel + non-edit (temporary) filters. Per-group toggle still works in the temporary state.
  • Chat tool flow: issue a setDataBlockFilters write with modesByColumn: { typeId: 'AND' } → verify the persisted filter has the right modes.

🤖 Generated with Claude Code

Replaces the global filter `mode` with per-property modes and collapses
multi-value selections into single GraphQL `in` clauses where the schema
supports it. Cuts GraphQL filter payloads, expresses "tagged with both X
and Y" cleanly, and hides the toggle for value-typed groups where AND
within a property has no representation on the wire.

Why:
- The single global `'AND' | 'OR'` toggle was the wrong granularity. Users
  want "type is Person OR Author, AND status is Active" — per-property OR
  with implicit AND between columns. The global mode couldn't express it.
- Multi-value selections expanded to N nested `OR` clauses on the wire.
  The connection schema has `in` on every relevant scalar, so RELATION
  multi-select collapses to one `toEntity.id.in: [...]` clause and TEXT
  multi-select to one `text.inInsensitive: [...]` clause.

What changed:
- `experimental_query-layer.ts`: `NumberCondition.in: number[]`,
  `ValueCondition.dataType` discriminator so the converter can route to
  the right scalar field on `ValueFilter`.
- `converters.ts`: `convertValueConditionToValueFilter` switches on
  `dataType` and emits `text.inInsensitive` / `integer.in` (BigInt
  strings) / `float.in` / `decimal.in` (BigFloat strings) / `datetime`,
  `date`, `time` (StringFilter `inInsensitive`) / `boolean.is`. The
  legacy `text`-fallback path is kept for callers that don't supply
  dataType.
- `filters.ts`: persisted shape gains `modes: Record<columnId, 'AND' |
  'OR'>`. Legacy top-level `mode` stays in the schema for read-time
  compat; the writer never emits it. Read-time migration: legacy
  `mode: 'OR'` fans out to every present property group; absent or
  `'AND'` => empty modes (per-group default at the WHERE-builder is OR,
  which collapses cleanly to `in`). Old blocks read fine; the next save
  rewrites in the new shape and the legacy keyword disappears.
- `use-filters.ts`: replaced `filterMode` / `setFilterMode` (and
  temporary equivalents) with `modesByColumn` / `setGroupMode(columnId,
  mode)`. `setFilterState` trims mode entries for removed columns.
- `use-data-block.tsx`: new `buildGroupWhere(filters, groupMode)` —
  RELATION + OR (or single) collapses to one `in` clause across
  relations / backlinks / types / spaces; AND keeps `AND-of-singles` so
  intersection semantics stay expressible. TEXT collapses to
  `inInsensitive` (or `name.in`). Numeric / date groups remain
  `OR-of-singles` — UI doesn't surface multi-value chips for them
  today, but the converter is ready when it does.
- `table-block-filter-pill.tsx`: toggle hidden for non-relation groups.
  `FilterGroup` carries `isRelationGroup` so the pill knows.
- `table-block.tsx` / `power-tools-screen.tsx` / `use-power-tools-data`:
  swap `setActiveFilterMode` closures for `setActiveGroupMode(columnId,
  next)` with per-group state.
- `edit-types.ts` / `edit-dispatcher.ts` / chat `data-block.ts` write
  tool: `setDataBlockFilters` intent now carries `modesByColumn` instead
  of `mode`. Tool input schema accepts `modesByColumn?: Record<columnId,
  'AND' | 'OR'>` (optional).

Tests: 75/75 in touched files (converters + filters + edit-dispatcher +
chat data-block tool). New cases for per-property mode round-trip,
legacy fan-out, OR omission when default, scalar `in` routing per
dataType, relation multi-value `in` collapse.
@vercel

vercel Bot commented May 1, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
geogenesis Ready Ready Preview May 1, 2026 4:55pm

Request Review

@ohohoreilly

Copy link
Copy Markdown
Contributor

Closing as part of a sweep of the open-PR queue. Not a judgement on the work — reopen if you still want it and I will help get it current.

Opened 2026-05-01 and now conflicting with master. At this distance a rebase is usually more work than redoing the change against current code, and the surrounding code has moved a long way underneath "feat(data-blocks): per-property AND/OR mode + in-operator collapse".

@jwalkingjew — if the idea still stands but the branch does not, a fresh PR or a ticket is probably a better route than reviving this one.

Nothing is discarded: the branch and its history remain, and reopening costs a click.

Context: 74 PRs were open, 25 older than two months, the oldest from February. The point is to make the queue mean something so genuinely ready work is visible rather than buried — #2449 sat ready for three days this week partly because of the noise. Only non-draft, conflicting PRs are in scope; drafts and anything still mergeable are being left alone.

This branch was successfully deployed

1 active deployment
Preview — 7f9ef8c0 Deployed May 1, 2026 by vercel[bot]
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.

2 participants