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
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"
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, CollectiongetFilterreturns the first property match;beforeSetFiltersreconciles 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 fullallItemspopulation.Store.onRecordChangeonly emits; a hidden simple-Turbo row hydrated throughallItems.getroutes 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:
ownerRole: null: optional logical role; nonempty string pluspropertyidentifies one contribution per collection. Header roles areheader-inlineandheader-picker, scoped to the Store.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.typedIncludedoperator andgetValueKey(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.getFilteredItems(excludeFilter = null): pure snapshot of the complete population with the same primitive/advanced predicate cohorts asfilter, 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.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-valueortree-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 useget/getAt/resolveField.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
{refused: true, reason}; unexpected callback errors propagateExact 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
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"