Skip to content

[Decision] Should the published ComponentMeta.icon key be retired? Three first-party zeros say dead — but WidgetRegistry is a first-party PRODUCER whose consumer is expected to be external #7609

Description

@os-sam

Filed by the domain:ui execution seat (session_01KbJQ1y1J12nZxYzFWhP8Q3, os-sam) as the home for the question #5936 deliberately fenced out, so it does not close with that card. Filed unassigned.

Not dispatchable as filed — removing a published capability is a maintainer decision.

Why this is a card and not a line in a closing thread

#5936 asked "who consumes a component registration's icon meta?" and funded a three-repo search. It came back a third zero. Its own scope text says that outcome "would make the meta itself dead surface and change the ruling to retirement".

⚠️ But "retirement" has two readings with very different blast radius, and #5936's dispatch fenced the wide one out:

⭐ Three seats have now independently gone looking for this key's consumer, and #5936 is the second time that search was funded. Without a recorded decision there will be a fourth. That is the cost this card exists to stop.

The measurement — dead in every first-party population

population reading controls that fired
objectui zero, re-derived over all five meta-bearing registry read paths (not just getMeta(...).icon) getMeta( 51 occurrences · -i icon 4898 lines
objectstack zero @ daacc1071
cloud zero @ 9b6abe0f2fd5 — structural: no component-registration surface at all, and no @object-ui/* dependency in any of its 19 package.json files getMeta 276 · -i icon 412

getMeta()'s four first-party callers read .labelling, .namespace, .isContainer, .inputs and .deprecated. getPublicConfigs()'s one reads { type, isContainer }. getConfig(), getAllConfigs(), getNamespaceComponents() have none. None reads .icon.

⭐ The evidence that cuts the other way, and it is new

packages/core/src/registry/WidgetRegistry.ts:173 copies manifest.icon into this meta from external widget manifests; :179 does the same for the inputs' defaultValue.

⇒ This is a first-party producer pipeline built specifically to carry an icon from an outside widget into registration meta. A key with a producer path and no first-party consumer is not the same shape as a key nothing touches — it is the shape of a key whose consumer is expected to be external (the designer palette).

⚠️ Three zeros close the three known populations. They are not a proof of absence: a customer-authored app, or an unmeasured repo, could read it. That is the standard ADR-0049 retirement premise, not a proof — and here it collides with a producer path pointing outward.

The options

what it does cost
A — keep, and close the question in the record The key stays. The boundary, the three readings and the producer-path evidence are already recorded in scripts/check-lucide-icon-record-names.mjs's header (landed with PR #7604), which states what would re-open it: an actual reader, in any repo A published key with no first-party consumer stays on the surface. ⚠️ Mitigated, not eliminated: the reasoning is now where the next auditor hits it
B — retire it (ADR-0049 enforce-or-remove) Remove ComponentMeta.icon; the coupling test kept by PR #7604 goes with it automatically, since both its operands are on the key Would break the WidgetRegistry inbound path silently — external widget manifests would still declare an icon and it would land nowhere. Also a published-surface removal for every downstream host
C — keep, and make the external contract explicit A, plus naming the expected external consumer in the type's own docblock, so "no first-party reader" stops reading as "dead" Costs one docblock. Does not resolve whether the external consumer actually exists

四维分析(业务角度)

① 实际业务需求 — 有实测拉动,但方向是入站而非出站。WidgetRegistry 那条生产路径是真代码,专门把外部 widget manifest 的图标搬进注册 meta;这不是投机能力面,是一条已建成的集成。⚠️ 但"谁在读"三个仓都测不到 —— 拉动是生产侧证实、消费侧未证实。B 会静默切断已证实的那一半。

② 项目长远合理性(权重 ≥50%) — 指向 A/C。判据按本轴本义读:缩小特例与契约增生。这个键不是增生出来的特例,它有一条成建制的生产链;删掉它并不缩小契约面,而是在契约上开一个洞:manifest 照旧声明 icon,只是再也落不了地,且没有任何红灯。⛔ 更关键 —— 移除一个消费端在仓外的键,等于用"我们看不见"替换"它不存在",这正是本卡三次搜索所要终结的推理形态。保留 + 把边界写进读者会撞上的地方,是一份记录、改在它该在的地方;删除是把不确定性转成一次不可逆动作。

③ 防 AI 写错 — 指向 C > A > B。真正的结构性修法已经落地:网关头部现在带三份读数、各自对照、2026-09-04 裁定与"三个零不是不存在的证明"的告诫,并写明什么才能重开(任一仓里的真实读者)。C 再补一句:在类型自己的 docblock 上点名"消费者在仓外",让下一个审计者在类型定义处就读到,而不是先搜一圈再找到网关。⛔ B 的防错收益是假的:它消灭的是一个键,不是一个误解,而且会让"声明了却不兑现"从可发现变成不可发现。

④ 创业阶段不扩散 — 这条最像支持 B,必须诚实处理:「已发布零消费的能力不因沉没成本获得豁免」。但判据里的"零消费"在这里不成立 —— 三个零测的是第一方消费者,而本键的消费者按设计就在第一方之外。本轴管的是不要新增没有拉动的声明面;它不要求删除一个有生产链、且删除会造成静默断裂的既有键。⇒ 本轴在此不构成删除理由,只要求⛔ 不再围绕它扩建新面。

权衡与推荐:四棱不同向 —— ① 半支持保留、② 明确指向保留、③ 指向 C、④ 表面支持删除但其判据不成立。按长远合理性权重下限,②③ 合起来压过 ④ 的表面读数。

推荐 C(= A 加一句 docblock)。它一次付清长远形态:把"消费者在仓外"从一个每次都要重新发现的事实,变成类型定义上的一句话。⛔ 不推荐 B:它的唯一硬证据是"我们看不见消费者",而同一份测量里有一条指向外部的生产路径。

What is already done, so this card does not re-fund the search

⛔ Do not re-run the consumer census. It is finished and recorded, with controls, in the header of scripts/check-lucide-icon-record-names.mjs (PR #7604). This card needs a decision, not a measurement.

Related: #5936 (the search, and the narrow half) · PR #7604 (the narrow retirement + the recorded boundary) · objectstack#12931 (the cloud reading) · #5633 / PR #5932 (the gate) · #5622 (the pin this came from) · #5935 / PR #7491 (the resolver consolidation).

Activity

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

    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpm:queue

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions