You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(lint): walk page filterBy and lookup-field lookupFilters as authored filters (#19818)
Fixes#19791
Clause-②: no
<sub>Rewritten short by the `domain:spec#5` seat (2026-09-23T15:28Z),
which took this PR over from seat 2. The earlier body is in the edit
history; the dev reports are on #19791.</sub>
A page's `interfaceConfig.filterBy` and a lookup field's `lookupFilters`
hold filter rules that reach the engine's `where`, but the lint's shared
filter walk did not enter them. A date-range preset in either, such as
`{ field: 'close_date', operator: 'gt', value: 'last_30_days' }`,
parsed, linted and published green.
## What changed
- **Lint** (`@objectstack/lint` patch): `FILTER_KEYS` in
`packages/lint/src/filter-walk.ts` adds `filterBy` and `lookupFilters`.
`filter-preset-comparand` binds a `filterBy` rule to
`interfaceConfig.source` (else the page's `object`), and a
`lookupFilters` rule to the field's `reference`. A `relatedListFilter`
on the same field still binds to the owning object.
`filter-token-unknown` reaches both keys through the same walk.
- **Migration entry `18.filter-preset-ordering-comparand-refused`**
(`@objectstack/spec` patch; rulings `5793402649` and `5794625162` on
#19791): the fix makes parts of it false, and they are deleted with no
replacement: `surface` group (3) and the word "three" before it, the
by-hand clause in `acceptanceCriteria`, and the "reached by neither"
clause in `reason`. `registry.ts` is regenerated.
- **Pending note `.changeset/19778-preset-entry-carriers.md`** (from
#19801, not yet released): its item 3 said both keys lint green and must
be swept by hand. That is false after this PR, so item 3 is deleted,
"The three groups" becomes "The groups", and the Reach sub-bullet
counting "nine sentences" is deleted too.
## `Check Changeset` is red on purpose
This PR edits a pending changeset it did not add (the #19778 note
above). `check-empty-changeset.mjs` calls this a DELIBERATE CORRECTION:
the check stays red. The correction is recorded on this PR
(`5796661306`) and awaits the maintainer's written confirmation. `Check
Changeset` is not a required context.
## Measured (dev reports on #19791)
- With the card's rule in each key: `os lint`, `os validate` and the
runtime publish gate refuse it at this head; none of them refuses it at
`afc3b64928`. Lit control (a component `dataSource.filter`): refused on
both trees. Dark control (an ISO date): green on both trees.
- New tests: 5 in `validate-preset-comparands.test.ts` and 1 in
`validate-filter-tokens.test.ts`. Restoring the base source turns 5 red.
Three ablations each turn their pin red: dropping the two keys, dropping
the `lookupFilters` binding, and dropping the `interfaceConfig` reader.
- Two at-tier reviews (`5795612549`, `5796648031`) failed sentences, not
code. Rounds 4 and 5 cut them: three "the two carriers" exclusives
(object-grid `defaultFilters` is also outside the walk), a lit-control
claim, an unknown token called "silent" (the engine throws
`FILTER_TOKEN_UNKNOWN`), and "refused only at query time".
- CI at `4dda1fd0fd`: all seven required contexts pass. `Check
Changeset` is red as described above; the gate comment is `5796661306`.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1
---------
Co-authored-by: Claude <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: .changeset/19778-preset-entry-carriers.md
+1-3Lines changed: 1 addition & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -8,14 +8,12 @@ Clause-②: no
8
8
9
9
No behaviour moves. No schema, accept set or lint rule is touched, and no export is added, removed or retyped. Every line this change edits in `registry.ts` is a string literal inside that one step-18 entry, which the exported `MIGRATIONS_BY_MAJOR` carries, so what moves in `dist` is prose.
10
10
11
-
-**The three groups the entry now draws.** They list the carriers measured, not a closed partition; the entry's grep sentence is the catch-all. Each was measured against the built `dist` with a preset comparand (`last_30_days`, and `today` as a `between` endpoint), and an ISO-date dark control reads green in every cell.
11
+
-**The groups the entry now draws.** They list the carriers measured, not a closed partition; the entry's grep sentence is the catch-all. Each was measured against the built `dist` with a preset comparand (`last_30_days`, and `today` as a `between` endpoint), and an ISO-date dark control reads green in every cell.
12
12
1. Slots typed `FilterConditionSchema`: `DashboardWidgetSchema.filter`, `GlobalFilterOptionsFromSchema.filter`, `DatasetSchema.filter`, `DatasetMeasureSchema.filter`, `ReportSchema.runtimeFilter`, `JoinedReportBlockSchema.runtimeFilter`, `FieldSchema.relatedListFilter` and `FieldSchema.summaryOperations.filter`. A parse of the declaring schema refuses each one at the comparand's own path, and the lint rule reports each one as well.
13
13
2. Filters under a key the lint walks whose declared type carries no preset check. These are `ViewFilterRuleSchema` rule arrays (a view's `filter`, a page element's `dataSource.filter`, a page component's `filter` prop) and a Mongo-shape record typed as a loose record rather than `FilterConditionSchema` (a flow `get_record` / `update_record` / `delete_record` node's `config.filter`). These parse green, and the lint rule alone refuses them.
14
-
3. A page's `interfaceConfig.filterBy` and a lookup field's `lookupFilters` parse green and also lint green, because the lint's filter walk descends neither key. Neither door refuses them at publish, so the entry now tells the upgrader to sweep both by hand. Two controls back the `filterBy` reading. A malformed `filterBy` value is refused at `interfaceConfig.filterBy.0.value`, so the slot is parsed. The same rule under `interfaceConfig.filter` is refused by the lint, so the key name is what decides. `lookupFilters` takes `gt`, `gte`, `lt` and `lte` as its only ordering spellings (`greater_than`, `after`, `between`, `>` and `$gt` are refused at `lookupFilters.0.operator`), and the by-hand clause names those four.
15
14
-**Two more false sentences are narrowed.**
16
15
- The `replacement` called the dashboard date-filter positions "the only place any layer ever resolved" a preset name. An analytics query's `timeDimensions[].dateRange` accepts and resolves the names too.
17
16
- The `reason` said equality and membership "are NOT judged". That holds for the schema door only. The lint rule refuses a preset in an equality or membership position on a field it can resolve to a declared `date` or `datetime`, while `this_quarter` on a `select` field stays green. Where the filter binds to no object, such as a widget whose `dataset` names no dataset, that arm does not fire.
18
17
-**Reach.** Counted over `dist/index.js`, `dist/index.mjs`, `dist/browser/index.js` and `dist/browser/index.mjs`:
19
18
- The removed carrier list `page filter, component filter, rollup filter` and each of the four other removed claims read 4 before and 0 after.
20
-
- Each of nine sentences unique to the corrected text reads 0 before and 4 after.
21
19
- The unchanged dark control `compared false against every row: HTTP 200` reads 4 on both sides.
`filter-preset-comparand` now judges a list page's `interfaceConfig.filterBy` and a lookup field's `lookupFilters`, consumed filter carriers the shared filter walk never entered (#19791).
6
+
7
+
Both carriers are rule arrays (`{ field, operator, value }`) whose values reach the engine's `where` verbatim, and neither schema carries a preset check. So `{ field: 'close_date', operator: 'gt', value: 'last_30_days' }` in either one parsed green and linted green, then the engine refused it at query time (`INVALID_FILTER` / 400). The same rule on a component `dataSource.filter` or a view `filter` was already refused. `filterBy` and `lookupFilters` join `FILTER_KEYS`, so `os lint`, `os validate` and the runtime publish gate (for `page` and `object` writes) now refuse it where it is written. Each finding carries its path (`pages[0].interfaceConfig.filterBy[0].value`, `objects[2].fields.account.lookupFilters[0].value`).
8
+
9
+
-**Which object a condition addresses.** The field-typed arm, which refuses a preset under equality or membership on a `date` / `datetime` field, binds `filterBy` to `interfaceConfig.source`. Without a `source` it falls back to the page's `object`. It binds `lookupFilters` to the field's `reference` and never to the object that owns the field, because the picker queries the referenced object. A `relatedListFilter` on the same field still binds to the owner.
10
+
-**`filter-token-unknown` reaches the same two carriers.** An unresolvable placeholder such as `{current_user}` in `filterBy` or `lookupFilters` is now reported, as it already is in a view's `filter`. `{current_user_id}` and the date macros stay clean.
11
+
-**What you do:** in a `filterBy` or `lookupFilters` rule, replace a preset name with the `{date-macro}` window the message names (`{ operator: 'gte', value: '{30_days_ago}' }`) or with an ISO date.
The shipped ADR-0087 semantic entry `filter-preset-ordering-comparand-refused` drops, from its `surface` and its `reason`, the text that said a page's `interfaceConfig.filterBy` and a lookup field's `lookupFilters` are refused by neither door at publish, and drops, from its `acceptanceCriteria`, the by-hand search it prescribed for those two keys. The `@objectstack/lint``filter-preset-comparand` rule now walks both keys, so `os lint`, `os validate` and the runtime publish gate (on `page` and `object` writes) refuse `{ field: 'close_date', operator: 'gt', value: 'last_30_days' }` in either one, while the schema parse still accepts it.
0 commit comments