LearnNewsExamplesServices
Frontmatter
id19669
titleShared column filter ownership and observational typed value counts
stateClosed
labels
enhancementaicoregrid
assigneesneo-gpt
createdAtOct 12, 2026, 5:37 PM
updatedAtOct 12, 2026, 9:45 PM
githubUrlhttps://github.com/neomjs/neo/issues/19669
authorneo-gpt
commentsCount1
parentIssue19643
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[ ] 19671 Grid header menu: trigger, floating surface and the wired value picker
closedAtOct 12, 2026, 9:45 PM
milestonev13.3

Shared column filter ownership and observational typed value counts

Closed Backlog/active-chunk-8 enhancementaicoregrid
neo-gpt
neo-gpt commented on Oct 12, 2026, 5:37 PM

Context

Core delivery leaf of #19643, graduated from D#19646 at accepted contract eaae5352…. Pre-work epic review: 6127438609. This is the agreed filter/count responsibility; the steward retains the floating header/picker surface.

The Problem

The executed current-source discriminator shows property-first header lookup overwriting picker or application predicates depending on creation order. Counts must evaluate the full population under every other predicate, without temporarily editing filters or hydrating raw records. Selected membership must also stay effective on normal record edits after all UI controls close.

Design authority: the graduated contract requires “Store-scoped (role, dataField)” identity, exact-contribution exclusion and persistent Store intent; this is an additive feature, not a claim that the legacy property-first API is defective for all callers.

The Architectural Reality

At Engine 9690340f, Collection getFilter returns the first property match; beforeSetFilters reconciles operator/property/value while ignoring callback, scope and additional configuration. Filter owns evaluation and local configuration; Store owns Model/RecordFactory input, Turbo hydration and the full allItems population. Store.onRecordChange only emits; a hidden simple-Turbo row hydrated through allItems.get routes subsequent edits to that projection's Model/Store.

Grid and Table header writers call the property-only method. ComboBox, Chip, list.Base and list.Country use it for their existing unowned typeahead/highlight behavior. The core must isolate all these producers/consumers without importing UI identity into Store.

The Fix

Extend the existing Collection/Filter/Store primitives, and migrate the four header writers plus four built-in typeahead/highlight readers. No new source service or registry.

Proposed public surfaces:

  • Filter ownerRole: null: optional logical role; nonempty string plus property identifies one contribution per collection. Header roles are header-inline and header-picker, scoped to the Store.
  • Collection getFilter(property, ownerRole): omitted role preserves first-match behavior; explicit null selects unowned; a string selects the exact tuple. Validate duplicate owned tuples before reconciliation effects. Preserve complete incoming predicate configuration; never coalesce owned and unowned/equal-but-different-role filters.
  • Filter typedIncluded operator and getValueKey(value): strict scalar identity, valid Date epochs distinct from numbers, no UI callback scope. The new operator is local-only; remote consumers are deferred. Nonempty selection is explicit inclusion; removal/Clear is the consumer's removal of that exact contribution. Reject unsupported values.
  • Collection getFilteredItems(excludeFilter = null): pure snapshot of the complete population with the same primitive/advanced predicate cohorts as filter, excluding only an exact current Filter instance; stale or foreign exclusions throw. Reuse this evaluation path for actual filtering rather than introduce a second predicate algorithm.
  • Store getFilterValues(property, excludeFilter = null): typed {key, value, count} buckets from that snapshot. Return {refused: true, reason} for expected refusals: unknown-filter (a stale/foreign exclusion), remote-projection, incomplete-population, unknown-field, raw-complex-fields, noncanonical-field, unsupported-value or tree-population; successful arrays retain population order. Display-label sorting belongs to the surface; direct canonical eager/simple-Turbo values only. Do not infer reconstruction capability from simple metadata or use get/getAt/resolveField.
  • Filter reapplyOnRecordChange: false: false-default opt-in. Store reuses its filtering writer once per normal edit before publishing settled membership, including edits routed from its own allItems projection; guard current object membership so stale retained records do not affect a replacement population. Clearing/disabling/replacing the opt-in contribution removes this behavior. Unowned-only legacy behavior stays unchanged.

Header ownership changes are limited to producer isolation; the surface leaf binds controls to authoritative Store state, handles source/field retarget and disposes borrowed observers. Source identity and count APIs contain no popup/list state.

Contract Ledger

Target surface Source authority Proposed behavior Fallback Docs Evidence
Filter ownerRole; Collection.getFilter/beforeSetFilters existing Filter config and Collection methods exact shared tuple; complete config; pre-effect duplicate refusal omitted getFilter role remains legacy first-match; null unowned Filter/Collection JSDoc + Collections guide real instances; all creation orders; replacement/callback/disabled/capability controls
typedIncluded; Filter.getValueKey Filter operators/isFiltered scalar identity and Date epoch inclusion, with no UI capture existing operators retain semantics; unsupported typed inputs throw Filter JSDoc + guide null/undefined/blank/boolean/string/number/Date and invalid controls
getFilteredItems; Store.getFilterValues Collection.filter and Store full population same ordered predicate cohorts; exact instance exclusion; observational buckets Collection rejects unknown exclusions; Store returns {refused: true, reason}; unexpected callback errors propagate method JSDoc + guide reference counts, same-property inline/app predicates, no filter events/hydration/raw writes
reapplyOnRecordChange RecordFactory change → Store.onRecordChange; Store.createAllItems opt-in local membership settles once before event, even hidden Turbo edits false preserves existing unowned edit behavior; silent raw edits require caller invalidation config/hooks JSDoc + guide eager/Turbo hidden/visible/bulk edits; Clear/disable/replacement; stale record and teardown
Header/typeahead call sites existing Grid/Table writers and ComboBox/Chip/List/Country readers shared header-inline identity; built-in readers select unowned external property-only callers retain ambiguity method JSDoc actual header writer discriminator plus built-in caller control

Exact internal evaluation/caching details may simplify during implementation; the behaviors and fallback above remain the acceptance boundary.

Discussion Criteria Mapping

Shared ownership/complete reconciliation → AC1/AC2. Typed explicit inclusion and exact-contribution observational counts → AC3/AC4. Source intent and ordinary record edits → AC5. Canonical eager/simple-Turbo coverage and raw-query purity → AC4/AC6. Production controls, Buffered cap/selection retention, source observer lifetime and actual 100k list/input performance stay with the surface leaf and parent entry matrix, not claimed delivered here.

Acceptance Criteria

  • AC1: In all picker/inline/application creation orders, each header writer changes only its shared header-inline contribution; old omitted-role lookup remains first-match and explicit-null built-in callers select their unowned contribution.
  • AC2: Equal predicates with different roles remain distinct. Complete callback/scope/disabled/include-empty/capability configuration survives authoritative replacement. Duplicate owned tuples refuse before destroying or changing previous valid filters. Omitted contributions stay removed.
  • AC3: typedIncluded distinguishes string/number/boolean/null/undefined/blank and valid Date epochs from numeric timestamps; equal Date epochs match. Objects, arrays, NaN and invalid Dates refuse; legacy operators remain unchanged.
  • AC4: Contextual counts apply inline/application predicates on the same property, exclude only the requested picker instance, and match reference primitive/advanced cohorts. Query leaves source membership, events, raw keys/values and hydration/cache-write counters unchanged.
  • AC5: An opted-in selection remains effective across no-view eager/simple-Turbo edits, including a hidden allItems-hydrated record and a bulk edit. Subscribers see final membership/index; one edit performs one reapplication. Clear/disable/replacement removes the opt-in; stale retained records and destroy do not affect current data. Legacy unowned-only edit behavior is a negative control.
  • AC6: A fully loaded canonical 100k bigData-shaped Turbo source counts without hydration or raw writes; unsupported populations return documented refusal reasons without mutation. API documentation states canonical-input preconditions and silent/raw mutation invalidation.
  • AC7: Focused and owning Engine unit tests, documentation/static guards and current-head required CI pass. The surface consumes the landed API; this leaf does not close the parent product matrix.

Out of Scope

Floating picker/menu, toolbar, Buffered admission policy and rendering, option/search labels, control projection/teardown and actual UI performance, column gestures, dynamic row geometry, remote facets, partial/Tree populations, raw complex reconstruction, arbitrary external property-only ownership, general typeahead composition.

Avoided Traps

No view IDs or global ownership registry; no UI callback capture in typed selection; no remove/reapply counting; no raw hydration disguised as a query; no header listener as the persistent membership writer; no broad automatic refilter policy for legacy unowned filters. Existing source methods own the concern.

Decision Record: Not needed, as classified by D#19646. Decision Record impact: none. Structure-map: N/A — existing Engine sources. New unit specs use sibling-source-mirror patterns; structural fast-path before writing.

Related

Surface #19670 and #19671 consume this core; #19671 is natively blocked by this ticket. Prescription read/disposition: 6127702097.

Parent #19643 · source D#19646 · sibling #19658 · overlap coordination #19515.

Sweeps: final live latest-open, all-read-state A2A claims, MC problem-rationale and own-assignment reads run immediately before filing; no equivalent core child found. The accepted parent already owns the outcome; no new epic is created.

Origin Session ID: 8c0f4f80-c07d-415b-b4f9-3f323d031ce3 Retrieval Hint: "D19646 getFilter property shared role counts hidden Turbo allItems recordChange"

tobiu referenced in commit d4abf02 - "feat(data): isolate column filters and count typed values (#19669) (#19673) on Oct 12, 2026, 9:45 PM
tobiu closed this issue on Oct 12, 2026, 9:45 PM