Repository navigation
feat(metadata): give four declared-but-unnamed fields the consumer they should already have - #1831
Merged
Merged
Conversation
…ey should have
`field-no-consumers` (new in `@objectstack/lint@17.4.0`) fires on a declared
field no *metadata* names. All four below were already stored and editable
through the synthesized layout — a synthesized layout names nothing — so the
gap was a display gap, not a dead field. Nothing is suppressed, whitelisted,
re-severitied or deleted.
- crm_account.logo — `coverField` on the `account_gallery` view ("Branded
account cards" had a brand colour and no brand image), plus a form field in
the `profile` section beside `brand_color`, without which there was no
upload control anywhere in the app.
- crm_campaign.description — the `basic` form section, which already
enumerates that group and had omitted the markdown brief seven seed
campaigns fill.
- crm_contract.description — the `notes` form section, above `special_terms`.
Which of the two markdown bodies a reader uses is settled by
`content/docs/revenue/contracts.mdx`, which sends every free-text TERM to
Special Terms by name three times.
- crm_campaign_member.added_date — `relatedListColumns` on the `crm_campaign`
lookup. That is the one mechanism a detail-page related list actually reads
(#944); a `list` view is never consulted, and adding the field to
`highlightFields` would hoist the `basic` group's only remaining field and
shadow the group.
Docs follow the two panels that changed shape, in all three locales.
`crm_quote_line_item.line_number` and `crm_article_feedback.comment` are left
standing on purpose — each needs a product decision, stated in the PR body.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DuzfS5chho38Yx1jxx9DEj
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
This was referenced Sep 9, 2026
os-steve
marked this pull request as ready for review
September 9, 2026 23:41
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #1826
Fixes— 4 of the 6 fields are cleared, and 2 are reported standing because each needs a product decision this PR must not guess at. #1826 stays open for those two. See The two that stand below.Description
field-no-consumersis a new rule in@objectstack/lint@17.4.0. It fires on a declared field that no metadata names — "no view column, form section, page binding, flow node, dataset, widget, formula, validation, hook or action names it". Every field in this card was already stored and already editable, because these objects render through the synthesized detail/form layout and a synthesized layout names nothing. So the gap was a display gap, not a dead field, and the remedy is to give each field the consumer it should already have.⛔ Nothing here is suppressed, whitelisted, locally re-severitied or deleted.
⭐ The empirical check the card gated everything on — it HELD
The card's own words: the claim that adding a view column clears
field-no-consumersis the PM's reading of the rule's wording, to be verified on the first field before grinding through the rest.Measured on this branch's base,
965933b:965933bcrm_campaign.description, a form-section binding)The single-field probe dropped the count by exactly one and
objects[2].fields.descriptionleft the issue list. The remedy is correct for this rule. Parsed withraw_decodefrom the first{, as the card prescribes.Warning breakdown, final:
field-no-consumers12 → 8,component-props-unknown-key1 → 1 (#1216's, ⛔ untouched). Of the 8 that stand, 5 are thecrm_contact.mailing_*group and 1 iscrm_forecast.seed_key— both explicitly out of this card — and 2 are reported below.Changes Made
1.
crm_account.logo— clearedTwo bindings, because one without the other would be half a feature.
gallery.coverFieldon theaccount_galleryview. The view is literally labelled Branded account cards and had a brand colour and no brand image;logois the object's only image field.logoalso joins that view'scolumns, for the reason thecampaign_timelineview already records: the list data source projects the configured columns, and the cover binding reads off that projection.coverFitis left unstated — the schema default is the crop these cards want, and materialising a default rewrites "the author said nothing" into "the author asked for this".profilesection, besidebrand_color, full span. Without it there is no upload control anywhere in the app, so the cover could never have been filled.2.
crm_campaign.description— clearedAdded to the
basicform section oncampaign.view.ts. That section already enumerates thebasicgroup field by field and had simply omitted the markdown brief — which seven seed campaigns fill and no screen would show, and which no marketer could write.3.
crm_contract.description— cleared, and the card's question answeredAdded to the
notesform section, abovespecial_terms, in a one-column section suited to long-form text.The card asked which of the two markdown bodies a reader is meant to use, and this is answerable from the repo rather than by preference:
content/docs/revenue/contracts.mdxsends every free-text term to Special Terms by name three separate times — renewal instructions, a billing cadence the picklist will not hold, a payment arrangement the picklist will not hold — while listing Description in the Contract Information group as part of the record's identity block. They are not redundant. Description was the one field in that documented inventory no form offered, so the summary the guide tells a reader the record carries could not be written. The rationale is a comment beside the code.4.
crm_campaign_member.added_date— clearedrelatedListColumnson thecrm_campaignlookup:['crm_lead', 'crm_contact', 'added_date', 'status', 'response_date'].⭐ This is the one mechanism that actually reaches that panel, and it is measured in this repo rather than assumed —
test/view-references.test.ts(#944) records that a detail-page related list readsrelatedListColumnson the child's lookup first and only then falls back to the child'shighlightFieldsminus that lookup; the child'slistview is never consulted. So authoring acrm_campaign_memberview bundle would have satisfied the linter and shown a user nothing.The value is a superset of the fallback it replaces (Lead / Contact / Status / Response Date) plus the missing column, ordered the way the row happens: who was enrolled, when, then how they answered.
⛔ Deliberately not solved with
highlightFields.added_dateis the only field in thebasicgroup that is neither the record title nor already in the highlight strip. Hoisting it would leave the group with nothing of its own and silently vanish its heading from every detail page (field-group-shadowed) — the exact trap that object's ownfieldGroupsnote records being rescued from (#715, #582).This also settles the tension the card flagged: #1807 / PR #1814 built two elevated sub-flows so this stamp keeps being written, and the rule was reporting that nobody reads it. The stamp is now legible where a marketer reads memberships. ⛔ Nothing about the field's declaration, its
readonly: true(#1667) or its writers changed.5. Documentation
Two user-visible panels changed shape, so their pages follow — all three locales each:
content/docs/sales/accounts.mdx(+.zh-Hans/.zh-Hant) — the Branding section row named only the brand colour, and the Account Cards row described a card with no image.content/docs/marketing/campaign-members.mdx(+.zh-Hans/.zh-Hant) — the page stated the panel's columns as four, named them, and explained that a related list's columns "come from the object's highlight fields". The first is now five, and the second is superseded by the mechanism this PR uses. The load-bearing half of that sentence — a related-list panel never reads the child's list view — is kept, because it is still true and is what stops the next author authoring one.⛔
content/docs/releases/untouched. The changeset is this PR's input to the release notes.crm_quote_line_item.line_number— standingThe card's candidate was a related-list column or sort key. That candidate is wrong, and the repo says why: the field has no writer.
readonly: true, and grep finds no hook, no flow node and no action that writes it.quote_generationcreates the quote and no line items at all, so quote lines are only ever created by hand — and since objectql 17.4.0 a static readonly field is stripped from a non-system caller's INSERT too, so a rep cannot seed it either. The only values in existence are the 19 seed rows;test/seed-consistency.test.tspins those to1..nper parent, which is a pin on the fixture, not evidence of a runtime writer.⇒ A column or sort key here would render blank on every line a user creates and show a number only on demo data. That is a fabricated surface, which the card ranks below an honest standing warning.
The decision needed: give the ordinal a writer (a per-parent assigner on insert, in the shape
_line-item-price-fill.tsestablishes — and because the field isreadonlythat writer must be system-context, which puts it under AGENTS.md house rule 9, the same question #1807 settled foradded_date), or retire the ordinal and its 23 carrier sites. Filed as #1828, which also records that the twincrm_opportunity_line_item.line_numberhas the same gap and is shipped in the billing hand-off payload on every won deal — so whichever answer is chosen should cover both objects.crm_article_feedback.comment— standingThe card anticipated this one may come back as "retire the column". It comes back as a genuine product decision, and here is the measurement behind it.
The field has neither a writer nor a reader. The only things that create feedback rows are
mark_article_helpful/mark_article_not_helpful— one-click header buttons whose bodies writecrm_knowledge_article,verdictandowner_idand nothing else. A reader is never asked for a note. And itsdescriptionpromises "Optional note explaining the verdict — read by the article's author", while no surface offers the author anything of the kind; the object's own docblock places the comment as "the group's own content, one click into the row" on the feedback record's synthesized page, which is not a thing an author is ever routed to.The decision needed, and it is not settleable from this repo:
descriptionpromises — a capture step (which cannot be atype: 'script'action: those bodies collect no input, so the one-click thumb would have to become a screen flow) and an author-facing surface. That is a feature with a real UX cost: asking for a note suppresses votes, and this object exists to make two counters trustworthy.descriptionwith it.⛔ Not retired here, per the card's explicit instruction.
Testing
pnpm verifygreen end to end —validate·typecheck·lint·lint:i18n-gate·hygiene·hygiene:tokens·build·testpnpm test— 165 test files passed, 3454 passed, 1 skipped, 0 failed (unchanged from base; ⛔ no test added — AGENTS.md scope rule 3, this repo does not grow a gate farm for a platform rule)pnpm lint --json—errors: 0·warnings: 9·suggestions: 12,passed: truepnpm lint:i18n-gate— 0i18n/missing-*issues. No new locale key was needed: all four fields already carry labels in en / zh-CN / es-ES / ja-JP, every form section reused is an existing_sectionskey, andrelatedListColumnsadds no translatable surface.pnpm hygiene:tokens— authored total ~137,818 (ceiling ~140,000); business semantics ~85,291; interaction layer ~37,882. All clean, no ceiling moved..changeset/four-fields-get-the-surface-they-promised.md), written for the release-notes reader.Additional Notes
Out-of-scope findings filed rather than fixed here: #1828 (the
line_numberwriter gap, both line-item objects, plus the billing payload that ships the always-null field) and #1829 (campaign-membersdocs still describe the tracker fields and statuses #597 removed, three locales).⛔ Untouched, as the card directs: the five
crm_contact.mailing_*fields,crm_forecast.seed_key(correct by design, upstream as objectstack#17135), thecomponent-props-unknown-keywarning (#1216's), and the--strictflip itself (#1581).🤖 Generated with Claude Code
https://claude.ai/code/session_01DuzfS5chho38Yx1jxx9DEj
Generated by Claude Code