Skip to content

Give the six declared-but-unnamed fields the consumer they should already have — clear 6 of the 12 field-no-consumers warnings blocking #1581 (epic #1579) #1826

Description

@os-steve

Filed by the epic PM (session_01DuzfS5chho38Yx1jxx9DEj) on the maintainer's instruction, 2026-09-09, verbatim: 「继续处理所有相关任务」. pm:epic reserves it for the epic PM.

#1581 is gated on this. Its acceptance needs errors: 0 and warnings: 0. Errors are 0 since the 17.4.0 migration (#1807 / PR #1814, main 965933b). 13 warnings stand — and until they reach 0, os lint --strict cannot flip, which in turn blocks six family cards (#1582–#1587). This card takes 6 of the 12.

Where the 12 came from

field-no-consumers is a new rule in @objectstack/lint@17.4.0. It fires on a declared field that no metadata names — its own wording: "no view column, form section, page binding, flow node, dataset, widget, formula, validation, hook or action names it."

⭐ The reading that governs all twelve, and it is not the obvious one. Every one of the 12 carries a group, every group is a declared fieldGroup, and each object renders through the synthesized detail/form layout rather than an authored *.page.ts. A synthesized layout names nothing. ⇒ "no consumer" here means "not named in metadata", ⛔ not "unreachable by a user" — all twelve are on screen and editable in the product today.

⛔ That is why none of these is a deletion. The per-field analysis is written up in PR #1814's body ("The 12 field-no-consumers warnings — per-field disposition"); ⛔ do not re-derive it, read it.

The six in this card

# field carriers the consumer that is missing
1 crm_account.logo 4 (locale labels only) Uploaded brand image in the branding group beside brand_color. No list column shows it; account_detail.page.ts does not bind it. Candidate: the account detail header.
2 crm_campaign.description 11 Markdown body on the basic group; seven seed campaigns fill it. On no view column, and no campaign detail page exists.
3 crm_campaign_member.added_date 57 (2 flow nodes + 4 labels + 51 seed values) ⭐ The sharpest — and it is in direct tension with the migration that just landed. #1807 requires this stamp to keep being written, and PR #1814 built two elevated sub-flows so it can be. The rule reports nobody reads it — because writers are carriers, not consumers. ⛔ Removing it would contradict a landed card. Candidate: a view column or detail binding on crm_campaign_member — makes the audit stamp legible and settles the rule in one move.
4 crm_contract.description 9 Same shape as #2. ⚠️ crm_contract also declares special_terms, which is consumed — so which of the two markdown bodies a reader is meant to use is a real question, not an accident. Answer it or say you could not.
5 crm_quote_line_item.line_number 23 (19 seed rows carry it) readonly ordinal in the basic group. Nothing orders or displays by it — the line-item related list uses it as neither sort key nor column, which is probably the actual defect.
6 crm_article_feedback.comment 4 (locale labels only) ⭐ Its own description states a consumer that does not exist: "Optional note explaining the verdict — read by the article's author." Nothing surfaces it to the author. ⇒ Either build that surface, or the column collects text nobody reads and the description is a false promise. This one may legitimately come back as "retire the column" — say which, with the reason.

⛔ How to do it, and how NOT to

  • ⛔ Never suppress, whitelist, locally re-severity, or delete a field to shrink the count. That is the gate farm this epic exists to remove, and it would make Turn on os lint --strict in this repo's verify chain and prove the gate reds (epic #1579, step 2b — the half that needs a release) #1581's flip meaningless.
  • ⭐ Add the consumer the field should already have — a view column, a detail/form binding, a sort key. The candidates above are the PM's reading of the dev's analysis, ⛔ not a specification: if the obvious consumer is wrong for a field, say so and propose the right one.
  • ⚠️ Where the right consumer is a genuine product decision you cannot settle from the repo, STOP and report it rather than inventing UI to silence a linter. A field left warning with a stated reason is a better outcome than a fabricated surface. ⭐ "I could not settle 2 of 6" is an acceptable result; a guess dressed as a decision is not.
  • ⚠️ Every change here is user-visible product surface. Follow this repo's authoring conventions and i18n discipline (a new column or binding needs its locale labels in every shipped locale).

Acceptance

  • Measure pnpm lint --json before and after on your own base SHA and report both triples. ⭐ The claim "adding a view column clears field-no-consumers" is the PM's reading of the rule's wording — ⛔ verify it empirically on the first field you fix and say whether it held. If it does not clear the rule, STOP and report: the remedy is then wrong for all six.
  • pnpm verify green end to end.
  • Every one of the six is either cleared (with the consumer named) or reported standing (with the reason and what decision it needs).
  • Changeset: user-visible metadata changes here, so ⛔ not empty frontmatter — write it for the release-notes reader.

⛔ Not in this card

Refs: #1579 (epic) · #1581 (the gate this unblocks) · #1807 / PR #1814 (where the 12 were measured and dispositioned) · #1667 (added_date ruled readonly) · objectstack#17135.

Activity

  1. added
    pm:epicParent delegated to a dedicated epic PM — other PMs never dispatch into its subtree
    on Sep 9, 2026
  2. self-assigned this
    on Sep 9, 2026
  3. os-steve commented on Sep 9, 2026

    @os-steve
    CollaboratorAuthor

    Claim: session_01DuzfS5chho38Yx1jxx9DEj → branch claude/issue-1826-field-consumers-six

    Dispatched by the epic PM for #1579. The dev inherits this claim — ⛔ do not post a second Claim:, ⛔ do not touch the assignee field.

    Base off main at 965933b or later. ⚠️ 965933b is where PR #1814 (the 17.4.0 migration) landed; anything older still carries the pre-migration refreshInterval carrier and the old readonly semantics, and the 13 warnings will not measure the same. ⛔ Do not branch from claude/issue-1581-lint-strict-flip — it is polluted by #1806's superseded bump commits.

    Model: Opus, on the maintainer's ruling 「没有 fable 派 opus」.
    Deliverable: a draft PR. 「等总监复审」 — ⛔ do not mark ready for review, ⛔ do not enable auto-merge. The PR stops at draft and waits for the director.


    Generated by Claude Code

  4. claude commented on Sep 9, 2026

    @claude
    Contributor

    os-dev-report

    {
      "issue": 1826,
      "status": "needs_decision",
      "branch": "claude/issue-1826-field-consumers-six",
      "pr": "https://github.com/objectstack-ai/hotcrm/pull/1831",
      "premise_still_valid": true,
      "summary": "The card's gating premise HELD and is verified: a metadata binding does clear field-no-consumers. 4 of the 6 fields are cleared with the consumer named; 2 are reported standing because each needs a product decision, which the card named as an acceptable result. pnpm verify is green end to end and the PR is a draft awaiting the director. Base 965933b (= origin/main tip, where PR #1814 landed). One per-field candidate in the card body was falsified: crm_quote_line_item.line_number cannot take a view column or sort key, because the field has NO writer at all — a column would render blank on every line a user creates and carry a number only on the 19 demo seed rows. crm_article_feedback.comment has neither a writer nor a reader: the two feedback actions are one-click header buttons that never ask for a note, so its description ('read by the article's author') promises a surface that does not exist on either side. Nothing was suppressed, whitelisted, re-severitied or deleted. The #1216 component-props-unknown-key warning and the mailing_*/seed_key groups were not touched.",
      "tests": "pnpm verify GREEN end to end under the container's shared verify lock (os-verify-lock VERDICT command-exit 0, held 161s). pnpm test: 165 test files passed, 3454 passed, 1 skipped, 0 failed — unchanged from base; no test added, per AGENTS.md scope rule 3 (this repo does not grow a gate farm for a platform rule). pnpm lint --json triples, parsed with raw_decode from the first '{': BASE 965933b = errors 0 / warnings 13 / suggestions 12. AFTER-ONE-FIELD probe (crm_campaign.description added to the basic form section) = errors 0 / warnings 12 / suggestions 12, and objects[2].fields.description left the issue list — the empirical check the card gated on, and it held. FINAL = errors 0 / warnings 9 / suggestions 12, passed true; field-no-consumers 12 -> 8, component-props-unknown-key 1 -> 1. pnpm lint:i18n-gate: 0 i18n/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 _sections key, relatedListColumns adds no translatable surface). pnpm hygiene + hygiene:tokens clean, no ceiling moved (authored total ~137,818 of ~140,000). No ablation applicable: this card adds metadata bindings, it builds no gate.",
      "mcp_calls": "4 — two targeted search_issues (duplicate check before filing) and two issue creates. CHANNEL SWITCH DECLARED: the REST /search/issues endpoint is blocked in this container ('sessions are bound to their configured repositories'), including for the control probe, so the duplicate check fell back to MCP search_issues as the rules prescribe. All reads (issue body, comments, PR #1814 body, PR read-back) went over repo-scoped REST at 0 MCP cost; the PR create and this report comment went over repo-scoped REST too.",
      "open_questions": [
        {
          "question": "crm_quote_line_item.line_number — the field has no writer in the app at all. readonly:true, no hook, no flow node, no action writes it; quote_generation creates the quote and no line items, 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. Only the 19 seed rows carry values (test/seed-consistency.test.ts pins those 1..n per parent — a fixture pin, not a runtime writer). What should happen to the ordinal?",
          "options": [
            "A — Give it a writer: a per-parent ordinal assigner on insert, in the shape src/objects/_line-item-price-fill.ts already establishes for this pair of objects. Because the field is readonly the writer must be system-context, which puts it under AGENTS.md house rule 9 (elevate as little as possible) — the same question #1807 settled for crm_campaign_member.added_date. Cost: a new hook plus the elevation review; then a related-list column becomes truthful and clears the warning.",
              "B — Retire the ordinal and its 23 carrier sites (4 locale labels + 19 seed values), and drop the key from billing-handoff's LINE_ITEM_FIELDS and the three billing-handoff doc pages. Cost: the billing payload loses a line ordinal it never really carried.",
            "C — Add the view column anyway. REJECTED here: it renders blank on every line a user creates and shows a number only on demo data, which is the fabricated surface the card ranks below an honest standing warning."
          ],
          "recommendation": "A, because the twin object makes it load-bearing rather than cosmetic: crm_opportunity_line_item.line_number has the identical gap AND billing-handoff.flow.ts ships it in the billing payload on every won deal, so the app currently hands the billing system a field that is null on every non-seed row. Filed as #1828 with that measurement. Whichever answer is chosen should cover both line-item objects, since they deliberately mirror each other."
        },
        {
          "question": "crm_article_feedback.comment — the column has neither a writer nor a reader. The only creators of feedback rows are mark_article_helpful / mark_article_not_helpful, one-click header buttons whose bodies write crm_knowledge_article, verdict and owner_id and nothing else, so a reader is never asked for a note; and its own description promises 'Optional note explaining the verdict — read by the article's author' while no surface routes an author to it. Build the promise, or retire it?",
          "options": [
            "A — Build what the description promises: a capture step plus an author-facing surface. Note the capture cannot be a type:'script' action — those bodies collect no input — so the one-click thumb would have to become a screen flow, and the author surface needs somewhere on the knowledge article to read the notes.",
            "B — Retire the column and its 4 carrier sites, and delete the false promise in the description with it."
          ],
          "recommendation": "No recommendation — this is a genuine product decision and I decline to dress a guess as one, per the card. The trade-off to weigh: this object exists to make helpful_count / not_helpful_count trustworthy (its docblock is explicit that a two-valued verdict was chosen deliberately, and that 'a vocabulary nothing reads would recreate the very defect it is fixing'). Interposing a note prompt between the reader and the thumb suppresses votes, which is a direct cost to the counters the object was built for. If the article author is not actually asking for prose, B is the honest answer and matches the object's own stated design philosophy. Not retired here, per the card's explicit instruction."
        }
      ],
      "out_of_scope_findings": [
        "filed as #1828: crm_opportunity_line_item.line_number has no writer either, yet billing-handoff.flow.ts ships it in LINE_ITEM_FIELDS — the billing payload carries a field that is null on every line item created in the product, non-null only on demo seed rows, and content/docs/revenue/billing-handoff.mdx documents it with a worked example value.",
        "filed as #1829: content/docs/marketing/campaign-members.mdx and its two Chinese siblings still document First Opened / First Clicked fields and Opened / Clicked / Bounced statuses that #597 removed from crm_campaign_member — and then derive Open rate and Click rate from those phantom statuses. #961 corrected a different paragraph on the same page without reaching these.",
        "not filed, noted only: src/views/account.view.ts's file docblock lists 'gallery — branded account cards with brand_color highlights', which described the cards before this PR gave them a logo cover; corrected in place as part of this change, not a separate card."
      ]
    }

    Generated by Claude Code

  5. removed
    pm:epicParent delegated to a dedicated epic PM — other PMs never dispatch into its subtree
    on Oct 8, 2026
  6. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    Contributor

    Ruling: batch hotcrm-R74 item 1 · letter A · maintainer 「同意」 2026-10-08T03:06Z

    repo:hotcrm seat, session_012zh91QzFgePbkmuHnugLN3. The maintainer answered 「同意」 in this session's chat to the batch presented there with the recommendations 1A 2A 3A 4B. Item 1 is this card's last open question: crm_article_feedback.comment.

    The decision as presented: keep the field and close this card (A). The fallback was B, retire the field; C was to build a guided capture step after the vote.

    The premise was refreshed before presenting. The 2026-09-09 report said the field had "neither a writer nor a reader". On origin/main c967803 that no longer holds:

    Disposition of all six fields, so the card closes complete:

    Release: the epic-PM claim 5603053957 (session session_01DuzfS5chho38Yx1jxx9DEj, assignee os-steve) is released with this close. Source: the maintainer's 「同意」 above, in this session's chat; epic #1579 is dissolved by item 4 of the same batch. Destination: closed completed; pm:epic and the assignee are removed in the same act.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions