Skip to content

One condition is shown but another is applied in GenericFilter - #5634

Merged
fractal3000 merged 5 commits into
release_3_0from
bug/5516-one-condition-is-shown-but-another-is-applied-in-generic-filter
Sep 7, 2026
Merged

fractal3000 merged 5 commits into
release_3_0from
bug/5516-one-condition-is-shown-but-another-is-applied-in-generic-filter

Conversation

@fractal3000

@fractal3000 fractal3000 commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

See #5516

GenericFilter composes the data loader condition as base AND current configuration, where the base is whatever the application set on the loader itself. Until now that composition ran only when the configuration was switched; every other applying gesture just loaded. When the application replaced the loader condition after a configuration was already active (allowed since #5400), the loader kept loading by the new base alone while the filter showed the configuration's condition.

Now every applying gesture recomposes before loading — but only when the loader condition actually diverged: the filter remembers the condition object it set last (lastConditionSetByFilter, the rule introduced by #5400) and recomposes only if the application has replaced it since. An untouched loader condition is not rewritten, so applications that never replace the base see byte-for-byte the previous behavior. The covered gestures:

  • GenericFilter.apply(), the Apply button and its shortcut; a standalone GroupFilter.apply() — recompose in place;
  • a condition value change (Enter, a dropdown selection) and an operation change, which load the data loader directly from the condition component or from the delegated group: before such a load the component invokes a recomposition delegate — a callback to the owning GenericFilter or standalone GroupFilter, maintained by the owner itself (a group hands its chain to children in add() and clears it in remove(), the filter sets it on a configuration's root group, the converters set it on components they create). Works at any nesting level, including a group nested in a standalone groupFilter; DataGridHeaderFilter stays outside this wiring, which is pinned by a test.

Tests: GenericFilterApplyAfterBaseChangeTest (flowui, 12 cases) and DataGridHeaderFilterApplyTest (the header filter is outside the recomposition wiring) — apply()/Apply button/value change/operation change after the base was replaced, for GenericFilter and standalone GroupFilter incl. a nested group, autoApply=false variants, repeated apply() does not accumulate conditions, exactly one load per gesture, the empty configuration without a base, and the delegated root group leaving an untouched loader condition alone. Six of the original eight fail without the apply-path fix; the four gesture cases fail without the gesture fix. Full :flowui:test (1122) and :flowui-data:test (31) pass; the filter-test scenario app was swept with Playwright on 3.0.1 and on this branch — 47 views, the only differences are the scenarios that reproduce this issue.

Notes for review and release notes:

  • Behavior change (the point of the fix). After dataLoader.setCondition(...) while a configuration is shown, any applying gesture composes base AND configuration. An application that replaced the loader condition specifically to override the filter will see the configuration re-applied. Repro: replace the loader condition after a configuration is active, then press Enter in a condition field.
  • Known characteristic. Two filters on one DataLoader: alternating applying gestures adopt the neighbour's composition as the base and re-add their own output — the condition tree grows with duplicate (idempotent) AND entries; query results stay correct. Repro: GenericFilter + standalone groupFilter on one loader, alternate gestures between them. Root cause is the shared loader condition, out of scope here.
  • Remaining gap (unchanged by this PR). A standalone propertyFilter/jpqlFilter that is not part of a composite filter, and a configuration whose condition components are all invisible, still load by a replaced base. Both are reachable only programmatically.
  • Pre-existing, filed as GenericFilter: restoring filter state from the URL fires a redundant data load when a condition operation differs #5655. During URL restore, an operation change fires an intermediate load (GroupFilter.add registers an unconditional operation-change listener). This PR does not change the number of loads; the fix is GenericFilter: restoring filter state from the URL fires a redundant data load when a condition operation differs #5659, stacked on this PR.

QA (filter-test from #5586, Scenarios 16, 25, 26 — the attached archive of #5586 predates 25/26; the current project has them):

  • Scenario 16, Set base AFTER activation (amount ≥ 100): the label must show AND[amount greater_or_equal 100, AND[status equal OPEN]] and only Open orders — not amount greater_or_equal 100 alone.
  • Scenario 25, Replace base only, then the filter's Refresh, or a change of the Status value, or of its operation: both conditions, not the base alone.
  • Scenario 26 (standalone GroupFilter), Replace base, then apply(), and Replace base only + change the Status value or operation: both conditions.
  • A view with a filter and no configuration selected: Refresh still loads all rows; no errors.
  • autoApply="false": pressing Apply after the application replaced the loader condition loads by both conditions.

🤖 Generated with Claude Code

@fractal3000 fractal3000 linked an issue Aug 28, 2026 that may be closed by this pull request
@fractal3000
fractal3000 force-pushed the bug/5516-one-condition-is-shown-but-another-is-applied-in-generic-filter branch from 88b93de to 9180cfe Compare August 28, 2026 16:38
@fractal3000
fractal3000 marked this pull request as draft August 31, 2026 06:34
@fractal3000
fractal3000 force-pushed the bug/5516-one-condition-is-shown-but-another-is-applied-in-generic-filter branch from 9180cfe to 8ce8da9 Compare September 4, 2026 08:41
Pavel Aleksandrov and others added 2 commits September 4, 2026 15:56
…replaced base keeps the shown configuration

The composition 'base AND current configuration' ran only on a configuration
switch; apply(), the Apply button and a standalone GroupFilter.apply() just
loaded, so a base replaced by the application after activation (allowed since
the base-adoption rule) was loaded alone while the filter kept showing its
configuration.

These entry points now recompose before loading - but only when the loader
condition actually diverged: the filter remembers the condition object it set
last and recomposes only if the application has replaced it since. An untouched
loader condition is not rewritten: the condition object stays stable across
applies, an empty configuration without a base keeps the loader condition null,
and the moment the base is first captured is unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…load

A value change (Enter, a dropdown selection) and an operation change load the
data loader directly from the condition component or from the delegated group,
bypassing the owner's composition. When the application had replaced the loader
condition, such a load ran by the new base alone while the filter kept showing
its configuration. Before these loads the component now finds the nearest
non-delegated owner (GenericFilter or a standalone GroupFilter) up the
component tree and lets it recompose - under the same divergence guard as
apply(), so an untouched loader condition is left exactly as before.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Pavel Aleksandrov and others added 2 commits September 5, 2026 15:57
Review follow-up. The delegated component no longer walks the component tree
looking for its owner through a marker interface: it receives a recomposition
delegate from the owner itself. A group hands its chain to children when they
are added (and clears it on removal), the filter sets it on a configuration's
root group, the converters set it on the components they create; late-bound
method references let a request from any nesting level reach the outermost
owner. The interface and the traversal are removed; the apply entry points
call recomposeLoaderConditionIfOutdated() instead of inlining its body.
DataGridHeaderFilter stays outside the wiring, pinned by a new test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The root-group construction in RunTimeConfigurationBuilder duplicated
GenericFilter.createConfigurationRootLogicalFilterComponent line by line -
the builder now calls it. The single-condition branch of the converter
wiring is removed: such a component always joins a group before it can
load, and the group re-wires it. The staleness predicate lives once in
BaseConditionSupport.isReplacedExternally and is reused by the recompose
routine and both filters. GroupFilter merges the two recomposition methods
into one recomposeLoaderConditionIfOutdated, mirroring GenericFilter, so
children receive the same-named delegate. A delegated condition now
recomposes on apply() regardless of autoApply, consistently with the other
apply entry points, which is pinned by a test. Javadoc inaccuracies fixed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@fractal3000

Copy link
Copy Markdown
Contributor Author

Rework pushed: 12890bbb48 (a delegate instead of the interface + traversal, dedup, a header-filter test) and 4155278db6 (a quality pass over the final diff). :flowui:test is green, both gestures (value / operation change after the loader condition was replaced) verified on the demo project, and the UI sweep shows no behavior change.

@fractal3000
fractal3000 merged commit 08a18e2 into release_3_0 Sep 7, 2026
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.

One condition is shown but another is applied in GenericFilter

3 participants