Skip to content

fix(bindx-dataview): start a missing filter at its initial artifact - #134

Merged
matej21 merged 3 commits into
mainfrom
fix/filter-initial-artifact
Sep 28, 2026
Merged

matej21 merged 3 commits into
mainfrom
fix/filter-initial-artifact

Conversation

@matej21

@matej21 matej21 commented Sep 28, 2026 •

Copy link
Copy Markdown
Member

Closes #125.

The bug

useFilteringState read a registered filter that its artifact record does not name in three different ways:

View Before
filters.get(name).artifact handler.defaultArtifact() (ignoring initialArtifact)
getArtifact(name) undefined
resolvedWhere, hasActiveFilters filter skipped

A record misses a name when it was stored before the filter existed, or when setAllArtifacts restores a preset that omits the filter. A filter UI built on filters then showed one state while the query applied another, and a filter meant to start constrained started unconstrained.

A DataGrid column also had no way to declare where its filter starts: useDataGridSetup registered each column filter as { handler } only.

The fix

  • useFilteringState resolves the record once: { ...initialArtifacts, ...stored }, where a filter's initial artifact is initialArtifact ?? handler.defaultArtifact() (the same value it already used when nothing was stored). filters, getArtifact, resolvedWhere, hasActiveFilters and the setArtifact updater all read that record. Nothing extra is written to storage.
  • ColumnLeafProps.filterInitialArtifact (optional) is passed to the filter definitions as initialArtifact by useDataGridSetup. The built-in columns accept it: every createColumn column (typed by the column's artifact type), the enum and enum-list columns (headless and bindx-ui, values narrowed to the enum), the relation columns (RelationFilterArtifact) and the generic DataGridColumn.
  • ColumnComponent<TExtraProps, TFilterArtifact> gains the artifact parameter, defaulting to never, so a specific column stays assignable to the unparameterized ColumnComponent (const C: ColumnComponent = createColumn(textColumnDef, …) compiles). Such an erased column takes no filterInitialArtifact.
  • EnumFilterArtifact / EnumListFilterArtifact take the enum value type (<TValue extends string = string>), so values / notValues of an enum column's initial artifact must be values of its enum.

Behaviour changes

  • getArtifact(name) for a registered filter the record does not name returns its initial artifact (initialArtifact ?? handler.defaultArtifact()), not undefined. It returns undefined only for an unregistered name.
  • setAllArtifacts({}) (or any record that omits a registered filter) puts that filter at its initial artifact, so a filter with an initialArtifact becomes active. To clear it, pass the handler default for it explicitly.
  • A saved preset or stored record written before a filter existed, or that omits it, restores that filter at its initial artifact, and the query applies it.
  • filters, getArtifact, resolvedWhere, hasActiveFilters and a setArtifact updater now agree for such a filter (before, filters showed the handler default while the query skipped it).

Semantics chosen

Two resets existed already, and this PR keeps both:

  • resetFilter(name) and setArtifact(name, undefined) clear the filter: they write the handler's default (inactive) artifact.
  • resetAll() returns to where the grid starts: the initial artifacts.

A name missing from the record now reads as "never touched", so it reads as where the grid starts: its initial artifact. For setAllArtifacts this means that a restored record that omits a filter puts that filter at its initial artifact, not at the handler default. A caller that stores only the active filters of a preset, and wants the omitted ones cleared, passes the handler default for them explicitly. Without an initialArtifact the two are the same value, so nothing changes for filters that do not declare one.

One existing assertion changed for that reason: after setAllArtifacts omits a filter with no initial artifact, getArtifact returns the handler default {} instead of undefined. The query is unchanged (the filter is inactive).

Tests

tests/react/dataview/filterMissingFromStoredState.test.tsx (the failing repro from bug/grid-filter-initial-artifact-and-missing-default, extended):

  • a stored record without the filter → initial artifact in getArtifact, filters, resolvedWhere, hasActiveFilters;
  • setAllArtifacts with a record that omits a filter → initial artifact everywhere;
  • a setArtifact updater receives the initial artifact of an omitted filter;
  • resetFilter → handler default, resetAll → initial artifact;
  • a DataGrid with <DataGridEnumColumn filter filterInitialArtifact={…} /> starts filtered, with and without a stored record that predates the filter;
  • filterInitialArtifact reaches the filter for a createColumn column (DataGridTextColumn, rows filtered) and a relation column (DataGridHasOneColumn, resolved where; MockAdapter does not evaluate relation conditions).

tests/unit/types/columnFilterInitialArtifact.test.ts (type level): a createColumn column and DataGridTextColumn assign to ColumnComponent; a column accepts an initial artifact of its own type only; enum columns reject values outside their enum.

Against main, 6 of the 7 fail; the reset case pins existing behaviour and passes on both.

Verification

  • bun run typecheck clean
  • bun run lint 0 errors (no warnings in touched files)
  • bun run test: 2121 pass, 0 fail

🤖 Generated with Claude Code

https://claude.ai/code/session_01Ge3kRufpi9b9HMBSBjRze5

matej21 and others added 3 commits September 28, 2026 11:07
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPdGxo89UiPkZHxreAxvFD
A registered filter that the artifact record does not name (a stored record
written before the filter existed, or a restored preset that omits it) was
read three ways: `filters` materialized the handler default, while
`getArtifact`, `resolvedWhere` and `hasActiveFilters` skipped it. The UI and
the query disagreed, and a filter's `initialArtifact` was ignored.

The record is now resolved once over the initial artifacts
(`initialArtifact ?? handler.defaultArtifact()`), and every view, including
a `setArtifact` updater, reads that. `resetFilter` still clears to the handler
default and `resetAll` still returns to the initial artifacts.

A DataGrid column can declare where its filter starts with
`filterInitialArtifact`, threaded from `ColumnLeafProps` into the filter
definitions of `useDataGridSetup`. The built-in scalar, enum, relation and
generic columns accept it.

Closes #125

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ge3kRufpi9b9HMBSBjRze5
`ColumnComponent`'s filter artifact parameter defaulted to the whole
`FilterArtifact` union. `filterInitialArtifact` sits in the props parameter, so
a column built by `createColumn` (typed by its own artifact) was no longer
assignable to the unparameterized `ColumnComponent`. The parameter now
defaults to `never`: an unknown column takes no initial artifact, and every
specific column fits.

`EnumFilterArtifact` and `EnumListFilterArtifact` take the enum value type
(default `string`), so an enum column's `filterInitialArtifact` accepts only
values of its enum.

Also documents `getArtifact` and the fallback for a stored `null` entry, and
adds pass-through tests for a `createColumn` column and a relation column plus
type-level tests for the assignments above.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ge3kRufpi9b9HMBSBjRze5
@matej21
matej21 merged commit e29b4bd into main Sep 28, 2026
4 checks passed
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.

Grid filter cannot start at a non-default artifact; a stored record missing a filter disagrees with the query

1 participant