Observation from #51 (PR #57). Not urgent, and deliberately not done there — deleting another card's guard is not a rider on the card that supersedes it.
What happened
#51's dispatch said "nothing has ever checked the five view files or the app nav". That was not accurate as of main — test/views.test.ts (landed in #49) already carries a stopgap half that resolves the simple view surface and the nav pair, and its docblock names the same three upstream issues (#14106 / #14107 / #14108).
#51 shipped test/metadata-bindings.test.ts anyway, because the value was in the superset — datasets, dotted paths, platform objects, and a self-test, none of which existed. But that leaves two guards covering overlapping ground, which is the "keep two guards in step" cost both files' headers warn against.
The overlap, precisely
test/views.test.ts › describe('every non-grid lens is bound to real fields') — the two assertions:
names only fields that exist on the object it is bound to
every nav entry resolves to a view that exists on the object it names (in describe('navigation'))
Both are strict subsets of metadata-bindings.test.ts. The rest of views.test.ts is product pins (the gantt bar spanning visible_from → due_date, the timeline reading last_update_at, one colour source, nothing ranked by a count, the deliberate absence of gantt.colorField) — those outlive the platform gaps and must stay wherever they end up.
declares the binding block its type needs is also NOT covered by the new file: that is binding-block presence (#14106), a different property from reference resolution (#14107). Keep it.
Two concrete defects in the older half, which is the argument for collapsing rather than leaving both
-
Its system-column list is hand-copied and has already drifted. SYSTEM_FIELDS there lists business_unit_id, which is not a platform column, and omits owning_business_unit_id, tenant_id, user_id and deleted_at, which are. The platform ships the registry — SystemFieldName from @objectstack/spec/system — and metadata-bindings.test.ts imports it. Net effect today: a view naming business_unit_id on duly_task (which declares business_unit) passes there as a "system field".
-
It skips every dotted path by construction. Its checker opens with if (!name || name.includes('.')) return, so a joined path on a view is unchecked. Harmless today only because no view authors one.
Suggested shape
Delete the two subsumed assertions from test/views.test.ts, leave a one-line pointer to test/metadata-bindings.test.ts, and keep the product pins and the binding-block-presence check where they are. Roughly a 40-line deletion; the 402-test suite should stay green minus those two.
Worth folding into whoever removes the stopgaps when objectstack#14105 / #14107 / #14108 land — at that point metadata-bindings.test.ts goes away entirely and only the product pins should remain.
Observation from #51 (PR #57). Not urgent, and deliberately not done there — deleting another card's guard is not a rider on the card that supersedes it.
What happened
#51's dispatch said "nothing has ever checked the five view files or the app nav". That was not accurate as of
main—test/views.test.ts(landed in #49) already carries a stopgap half that resolves the simple view surface and the nav pair, and its docblock names the same three upstream issues (#14106 / #14107 / #14108).#51 shipped
test/metadata-bindings.test.tsanyway, because the value was in the superset — datasets, dotted paths, platform objects, and a self-test, none of which existed. But that leaves two guards covering overlapping ground, which is the "keep two guards in step" cost both files' headers warn against.The overlap, precisely
test/views.test.ts›describe('every non-grid lens is bound to real fields')— the two assertions:names only fields that exist on the object it is bound toevery nav entry resolves to a view that exists on the object it names(indescribe('navigation'))Both are strict subsets of
metadata-bindings.test.ts. The rest ofviews.test.tsis product pins (the gantt bar spanningvisible_from→due_date, the timeline readinglast_update_at, one colour source, nothing ranked by a count, the deliberate absence ofgantt.colorField) — those outlive the platform gaps and must stay wherever they end up.declares the binding block its type needsis also NOT covered by the new file: that is binding-block presence (#14106), a different property from reference resolution (#14107). Keep it.Two concrete defects in the older half, which is the argument for collapsing rather than leaving both
Its system-column list is hand-copied and has already drifted.
SYSTEM_FIELDSthere listsbusiness_unit_id, which is not a platform column, and omitsowning_business_unit_id,tenant_id,user_idanddeleted_at, which are. The platform ships the registry —SystemFieldNamefrom@objectstack/spec/system— andmetadata-bindings.test.tsimports it. Net effect today: a view namingbusiness_unit_idonduly_task(which declaresbusiness_unit) passes there as a "system field".It skips every dotted path by construction. Its checker opens with
if (!name || name.includes('.')) return, so a joined path on a view is unchecked. Harmless today only because no view authors one.Suggested shape
Delete the two subsumed assertions from
test/views.test.ts, leave a one-line pointer totest/metadata-bindings.test.ts, and keep the product pins and the binding-block-presence check where they are. Roughly a 40-line deletion; the 402-test suite should stay green minus those two.Worth folding into whoever removes the stopgaps when objectstack#14105 / #14107 / #14108 land — at that point
metadata-bindings.test.tsgoes away entirely and only the product pins should remain.