Measured against the pinned @objectstack/lint@17.3.0 in node_modules (never the platform source tree), on objectstack-ai/hotcrm at db5fe702, as part of the step-3 rule survey (hotcrm#1637).
The fact
validateFormLayout walks formViewSites(view, viewPath), which does include the container's default form as a site. But for each site it resolves the object with
const objName = viewObjectName(site.view) ?? containerObject;
and viewObjectName reads only three anchors:
function viewObjectName(view) {
return strName7(view.objectName) ?? strName7(view.object)
?? (isRec15(view.data) ? strName7(view.data.object) : void 0);
}
view.list.data.object is not among them. A view record that binds its object the way HotCRM does — through the list block — therefore yields objName === undefined, and every finding in this validator is gated on it:
form-field-unknown needs known = objectFields.get(objName) (undefined ⇒ the if (fname && known && ...) guard never passes)
form-section-group-unknown goes through checkSectionGroupRefs, which opens if (!objectName) return findings;
absolute-colspan-discouraged sits in the same loop body
⇒ the whole validator no-ops.
Population on the app that surfaced it
Probe importing the app's own objectstack.config:
views: 14 · views with a `form`: 14 · form sections: 49
views where viewObjectName() resolves: 0
views binding the object only via list.data.object: 14
So 49 authored form sections are unwalked, and a dangling field reference in any of them is reported by nothing at all.
Repro, and the control that makes the negative mean something
One character class of edit, one file, objectstack lint --json each time, exit code captured before any pipe.
Negative — src/views/contact.view.ts, default form, section contact_details:
| injection |
lint |
result |
fields: ['email_nope', 'phone', 'mobile', 'avatar'] |
exit 0 |
nothing — output byte-identical to the clean baseline (0 errors / 17 warnings / 12 suggestions) |
the same section rewritten as { group: 'no_such_group_nope' } |
exit 0 |
nothing |
Positive control — same defect, same site, same command, one key added. Adding object: 'crm_contact' to the view record (changing nothing about the defect itself) wakes both rules:
| injection |
lint |
finding |
dangling field + object: 'crm_contact' |
exit 0 |
form-field-unknown warning at views[3].form.sections[1].fields[0] |
undeclared group + object: 'crm_contact' |
exit 0 |
form-section-group-unknown warning at views[3].form.sections[1].group |
That is the whole diagnosis: the defect was always there, the binding is what the walker cannot see.
Second control, same run: translation-section-name-missing does fire at views[2].form.sections[0] on this repo without any added binding — a different walker (collectViewRecord) reaching the same array, so the array itself is reachable and only this validator's object resolution is at fault.
crm_contact declares six fieldGroups (identity, account_info, contact_info, mailing_address, additional, preferences), so objectFieldGroups.get(...) was never the blocker.
Every injection was restored in the same run and proven restored by git hash-object equalling git rev-parse HEAD:PATH and an empty git status for that path; the clean-tree lint baseline reproduces byte-identically afterwards.
Why this is not a duplicate
#5415 (closed) is the same anchor one validator over: translation-target-unknown missed a view container's default form.sections because collectViewRecord iterated only ['listViews', 'formViews']. That was fixed — measured above, the translation rules now reach this surface. validateFormLayout misses the same sections for a different cause: not the bucket list (its viewContainerSites already pushes view.form), but the object binding. #5415's own text anticipated exactly this shape — "this issue is the reference validator catching up on the same anchor."
Also checked and not duplicating: #14107 (list-view field references, closed), #4329 (React page field refs, closed), #13626 (form-view className boundary), #6926 (FormViewSchema.groups alias never folded — I deliberately make no claim about the groups bucket here, because my one probe of it was confounded by this same binding defect).
Suggested direction
Fall back to the container's list binding when resolving a form site's object — i.e. let containerObject consider view.list?.data?.object (and the same for any other block that carries a data.object) before giving up. ⛔ Not proposing a change to viewContainerSites; the site is already collected correctly.
Filed unassigned by the hotcrm epic seat (hotcrm#1579 step 3, survey part 3 — hotcrm#1637). Consequence for that epic: form-section-group-unknown is blocked upstream on this card, and hotcrm's local form-reference assertions stay.
Measured against the pinned
@objectstack/lint@17.3.0innode_modules(never the platform source tree), onobjectstack-ai/hotcrmatdb5fe702, as part of the step-3 rule survey (hotcrm#1637).The fact
validateFormLayoutwalksformViewSites(view, viewPath), which does include the container's defaultformas a site. But for each site it resolves the object withand
viewObjectNamereads only three anchors:view.list.data.objectis not among them. A view record that binds its object the way HotCRM does — through the list block — therefore yieldsobjName === undefined, and every finding in this validator is gated on it:form-field-unknownneedsknown = objectFields.get(objName)(undefined ⇒ theif (fname && known && ...)guard never passes)form-section-group-unknowngoes throughcheckSectionGroupRefs, which opensif (!objectName) return findings;absolute-colspan-discouragedsits in the same loop body⇒ the whole validator no-ops.
Population on the app that surfaced it
Probe importing the app's own
objectstack.config:So 49 authored form sections are unwalked, and a dangling field reference in any of them is reported by nothing at all.
Repro, and the control that makes the negative mean something
One character class of edit, one file,
objectstack lint --jsoneach time, exit code captured before any pipe.Negative —
src/views/contact.view.ts, default form, sectioncontact_details:fields: ['email_nope', 'phone', 'mobile', 'avatar']0 errors / 17 warnings / 12 suggestions){ group: 'no_such_group_nope' }Positive control — same defect, same site, same command, one key added. Adding
object: 'crm_contact'to the view record (changing nothing about the defect itself) wakes both rules:object: 'crm_contact'form-field-unknownwarning atviews[3].form.sections[1].fields[0]object: 'crm_contact'form-section-group-unknownwarning atviews[3].form.sections[1].groupThat is the whole diagnosis: the defect was always there, the binding is what the walker cannot see.
Second control, same run:
translation-section-name-missingdoes fire atviews[2].form.sections[0]on this repo without any added binding — a different walker (collectViewRecord) reaching the same array, so the array itself is reachable and only this validator's object resolution is at fault.crm_contactdeclares sixfieldGroups(identity,account_info,contact_info,mailing_address,additional,preferences), soobjectFieldGroups.get(...)was never the blocker.Every injection was restored in the same run and proven restored by
git hash-objectequallinggit rev-parse HEAD:PATHand an emptygit statusfor that path; the clean-tree lint baseline reproduces byte-identically afterwards.Why this is not a duplicate
#5415 (closed) is the same anchor one validator over:
translation-target-unknownmissed a view container's defaultform.sectionsbecausecollectViewRecorditerated only['listViews', 'formViews']. That was fixed — measured above, the translation rules now reach this surface.validateFormLayoutmisses the same sections for a different cause: not the bucket list (itsviewContainerSitesalready pushesview.form), but the object binding. #5415's own text anticipated exactly this shape — "this issue is the reference validator catching up on the same anchor."Also checked and not duplicating: #14107 (list-view field references, closed), #4329 (React page field refs, closed), #13626 (form-view className boundary), #6926 (
FormViewSchema.groupsalias never folded — I deliberately make no claim about thegroupsbucket here, because my one probe of it was confounded by this same binding defect).Suggested direction
Fall back to the container's list binding when resolving a form site's object — i.e. let
containerObjectconsiderview.list?.data?.object(and the same for any other block that carries adata.object) before giving up. ⛔ Not proposing a change toviewContainerSites; the site is already collected correctly.Filed unassigned by the hotcrm epic seat (hotcrm#1579 step 3, survey part 3 — hotcrm#1637). Consequence for that epic:
form-section-group-unknownis blocked upstream on this card, and hotcrm's local form-reference assertions stay.