diff --git a/.changeset/dead-form-data-provider.md b/.changeset/dead-form-data-provider.md new file mode 100644 index 00000000..12428c9c --- /dev/null +++ b/.changeset/dead-form-data-provider.md @@ -0,0 +1,13 @@ +--- +'hotcrm': patch +--- + +Remove the `form.data` provider from all twelve view files. A form binds to its +object and record through the route context, so the block never wired anything — +the platform's own `liveness-dead-property` rule flagged every one of them. +Verified before removal, not taken on the validator's word: with the blocks gone, +a lead record's edit form still binds on 16.1.0 (8 inputs, 7 populated from the +record). Adds two guards — no view may reintroduce `form.data`, and every view +must still resolve its object from the list provider now that the `form.data` +fallback is gone. Clears the last 12 validate warnings; `pnpm verify` goes from +15 warnings to 3 (all pre-existing). diff --git a/src/views/account.view.ts b/src/views/account.view.ts index a6e204df..91f80cae 100644 --- a/src/views/account.view.ts +++ b/src/views/account.view.ts @@ -196,7 +196,6 @@ export const AccountViews = defineView({ form: { type: 'tabbed', - data: { provider: 'object', object: 'crm_account' }, sections: [ { label: 'Profile', diff --git a/src/views/campaign.view.ts b/src/views/campaign.view.ts index 4bbb293c..679edf95 100644 --- a/src/views/campaign.view.ts +++ b/src/views/campaign.view.ts @@ -96,7 +96,6 @@ export const CampaignViews = defineView({ form: { type: 'tabbed', - data: { provider: 'object', object: 'crm_campaign' }, sections: [ { label: 'Overview', diff --git a/src/views/case.view.ts b/src/views/case.view.ts index bdc0a443..26b88d0a 100644 --- a/src/views/case.view.ts +++ b/src/views/case.view.ts @@ -161,7 +161,6 @@ export const CaseViews = defineView({ form: { type: 'tabbed', - data: { provider: 'object', object: 'crm_case' }, sections: [ { label: 'Case', diff --git a/src/views/contact.view.ts b/src/views/contact.view.ts index 446c1af1..75204962 100644 --- a/src/views/contact.view.ts +++ b/src/views/contact.view.ts @@ -71,7 +71,6 @@ export const ContactViews = defineView({ form: { type: 'tabbed', - data: { provider: 'object', object: 'crm_contact' }, sections: [ { label: 'Identity', diff --git a/src/views/contract.view.ts b/src/views/contract.view.ts index ca7b6f41..39654360 100644 --- a/src/views/contract.view.ts +++ b/src/views/contract.view.ts @@ -91,7 +91,6 @@ export const ContractViews = defineView({ form: { type: 'tabbed', - data: { provider: 'object', object: 'crm_contract' }, sections: [ { label: 'Parties', diff --git a/src/views/forecast.view.ts b/src/views/forecast.view.ts index 1d551741..3f88caca 100644 --- a/src/views/forecast.view.ts +++ b/src/views/forecast.view.ts @@ -79,7 +79,6 @@ export const ForecastViews = defineView({ form: { type: 'simple', - data: { provider: 'object', object: 'crm_forecast' }, sections: [ { label: 'Snapshot', diff --git a/src/views/knowledge_article.view.ts b/src/views/knowledge_article.view.ts index 9ba36239..def0237d 100644 --- a/src/views/knowledge_article.view.ts +++ b/src/views/knowledge_article.view.ts @@ -90,7 +90,6 @@ export const KnowledgeArticleViews = defineView({ form: { type: 'simple', - data: { provider: 'object', object: 'crm_knowledge_article' }, sections: [ { label: 'Article', diff --git a/src/views/lead.view.ts b/src/views/lead.view.ts index 18989a36..3f5a3dc1 100644 --- a/src/views/lead.view.ts +++ b/src/views/lead.view.ts @@ -113,10 +113,6 @@ export const LeadViews = defineView({ */ form: { type: 'simple', - data: { - provider: 'object', - object: 'crm_lead', - }, sections: [ { @@ -401,10 +397,6 @@ export const LeadViews = defineView({ */ detail_form: { type: 'tabbed', - data: { - provider: 'object', - object: 'crm_lead', - }, sections: [ { label: 'General', diff --git a/src/views/opportunity.view.ts b/src/views/opportunity.view.ts index 68831fc4..a9d2ae59 100644 --- a/src/views/opportunity.view.ts +++ b/src/views/opportunity.view.ts @@ -222,7 +222,6 @@ export const OpportunityViews = defineView({ form: { type: 'tabbed', - data: { provider: 'object', object: 'crm_opportunity' }, sections: [ { label: 'Overview', diff --git a/src/views/product.view.ts b/src/views/product.view.ts index 54fe33e4..530b98a9 100644 --- a/src/views/product.view.ts +++ b/src/views/product.view.ts @@ -72,7 +72,6 @@ export const ProductViews = defineView({ form: { type: 'simple', - data: { provider: 'object', object: 'crm_product' }, sections: [ { label: 'Product Info', diff --git a/src/views/quote.view.ts b/src/views/quote.view.ts index 744d635b..7fb85eea 100644 --- a/src/views/quote.view.ts +++ b/src/views/quote.view.ts @@ -74,7 +74,6 @@ export const QuoteViews = defineView({ form: { type: 'tabbed', - data: { provider: 'object', object: 'crm_quote' }, sections: [ { label: 'Quote', diff --git a/src/views/task.view.ts b/src/views/task.view.ts index 75f0592f..873fa887 100644 --- a/src/views/task.view.ts +++ b/src/views/task.view.ts @@ -170,7 +170,6 @@ export const TaskViews = defineView({ form: { type: 'simple', - data: { provider: 'object', object: 'crm_task' }, sections: [ { label: 'Task', diff --git a/test/metadata-references.test.ts b/test/metadata-references.test.ts index 2ffece1b..b445ae35 100644 --- a/test/metadata-references.test.ts +++ b/test/metadata-references.test.ts @@ -1105,3 +1105,48 @@ describe('action labels are translated in every locale', () => { expect(bad, `action strings declared in code but not translated:\n ${bad.join('\n ')}`).toEqual([]); }); }); + +/** + * `form.data` is a data provider the form renderer never reads: a form binds to + * its object and record through the route context, so the block only *looked* + * like it was wiring the form to an object. The platform's own liveness rule + * (`liveness-dead-property`) flagged twelve of them, one per view file. + * + * Verified before removing them, rather than taken on the validator's word — + * the same validator reports false positives elsewhere (it cannot see view + * names inside the twelve grouped `views` exports, so it calls live routes + * dead). With every `form.data` deleted, opening a lead record's edit form on + * 16.1.0 still binds correctly: 8 inputs, 7 populated from the record + * (`first_name: "Mira"`, `company: "Atlas Construction"`, `status: "contacted"`, + * …), no console errors. + * + * Scope note: named forms under `formViews` (`quick_create`, + * `lead_conversion_wizard`, …) still carry `data` blocks. The liveness rule does + * NOT flag those and they have not been measured — a create form has no record + * in the route context, so the same reasoning may not hold. They are left alone + * deliberately; measure first if you plan to remove them. + */ +describe('form views do not declare a dead data provider', () => { + it('no view sets form.data', () => { + const bad: string[] = []; + for (const v of views) { + if (!v.form?.data) continue; + const objectName = v.list?.data?.object ?? v.object ?? '(unknown object)'; + bad.push(`${objectName}: form.data = ${JSON.stringify(v.form.data)}`); + } + expect( + bad, + `form.data is not consumed — a form binds via the route context:\n ${bad.join('\n ')}`, + ).toEqual([]); + }); + + it('every view still resolves its object without the form.data fallback', () => { + // objectOf() used to fall back to `form.data.object`. With those blocks gone + // the list provider is the only source, so a view that lost both would make + // every rule keyed on objectOf() silently skip it. + const unresolved = views + .filter((v) => !(v.list?.data?.object ?? v.object)) + .map((v) => v.list?.name ?? v.form?.type ?? '(unnamed view)'); + expect(unresolved, `views whose object no longer resolves:\n ${unresolved.join('\n ')}`).toEqual([]); + }); +});