|
| 1 | +--- |
| 2 | +"@objectstack/spec": minor |
| 3 | +--- |
| 4 | + |
| 5 | +feat(spec): retire the `element:filter` element at element grain (#9220, ADR-0049) |
| 6 | + |
| 7 | +**BREAKING** accept-set narrowing, landing after the v17.0.0 cut (the lockstep |
| 8 | +launch-window convention ships it as `minor`; the migration prescription is |
| 9 | +registered under protocol major 18, where `os migrate meta` users will look). |
| 10 | + |
| 11 | +`element:filter` never had a renderer or reader anywhere. Measured at |
| 12 | +retirement (objectstack `2f65b1b42`, objectui `5ffcc14`; cloud per the origin |
| 13 | +card's recorded sweep): objectui registers no renderer for it — its |
| 14 | +`renderers/basic/elements.tsx` header deferred the element to "owning plugins" |
| 15 | +that never materialized — Studio's designer palette carries it as a no-renderer |
| 16 | +`PALETTE_EXCLUSIONS` entry ("list surfaces own filtering (userFilters / filter |
| 17 | +builder)"), and the 2026-06 page-liveness audit recorded it rendering "Unknown |
| 18 | +component type". Every one of its six authorable keys — `object`, `fields`, |
| 19 | +`targetVariable`, `layout`, `showSearch`, `aria` — was a capability claim |
| 20 | +nothing kept: an author (human or AI) who configured a filter element got a |
| 21 | +success receipt for a component that renders nothing (the ADR-0078 shape). |
| 22 | +\#9198 retired `targetVariable` per-key on the two input elements and recorded |
| 23 | +this wider finding; per-key retirement would have been the wrong grain here, so |
| 24 | +the whole element retires at once. |
| 25 | + |
| 26 | +**What is refused:** any authored key on `element:filter` `properties`. All six |
| 27 | +keys are `retiredKey()` tombstones — refused at `tsc` (typed `never`) and at |
| 28 | +the parse, message carrying the element-grain prescription. The |
| 29 | +`ComponentPropsMap` row deliberately STAYS so the #5068 props gate keeps |
| 30 | +dispatching on the type and refusing loudly — deleting the row would demote the |
| 31 | +type to an unregistered custom string the gate deliberately skips. |
| 32 | + |
| 33 | +**What stays accepted:** a bare `element:filter` node with empty `properties` |
| 34 | +(the migrated shape) still parses at the node level — `PageComponentSchema.type` |
| 35 | +is an open union, so a node-level refusal is not expressible; the node was |
| 36 | +always inert and stays inert. `element:filter` is removed from the |
| 37 | +`PageComponentType` enum (de-advertisement — docs, palette derivations, and the |
| 38 | +authorable vocabulary), which changes no parse outcome. Filtering on list |
| 39 | +surfaces is unchanged and was never this element's: use a view's `userFilters` |
| 40 | +quick-filter bar or the list toolbar's filter builder. |
| 41 | + |
| 42 | +The retirement kit: |
| 43 | + |
| 44 | +- tombstones at the schema (`packages/spec/src/ui/component.zod.ts`), enum |
| 45 | + removal at `packages/spec/src/ui/page.zod.ts` |
| 46 | +- ADR-0087 registration: retired-key entries `ui/ElementFilterProps:object` / |
| 47 | + `:fields` / `:targetVariable` / `:layout` / `:showSearch` / `:aria` and the |
| 48 | + D2 conversion `element-filter-removed` (protocol 18), wired into the step-18 |
| 49 | + chain — `os migrate meta --from 17` strips the keys from old sources (pure |
| 50 | + lossless deletes; none ever had an effect to lose) and leaves the bare node |
| 51 | +- the #9198 conversion's negative-control fixture moves from `element:filter` |
| 52 | + to an open-union custom type (same assertion — the strip dispatches on the |
| 53 | + component type, not the key name) |
| 54 | +- pin tests (`component.test.ts` — refusal carries the prescription; the |
| 55 | + bare migrated node parses clean and materializes nothing) |
| 56 | +- generated baselines/docs follow the schema (`authorable-surface/`, |
| 57 | + `json-schema.manifest/`, spec-changes, upgrade guide, reference docs) |
| 58 | + |
| 59 | +## FROM → TO |
| 60 | + |
| 61 | +```ts |
| 62 | +// before — parsed green; nothing anywhere rendered it |
| 63 | +{ |
| 64 | + type: 'element:filter', |
| 65 | + properties: { object: 'order', fields: ['status'], layout: 'sidebar' }, |
| 66 | +} |
| 67 | + |
| 68 | +// after — delete the component; filtering belongs to the list surface |
| 69 | +// (view.userFilters / the list toolbar's filter builder) |
| 70 | +``` |
| 71 | + |
| 72 | +<!-- adr-0087: registered element-filter-removed --> |
0 commit comments