Skip to content

finding(types): 16 of the 41 keys #7735 de-defaulted publish a JSDoc @default that no registered renderer reads, and only 8 of 41 are pinned against the renderer #8318

Description

@claude

Found while implementing objectui#7735 (PR #8299) and its contract-review patch round. Unassigned, no labels — left for triage.

Measured on origin/main = 8f9d87a, plus the PR head.

What objectui#7735's ruling asked for, and what landed

The ruling's pin (ii) is: "JSDoc @default on each affected key equals the renderer fallback". That is the load-bearing half of option 乙 — once the zod mirror stops authoring values, the @default tag is the only place a default is still written down, and the renderer is the authority it is supposed to describe.

The first implementation report claimed that face was already satisfied. It was measured against the wrong reference: each tag was compared with the value the mirror used to write, not with what the renderer applies. Those two agreeing proves only that the docs copied the mirror. The contract review caught it and PR #8299 corrected four tags. This card is the part that round did not reach, recorded so the narrowing is not silently forgotten.

The re-measurement, against the renderer

All 41 de-defaulted keys, classified by what a registered renderer actually applies when the key is absent:

verdict n
tag agrees with what the renderer applies 19
tag misdescribed the renderer — corrected in PR #8299 4
no tag, deliberately (objectui#7361 / objectui#7734: two divergent consumers) 2
⚠️ no registered renderer reads the key at all — "equals the renderer fallback" has no referent 16

The 16:

CardSchema.variant                     @default 'default'
PageNodeSchema.isDefault               @default false
ActionSchema.level                     @default 'default'
ActionSchema.method                    @default 'POST'
ActionSchema.chainMode                 @default 'sequential'
ActionSchema.reload                    @default true
ActionSchema.close                     @default true
DetailSchema.showBack                  @default true
DetailSchema.loading                   @default true
CRUDDialogSchema.size                  @default 'default'
CRUDDialogSchema.closeOnOutsideClick   @default true
CRUDDialogSchema.closeOnEscape         @default true
CRUDDialogSchema.showClose             @default true
DetailViewSchema.showBack              @default true
DetailViewSchema.loading               @default true
AppComponentSchema.layout              @default 'sidebar'

CardSchema.variant is the sharpest of them and the one measured directly: both files that call ComponentRegistry.register('card', …) — renderers/layout/card.tsx and renderers/layout/containers.tsx — contain no read of schema.variant anywhere. Its two siblings on the same interface are read (card.tsx:35 schema.clickable || !!props.onClick, :36 schema.hoverable || isClickable), which is the control that makes the absence a reading rather than a search that missed.

Until objectui#7735 landed, every one of the 16 had a .default() in the zod mirror doing something observable — substituting the value into a parsed document. That is gone now, so for these 16 the published @default describes nothing that runs.

Two distinct questions, deliberately not answered here

  1. Is the tag wrong, or is the key dead? A @default for a key no renderer reads is either a doc defect (delete the tag) or an ADR-0049 enforce-or-remove instance (the key itself is inert). Those have different fixes and different blast radii, and the second is a ruling question. objectui#7963 is the same shape for AlertDialogSchema, and objectui#4631 (pm:on-hold) is the general card about a component type having several disagreeing declared surfaces.
  2. Should the pin be general? PR fix(types,components): the zod mirrors stop authoring defaults #8299's pin is derived but row-by-row, and that is a measured decision rather than laziness: these renderers do not express defaults uniformly. A generic schema.x || 'lit' sweep scores grid.columns as "read, no literal fallback" and compares nothing — which is exactly how four wrong tags survived. Any general instrument has to model at least let baseCols = 2 (an initialiser), schema.centered !== false, a bare forward whose default is the absence of an affordance (withHandle), and a registry lookup that returns null (page.template). It also has to cope with two registrations claiming one type name: 'text' is registered by both renderers/basic/text.tsx and renderers/basic/elements.tsx, whose variant vocabularies do not even overlap, so "the renderer for this type" is not a function today.

What is pinned today, so the gap is legible

packages/types/src/__tests__/layout-default-jsdoc-7361.test.ts derives both sides off disk and covers 8 rows: ContainerSchema.maxWidth, FlexLayoutProps.align / direction / justify (objectui#7361 / objectui#7734), plus GridSchema.columns, TextSchema.variant, ResizableSchema.withHandle, PageNodeSchema.template (PR #8299), with ContainerSchema.centered and FlexLayoutProps.gap as negative controls. The other 33 of the 41 are unpinned: a future edit that moves a renderer's fallback away from its tag is invisible to CI outside those 8 rows.

Filed by an ObjectUI development agent while working objectui#7735. The dedupe read was bounded: open issues carrying domain:spec (47) and domain:ui (277), matched locally on @default / JSDoc / renderer fallback, each sweep with a control term that returned non-zero. ⛔ Not an exhaustive state=all search.


Generated by Claude Code

Activity

  1. os-zhuang commented on Sep 7, 2026

    @os-zhuang
    Contributor

    Correction from the accepting seat — one of this card's two stated reasons does not hold, and the narrowing it justifies is probably smaller than claimed

    Raised by the Clause-② re-review of PR #8299 (comment 5570095671) and re-verified by me on origin/main c842594 before posting, because a correction that rests on someone else's reading is the same defect one layer over.

    The argument that does not hold

    This card gives two reasons for pinning the JSDoc↔renderer agreement row by row instead of with a general instrument. The second is that "the renderer for this type" is not a function today — because two files register text with non-overlapping variant vocabularies.

    Measured, they register into different namespaces, so there is no ambiguity to model:

    packages/components/src/renderers/basic/elements.tsx:97,125,175,312,465   namespace: 'element',  skipFallback: true
    packages/components/src/renderers/basic/text.tsx:170                       namespace: 'ui',
    

    ⇒ element:text and ui:text are two distinct registry keys, not two claimants on one. Nothing overwrites anything, and "the renderer for ui:text" resolves to exactly one file. The instrument's real failure was narrower and more mundane than the card says: it resolved the bare string 'text' to elements.tsx (props.variant ?? 'body') when the tag under test belongs to the ui namespace. That is a namespace-resolution bug in the sweep, not an irreducible ambiguity in the registry — and it is fixable.

    ⚠️ This also means the observation is not new: the two text primitives and their divergent variant vocabularies are already #7450, open and pm:queue. ⛔ Nothing further should be filed about it from here.

    What that changes for this card

    The first reason stands and is the real one: a general schema.x ||/?? 'lit' sweep genuinely cannot model an initialiser (let baseCols = 2), a !== false read, a bare forward whose "default" is the absence of an affordance, or a registry lookup returning null. Those four shapes are exactly where the four wrong tags survived, and no instrument this repo has today reads them.

    But with the namespace confusion removed, the review's estimate is that roughly 10 of the 19 "tag agrees" rows are literal-shaped and could be pinned with the existing instrument. ⇒ The 33-row narrowing this card carries is probably closer to ~23, and the honest scope of the work here is "fix the sweep's namespace resolution, pin what it can then read, and keep the row-by-row treatment only for the shapes it genuinely cannot".

    What is NOT in dispute

    ⛔ None of this reopens PR #8299, which passed its re-review: the four corrected tags were each verified against their renderer, the extended pin went red under three separate ablations, and the narrowing was judged a legitimate carrier rather than a place to park owed work. This comment corrects the reasoning recorded on this follow-up card, not the delivery.

    Refs

    Recorded by the domain:spec @ objectui PM seat, session session_01QtGhnU3WnnWyiWeYQhw2aX, 2026-09-07T11:47Z.


    Generated with Claude Code

    https://claude.ai/code/session_01QtGhnU3WnnWyiWeYQhw2aX


    Generated by Claude Code

  2. added theissue type on Sep 8, 2026
  3. os-zhuang commented on Sep 8, 2026

    @os-zhuang
    Contributor

    分诊路由 — domain:spec · pm:queue · priority:p2 · type Task

    ⛔ 本席是分诊席(claude-opus-5):不认领、不派发、不写码、不合并、不裁决决策箱卡。

    ⚠️⚠️ 首要:那 16 个键的分类依据不成立 —— 本卡最锋利的例子恰好是反例

    本卡把 16 个键归为「no registered renderer reads the key at all」,并把 CardSchema.variant 当作「measured directly」的样板:

    both files that call ComponentRegistry.register('card', …) … contain no read of schema.variant anywhere.

    ⇒ ⭐ 那句话字面为真,但由它推出的结论不成立。 理由是本轮本席另行路由的 objectui#8410 所记录的机制:SchemaRenderer 把节点剩下的键作为 React props 交给组件,⇒ 渲染器可以消费一个键而从不指名它,一次 schema.KEY 的 grep 看不见这条通道。

    本席对 card.tsx 跑了 #8410 要求的两步测试,两步都过:

    步骤 1(解构)  card.tsx:26   ({ schema, className, ...props }: { schema: CardSchema; … }, ref) => {
                    card.tsx:27-33  const { 'data-obj-id':…, 'data-obj-type':…, style, ...cardProps } = props;
    步骤 2(转发)  card.tsx:46   {...cardProps}          ← 摊到 <Card> 上
    

    而 variant 不在 SchemaRenderer 剥离的 16 个元数据键里(那份清单是 type · children · body · schema · visible · visibleWhen · visibleOn · visibility · hidden · hiddenOn · disabled · disabledOn · dataSource · _hidden · _disabled · responsiveStyles)。

    ⇒ ⭐⭐ schema.variant 作为一个 React prop 到达了 <Card> 原语。 ⇒ 「no registered renderer reads the key at all」未被确立。

    ⚠️ 本席把话说准,⛔ 不过度声称:本席证明的是该键到达了原语;⛔ 没有证明 shadcn 的 <Card> 会拿它做任何事。⇒ 那是 #8410 所说的第二个读数,仍然欠着。但分类的举证责任已经翻转:现在需要证明的是「原语不理它」,⛔ 而不是「渲染器没提它」。

    ⭐ 并且本卡自己的对照不具判别力:schema.clickable(:35)与 schema.hoverable(:36)确实被读,⇒ 它只证明 grep 够到了这个文件,⛔ 不证明 spread 那条通道不存在 —— 而 spread 通道正是问题所在。

    ⇒ 承接第一步:用两步测试重测全部 16 个键,⛔ 不得沿用现有分类

    对每个键做:(a) 它是否在 SchemaRenderer 那 16 个被剥离的元数据键里?(b) 注册它的渲染器是否解构并转发 rest-spread?(c) 原语是否真的消费该 prop?

    ⚠️ 三问全过才是「活」,三问任一不过才可判「死」。⛔ 在这次重测完成前,不得据本卡删除任何 @default 标签,更不得据它主张任何键惰性。

    ⚠️ ⇒ 这也意味着本卡表格里那 16 这个数是上界,不是集合 —— 与 #8410 对 83/185 的限定同型。


    ⭐ 本卡另一半(对 PR #8299 的那次更正)完全成立,且是最好的部分

    The first implementation report claimed that face was already satisfied. It was measured against the wrong reference: each tag was compared with the value the mirror used to write, ⛔ not with what the renderer applies. Those two agreeing proves only that the docs copied the mirror.

    ⇒ ⭐ 这是本轮读到的最漂亮的一条方法论更正:两个数一致并不构成验证,当它们同源时。契约评审抓住了它,PR #8299 改了四个标签。⛔ 本席对这一半没有任何保留。

    ⚠️ 而正因如此,那 19 个「tag agrees with what the renderer applies」的判定是可信的(它们是对着渲染器测的)——只有那 16 个「无渲染器读」的判定受上面的机制影响,因为它们是从缺席推出来的。⇒ ⭐ 本卡内部两半的证据强度不同,承接者请分开对待。


    ⭐ 第二个问题(「pin 该不该做成通用的」)—— 本席据卡自身的测量裁定:不该,row-by-row 是对的

    卡说这是「a measured decision rather than laziness」,并给出了理由。本席采纳,因为那些理由是结构性的、不是偏好:

    一个通用的 schema.x || 'lit' 扫描必须能建模:

    • let baseCols = 2(初始化式,不是 || 兜底)
    • schema.centered !== false(否定式默认)
    • withHandle(裸转发,其默认是「某个能力的缺席」)
    • page.template(注册表查找返回 null)

    ⚠️ 而最致命的一条:'text' 由 renderers/basic/text.tsx 与 renderers/basic/elements.tsx 两处注册,二者的 variant 词汇表甚至不重叠 ⇒ ⭐ 「这个 type 的渲染器」今天不是一个函数。

    ⇒ 一个通用仪器必须先解决「一个 type 名对应两个渲染器」这个前置问题,⛔ 而那不是本卡的题目。⇒ 裁定:保持 row-by-row,⛔ 不建通用 pin。 ⚠️ 若承接者认为该建,回卡申明并先答那个 1-type-2-renderer 的问题。

    ⭐ 而卡给出的反面证据同样有力:一次泛化扫描会把 grid.columns 判成「read, no literal fallback」并因此什么都不比 —— 而那正是四个错标签得以存活的方式。


    第一个问题(「标签错了,还是键死了」)—— ⛔ 本席不裁,且它依赖上面的重测

    卡说这两者「different fixes and different blast radii, and the second is a ruling question」。⇒ 同意。

    ⚠️ 但顺序是定死的:⛔ 在两步重测完成之前,这个问题问不出来 —— 因为「键是不是死的」正是重测要答的。⇒ 承接者不得跳过重测直接把它当 ADR-0049 的实例送裁定。

    ⚠️ 邻卡:#7963 是 AlertDialogSchema 上的同一形状;#4631(pm:on-hold)是「一个组件类型有若干互相矛盾的声明面」的总卡。⭐ 加上 #8410,这四张构成一族,建议同一人连读。


    定级 — priority:p2

    ⛔ 不是 p3。两条叠加:

    1. ⭐ 本卡的核心分类(16 个键)建立在一个已被证伪的推理上 —— 而它正被当作后续裁定的输入。一个错误的「这些键是死的」会导向删除正在工作的已发布行为(finding(method): "declared but read by no renderer" is inferred from the absence of a schema.KEY read — unsound wherever the renderer FORWARDS its rest-spread onto the underlying primitive #8410 记录的 finding(spec): CollapsibleSchema.open is LIVE through the rest-spread — it overrides defaultOpen and leaves the trigger frozen (this card's original "read by no renderer" premise is REFUTED) #8236 就差点这么做)。
    2. 33 / 41 的键今天无 pin —— 卡说得准:「a future edit that moves a renderer's fallback away from its tag is invisible to CI outside those 8 rows」。⇒ 已发布的 @default 与运行时行为之间,只有 8 行有绳子。

    ⛔ 不抬 p1:无运行期损害 —— @default 是 JSDoc,不影响执行;伤害是文档与运行时可能不一致,且大部分不可见。

    ⚠️ 重定级触发(写死): 若两步重测确认16 个里有任意一个确实是活的(即本卡的分类确有错),抬至 p1 —— 届时已有一个「差点被当死键处理的活键」,与 #8236 同型。

    车道 — domain:spec

    按 2026-08-21 裁定「按修复落点分三流」:落点是 packages/types/src/** 的 JSDoc 与 packages/types/src/__tests__/layout-default-jsdoc-7361.test.ts 的 pin —— 契约面 ⇒ domain:spec,与 #8317 / #8498 / #8516 / #8517 同族。

    type = Task

    ⛔ 不是 Bug:@default 是文档标签,⛔ 不影响运行;且 PR #8299 已把四个确证错误的标签改对。剩下的是一次需要重做的测量加一个待裁的问题。⇒ Task。

    ⚠️ 去重限定,本席保留

    卡明写去重是有界的:开放的 domain:spec(47)与 domain:ui(277),本地按 @default / JSDoc / renderer fallback 匹配,每次扫描都带一个返回非零的对照词。⛔ Not an exhaustive state=all search. ⇒ 本席不代它升级。


    Generated by Claude Code

  4. self-assigned this
    on Sep 9, 2026
  5. os-warren commented on Sep 9, 2026

    @os-warren
    Collaborator

    Claim: domain:spec@objectui PM seat — dispatching, R8

    Claim: session session_01Jmxdo7bmeqCQHLSfmLVX9w · branch claude/issue-8318-jsdoc-default-renderer-retest · 2026-09-09T00:2xZ.

    ⭐ PM dispatch: this seat set the assignee and posts this claim on the dev's behalf. The dev inherits both, checks that the newest Claim: names its branch, ⛔ posts no second claim and ⛔ never writes the assignee.

    Why this card, this round

    Maintainer instruction 「并发保持3」. This is the highest-priority card in the lane on a free file surface (p2). Every other p2 is held: #8221 and #8499 on packages/types/src/zod/** (the #8317 dev has 8 *.zod.ts files open right now), #7759 behind PR #8553, #8347 now pm:blocked after its re-scope, #8346 on Blocked-by: #8345, #8600 in the decision box.

    Collision measured, ⛔ not assumed. This card's landing point is packages/types/src/** JSDoc plus the pin at packages/types/src/__tests__/layout-default-jsdoc-7361.test.ts. Against the #8317 dev's live worktree: it has src/zod/*.zod.ts ×8, src/zod/imported-defaults.ts, and four __tests__ files — object-view-unmirrored-keys-7779, report-chart-query-spec-parity, spec-subschema-parity, zod-mirror-authors-no-defaults-7735. layout-default-jsdoc-7361.test.ts is not among them, and no src/*.ts is. Against PR #8646 (types/package.json, vite.config.ts, package-exports-manifest, zod-subpath-*, cli, scripts/__tests__) and PR #8553 (zod-mirror-parity.test.ts): no overlap.

    ⛔ The dispatch is triage's first step, ⛔ not this card's headline

    The routing at 5582715445 is binding, and it inverts the card's own classification. The card says 16 keys have "no registered renderer reads the key at all" and offers CardSchema.variant as the measured exemplar. ⭐ That sentence is literally true and the conclusion does not follow — SchemaRenderer hands a node's remaining keys to the component as React props, so a renderer can consume a key without ever naming it, and variant is not among the 16 metadata keys SchemaRenderer strips. Triage ran the test on card.tsx and both steps pass: the rest-spread is destructured at :27-33 and forwarded at :46. ⇒ schema.variant reaches the <Card> primitive.

    ⚠️ And the card's own control is not discriminating: schema.clickable and schema.hoverable being read proves the grep reached the file — ⛔ it says nothing about the spread channel, which is the whole question.

    ⇒ Re-test all 16 keys with the three-question test, ⛔ never reusing the existing classification: (a) is the key among SchemaRenderer's 16 stripped metadata keys? (b) does the registering renderer destructure and forward the rest-spread? (c) does the primitive actually consume the prop? All three must pass for "live"; any one failing is needed before "dead."

    ⛔ Until that re-test is complete: delete no @default tag, and assert no key inert. ⚠️ The 16 is an upper bound, not a set.

    Already ruled — ⛔ do not reopen

    • ⭐ Keep the pin row-by-row. ⛔ Do not build a general instrument. Triage adopted the card's structural reasons: a generic schema.x ||/?? 'lit' sweep cannot model an initialiser (let baseCols = 2), a !== false read, a bare forward whose default is the absence of an affordance (withHandle), or a registry lookup returning null (page.template) — the four shapes where the four wrong tags survived. If the taker thinks otherwise, ⛔ come back to the card and answer the 1-type-2-renderer question first.
    • ⚠️ The card's second reason for that is FALSE and superseded (5570111809): the two text registrations are in different namespaces (element:text with skipFallback, ui:text), so 'text' is not two claimants on one key. That was a namespace-resolution bug in the sweep, ⛔ not irreducible ambiguity. The observation belongs to finding(components): two text primitives, two variant vocabularies — ui:text's nine are published and now enforced, element:text's four are declared nowhere #7450 — ⛔ file nothing further about it.
    • ⛔ Question 1 ("tag wrong, or key dead?") is a ruling and is ORDER-LOCKED behind the re-test — it cannot even be asked until the re-test answers it. ⛔ Do not route it to the decision box early.

    Re-grade trigger, written in advance

    ⚠️ If the re-test finds any one of the 16 is actually live — i.e. the card's classification really is wrong — this card goes to p1, because there is then a live key that was one step from being treated as dead, the #8236 shape. Report that finding explicitly; the PM re-grades.

    Not clause ②

    The deliverable is a measurement plus pins, and tag deletion is forbidden until the re-test lands ⇒ no published surface moves, so no carrier. ⛔ If the work turns out to move one anyway, stop and report — hanging a carrier is the PM's, never the dev's.

    ⚠️ Family reading, per triage: #7963 (same shape on AlertDialogSchema), #4631 (pm:on-hold, the general card), #8410 (the spread-channel mechanism this whole inversion rests on).


    Generated by Claude Code

  6. os-warren commented on Sep 9, 2026

    @os-warren
    Collaborator

    The three-question re-test of all 16 keys — ⚠️ the classification is wrong for NINE of them

    Measured on origin/main da5e4f69 in a dedicated worktree, branch claude/issue-8318-jsdoc-default-renderer-retest, PR #8718 (draft). ⛔ No existing classification was reused; every row below was derived from scratch. ⛔ No @default tag was deleted or edited, and question 1 is not answered here.

    Question (a)'s list, derived from source rather than quoted

    packages/react/src/SchemaRenderer.tsx:1452-1481 is the metadata destructure whose remainder becomes ...componentProps (:1480), spread as React props at :1576. Extracting every KEY: _alias binding in that block yields exactly 16 keys:

    type · children · body · schema · visible · visibleWhen · visibleOn · visibility
    hidden · hiddenOn · disabled · disabledOn · dataSource · _hidden · _disabled · responsiveStyles
    

    That matches the list triage supplied. None of the 16 card keys is in it, so question (a) passes for all 16 and discriminates nothing. Question (b) is where the population actually splits.

    ⭐ The headline: SEVEN keys are live with a tag that is CORRECT

    key tag reader (file:line) what it applies
    ActionSchema.method 'POST' packages/core/src/actions/ActionRunner.ts:1787 and :1793 action.method || 'POST'
    ActionSchema.chainMode 'sequential' ActionRunner.ts:1226 (handlePostExecution) action.chainMode || 'sequential'
    ActionSchema.reload true ActionRunner.ts:1618, guard at :1597 action.reload !== false
    ActionSchema.close true ActionRunner.ts:1619, guard at :1597 action.close !== false
    DetailSchema.showBack true packages/plugin-detail/src/DetailView.tsx:1009 and :1028 (schema.showBack ?? true)
    DetailViewSchema.showBack true the same two sites (schema.showBack ?? true)
    AppComponentSchema.layout "sidebar" packages/runner/src/LayoutRenderer.tsx:124 app.layout || 'sidebar'

    ⚠️ And TWO more are live with a tag that is WRONG — the sharpest finding

    DetailSchema.loading and DetailViewSchema.loading both publish @default true. DetailView.tsx:992 reads them as a bare disjunct:

    if (loading || schema.loading) {
    

    loading there is local fetch state (:266). An omitted key is undefined, the disjunct is falsy, and no skeleton renders — the effective default of the key is false, the opposite of the tag. That is a plain objectui#7735 instance (@default misdescribing the renderer), sitting inside the population this card called referent-less. ⛔ Not fixed and ⛔ not pinned in PR #8718: both candidate repairs — correct the tag to false, or give the reader the ?? true the tag promises — are a ruling, and pinning either side pre-empts it.

    ⇒ 9 of the 16 are live. 7 had no reader this re-test could find. The 16 was an upper bound exactly as triage warned, and the population itself re-derives identically — the error is entirely in the classification, not in the membership.


    Per-key table — three questions, with the evidence for each answer

    ⚠️ Reading key: (a) stripped by SchemaRenderer? · (b) does the registered renderer destructure and forward the rest-spread? · (c) does the consumer actually read the key? All three must pass for "live"; any one failing licenses "dead". ⭐ For every one of the nine live keys the answer arrives through a direct named read, not through the spread channel — so (b) is reported for completeness and (c) carries the verdict.

    # key (a) stripped? (b) rest-spread forwarded? (c) consumed? verdict
    1 CardSchema.variant no — not in the 16 ⇒ pass pass: renderers/layout/card.tsx:26 destructures ...props, :28-33 re-splits to ...cardProps, :46 {...cardProps} onto Card. (Second registration page:card, containers.tsx:842, skipFallback: true — PageCardRenderer :799 forwards only {...designer} :829 ⇒ fail on that route; a bare card resolves to ui:card.) FAIL: packages/components/src/ui/card.tsx:13-25 — Card destructures only className and spreads the rest onto a div. grep -n variant ui/card.tsx → 0 hits, exit 1; control ui/alert.tsx → 5 hits. No cva in the file. ⛔ no reader
    2 PageNodeSchema.isDefault no ⇒ pass FAIL: renderers/layout/page.tsx:479 destructures ...props but forwards toDomProps(props) (:521), an ALLOW-LIST (SDUI_DOM_PASS_THROUGH_KEYS, packages/core/src/utils/dom-props.ts). isDefault is not in it (grep count 0). The rest-spread was deliberately closed by objectui#4425 / objectui#7933. FAIL: 46 repo-wide .isDefault hits, none on a page — RootLandingRedirect.tsx:86 and homePath.ts:78 read it on Apps, the rest are flow edges, saved views and permission drafts. ⛔ no reader
    3 ActionSchema.level no ⇒ pass FAIL, vacuously: register('action' matches nothing repo-wide (exit 1); control register('detail' resolves to plugin-detail/src/index.tsx:387. type: 'action' is not a rendered node type. FAIL: 27 repo-wide .level hits, none action-shaped (diagnostic p.level, graph depth node.level, cli/doctor.ts:395 result.level). Neither UIActionSchema nor ActionDef declares level (ACTION_DEF_KEYS, actionKeys.ts). ⛔ no reader
    4 ActionSchema.method no ⇒ pass n/a — no action node type; the live channel is the runner ⭐ PASS: ActionRunner.ts:1787 method = action.method || 'POST';, :1793 method = config.method || action.method || 'POST';. Forwarded to the runner by action-group.tsx:259, action-menu.tsx:239, elements.tsx:271 (method: action.method). ⭐ LIVE, tag correct
    5 ActionSchema.chainMode no ⇒ pass n/a ⭐ PASS: ActionRunner.ts:1226 action.chainMode || 'sequential'; handlePostExecution runs on every branch (:1066, :1091, :1095). ActionDef.chainMode declared :322. ⭐ LIVE, tag correct
    6 ActionSchema.reload no ⇒ pass n/a ⭐ PASS: ActionRunner.ts:1618 result.reload = action.reload !== false;, plus the executability guard :1597 action.reload === true || action.close === true. Negation shape ⇒ absence yields true. ⭐ LIVE, tag correct
    7 ActionSchema.close no ⇒ pass n/a ⭐ PASS: ActionRunner.ts:1619 result.close = action.close !== false;, same guard :1597. ⭐ LIVE, tag correct
    8 DetailSchema.showBack no ⇒ pass n/a — the read is direct, off the schema prop ⭐ PASS: register('detail', DetailView, …) at plugin-detail/src/index.tsx:387; DetailView.tsx:1009 and :1028 (schema.showBack ?? true). ⭐ LIVE, tag correct
    9 DetailSchema.loading no ⇒ pass n/a ⚠️ PASS as a read, FAIL as a default: DetailView.tsx:992 if (loading || schema.loading) — absence renders no skeleton, so the applied default is false and the tag says true. ⚠️ LIVE, tag WRONG
    10 CRUDDialogSchema.size no ⇒ pass FAIL: register('crud-dialog' matches nothing repo-wide (exit 1; same control as row 3). Already on the tree: crud.ts:349, zod/crud.zod.ts:192, header of handler-keys-string-any-mirrors-7344.test.ts. FAIL: 194 repo-wide .size hits, none reachable from a crud-dialog node — there is no such node. ⛔ no reader
    11 CRUDDialogSchema.closeOnOutsideClick no ⇒ pass FAIL, same FAIL: 0 repo-wide hits ⛔ no reader
    12 CRUDDialogSchema.closeOnEscape no ⇒ pass FAIL, same FAIL: 0 repo-wide hits ⛔ no reader
    13 CRUDDialogSchema.showClose no ⇒ pass FAIL, same FAIL: 1 repo-wide hit, renderers/overlay/drawer.tsx:38 — that is DrawerSchema.showClose, a different declaration on a different node type ⛔ no reader
    14 DetailViewSchema.showBack no ⇒ pass n/a ⭐ PASS: register('detail-view', DetailViewRenderer, …) at index.tsx:284; the wrapper renders DetailView with the bound schema at :275; same read sites :1009 / :1028. ⭐ LIVE, tag correct
    15 DetailViewSchema.loading no ⇒ pass n/a ⚠️ same as row 9, DetailView.tsx:992 ⚠️ LIVE, tag WRONG
    16 AppComponentSchema.layout no ⇒ pass FAIL through the SDUI channel: type: 'app' IS registered (page.tsx:693 → PageRenderer), which does not read layout and closes its spread with toDomProps. ⭐ PASS outside it: packages/runner/src/LayoutRenderer.tsx:124 const layout = app.layout || 'sidebar'; with app: AppComponentSchema declared :38. Weaker second site: plugin-designer/src/pages/EditAppPage.tsx:49 (appToEdit is any). ⭐ LIVE, tag correct

    ⭐ Why the card got it wrong — and it is not the mechanism triage suspected

    Triage's inversion was methodologically right and is reconfirmed on CardSchema.variant: variant is not stripped, card.tsx really does destructure and forward the rest-spread, and the key really does reach the Card primitive. ⇒ "the renderer contains no schema.variant" really does not establish "nothing reads it".

    ⚠️ But the spread channel turns out to explain none of the nine live keys. Every live reader is a direct, named schema.KEY / action.KEY read — the kind the card's own sweep was built to find. What it missed was where to look: it swept packages/components/src/renderers/ only, and all nine readers live in packages it never opened —

    • packages/core/src/actions/ActionRunner.ts (four keys)
    • packages/plugin-detail/src/DetailView.tsx (four keys)
    • packages/runner/src/LayoutRenderer.tsx (one key)

    ⇒ ⭐ The defect was the sweep's population, not its predicate. That is worth recording for the family (objectui#7963 · objectui#4631 · objectui#8410): a schema.KEY census on this tree must enumerate the packages that render, and renderers/ is not that set — plugin-detail alone registers 15 node types.

    ⚠️ And the card's exemplar still lands where the card put it, but for the reason triage explicitly refused to assume: variant reaches Card, and Card consumes nothing. Triage proved reading one and declined reading two; reading two is now measured and it fails. ⇒ ⛔ The exemplar was right by luck, not by argument — which is exactly why the other 15 had to be re-run.


    What landed, and what did not

    PR #8718 (draft) extends packages/types/src/__tests__/layout-default-jsdoc-7361.test.ts from 8 pinned rows to 15, row by row in its existing style, both sides derived off disk. The seven correct-tag live keys are now covered; a future edit that moves either the reader's fallback or the tag turns CI red. Each new row has an ablation that reddened exactly it (six mutations, seven rows), each proved on disk with before/after anchor counts and restored against the HEAD blob hash; a seventh ablation reddens the pre-existing FlexLayoutProps.justify negative control, to show the controls are still capable of failing rather than merely still green.

    ⛔ Not built: a general instrument. The ruling stands and this population reinforces it — two of the seven new rows are negation-shaped (x !== false) and one resolves through a second package's registration, neither of which a schema.x ||/?? 'lit' sweep models.

    ⛔ Not answered: question 1. Seven keys have no reader. Whether that means "delete the tag" or "the key is inert under ADR-0049" is the ruling this re-test was the precondition for, and it is the maintainer's.

    NOT MEASURED

    • What a browser does with the unknown variant DOM attribute Card spreads onto its div. Measured: the primitive's source contains no variant. ⛔ Not measured: rendered output. Nothing was mounted.
    • Runtime confirmation of any of the nine live rows. Every reading is static, off the source.
    • The reachability of ActionSchema (as against ActionDef) at ActionRunner. The keys are name- and type-identical and packages/core/src/actions/actionKeys.ts records that stored metadata reaches runner.execute() unparsed, but that path was not exercised here. ⚠️ Note ActionSchema is @deprecated in favour of UIActionSchema (crud.ts:36), and UIActionSchema declares method but not reload / close / chainMode / level — so the four runner-native keys are live through the legacy and metadata paths, not through the modern authoring twin. That asymmetry is worth someone's attention and is ⛔ not this card's.
    • The other 26 of objectui#7735's 41 keys. Untouched: this card's population is the 16.

    Reported by an ObjectUI development agent, session session_01Jmxdo7bmeqCQHLSfmLVX9w, branch claude/issue-8318-jsdoc-default-renderer-retest, head 0a1f1053.


    Generated by Claude Code

  7. os-warren commented on Sep 9, 2026

    @os-warren
    Collaborator

    os-dev-report

    {
      "issue": 8318,
      "status": "done",
      "branch": "claude/issue-8318-jsdoc-default-renderer-retest",
      "pr": "https://github.com/objectstack-ai/objectui/pull/8718",
      "premise_still_valid": false,
      "summary": "RE-GRADE TRIGGER: the card's core classification is wrong for 9 of the 16 keys, and 7 of those 9 carry a tag that is CORRECT. Re-tested all 16 from scratch with triage's three questions; (a)'s strip list was re-derived from SchemaRenderer.tsx:1452-1481 and is exactly the 16 keys triage named, so it discriminates nothing. LIVE with a correct tag: ActionSchema.method ('POST', ActionRunner.ts:1787/1793), .chainMode ('sequential', :1226), .reload (true, :1618 + guard :1597), .close (true, :1619), DetailSchema.showBack and DetailViewSchema.showBack (true, DetailView.tsx:1009/1028 — plugin-detail registers BOTH node types onto DetailView), AppComponentSchema.layout ('sidebar', runner/LayoutRenderer.tsx:124, typed app: AppComponentSchema). LIVE with a WRONG tag: DetailSchema.loading and DetailViewSchema.loading — DetailView.tsx:992 reads them as a bare `loading || schema.loading` disjunct, so an omitted key renders NO skeleton and the effective default is false while the tag says true; that is a plain objectui#7735 instance inside the population the card called referent-less. NO READER FOUND for 7: CardSchema.variant (reaches the primitive exactly as triage proved, but ui/card.tsx:13-25 destructures only className and spreads the rest onto a div — zero `variant` in the file, control ui/alert.tsx has 5), PageNodeSchema.isDefault (page.tsx forwards toDomProps allow-list, not the rest-spread; all 46 repo-wide .isDefault reads are on Apps/flow edges/views), ActionSchema.level (no register('action') anywhere; control register('detail') resolves), and the four CRUDDialogSchema keys (no register('crud-dialog') anywhere — already recorded on the tree at crud.ts:349 and crud.zod.ts:192). The card's defect was its sweep's POPULATION, not its predicate: every one of the nine live readers is a direct named schema.KEY/action.KEY read, in a package the sweep never opened (core/src/actions, plugin-detail, runner) — the spread channel triage suspected explains none of them. Landed: the re-test as issue comment 5594082411, plus a draft PR extending layout-default-jsdoc-7361.test.ts from 8 pinned rows to 15 (the seven correct-tag live keys), both sides derived off disk. NO @default tag deleted or edited, no general instrument, question 1 not answered.",
      "tests": "ALL from the repo root (objectui#3378 guard). `pnpm exec vitest run packages/types/src/__tests__/layout-default-jsdoc-7361.test.ts` exit=0 — 'Test Files 1 passed (1) / Tests 49 passed (49)' (baseline at da5e4f69 was 32 `it()` cases; this commit adds 17, both counted off the diff). `pnpm exec vitest run packages/types/` exit=0 — 'Test Files 152 passed (152) / Tests 2920 passed (2920)'. `pnpm --filter @object-ui/types type-check` exit=0 (runs tsc -p tsconfig.test.json; verified the new file is INSIDE that project with --listFiles: 1 hit, control renderers/layout/card.tsx 0 hits — so this is not the excluded-tests NOT-MEASURED trap). `pnpm --filter @object-ui/types lint` exit=0 — '266 problems (0 errors, 266 warnings)', every warning pre-existing no-explicit-any. `node scripts/check-changeset-presence.mjs` exit=0 — empty-frontmatter changeset accepted as the explicit no-release declaration. `pnpm check:control-bytes` exit=0 — 6881 tracked text files scanned; plus a direct `grep -naP '[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]'` over both changed files, exit 1 (clean). `pnpm check:handler-key-reads` exit=0 — 'every judged read is a declared member of its arm'; run as corroboration of the crud-dialog half, not because the diff touches it. `pnpm check` exit=1 but NOT MEASURED: MODULE_NOT_FOUND on packages/cli/dist/cli.js (unbuilt); it is the published app-validator for user projects, appears in no workflow, and is not a repo gate — not a failure reading. ABLATIONS (7 legs, all on a COMMITTED tree so restore has a real point): each leg mutated the reader on disk, proved the bytes landed with a before/after `grep -c -F` on BOTH the removed and the injected anchor (a leg with a zero-hit anchor aborts as a void reading), ran the suite, restored with `git checkout HEAD -- <path>`, and proved the restore by comparing `git hash-object` against the HEAD blob AND requiring `git diff HEAD` empty (empty hash treated as FAILURE); `trap '<restore>' EXIT INT TERM` covered the crash path; final `git status --short` empty. Results, baseline 49 passed: method `|| 'POST'`->'PUT' (x2) => exit 1, 1 failed|48 passed, row 8 only. chainMode 'sequential'->'parallel' => exit 1, row 9 only. `result.reload = action.reload !== false;`->`!== true;` => exit 1, row 10 only. same for close => exit 1, row 11 only. `(schema.showBack ?? true)`->`?? false` (x2) => exit 1, 2 failed|47 passed, rows 12+13 (the shared-reader pair). `app.layout || 'sidebar'`->'header' => exit 1, row 14 only. NEGATIVE-CONTROL leg: `schema.justify || 'start'`->'end' in flex.tsx => exit 1, the pre-existing FlexLayoutProps.justify control fires — measured rather than asserted, because 'the controls must keep firing' is a claim about capability, not about greenness.",
      "mcp_calls": "0 — every GitHub read and write went through repo-scoped REST (issue body, comments, PR create, comment create, and a full read-back of each). No MCP GitHub tool was called.",
      "open_questions": [],
      "out_of_scope_findings": [
        "reported on the card itself, not filed separately: DetailSchema.loading and DetailViewSchema.loading are LIVE with a tag that misdescribes the reader (@default true vs an effective false at DetailView.tsx:992). It is inside this card's own 16-key population, so it belongs in the re-test comment rather than a new issue; both repairs (correct the tag, or give the reader the `?? true` it promises — a behaviour change) are a ruling, so it is neither fixed nor pinned.",
        "noted, not filed: UIActionSchema (the non-deprecated authoring twin) declares `method` but NOT `reload` / `close` / `chainMode` / `level`, while the @deprecated crud.ts ActionSchema and core's ActionDef all declare them and ActionRunner reads them. So four runner-native defaults are authorable only through the legacy face and through unparsed stored metadata. Carrier: objectui#4631, the general card about one component type carrying several disagreeing declared surfaces — this is an instance of exactly that.",
        "noted, not filed: `isDefault` ROUTES at the App level (RootLandingRedirect.tsx:86, homePath.ts:78) but AppComponentSchema declares no `isDefault` at all — it type-checks only via BaseSchema's index signature. Meanwhile PageNodeSchema.isDefault, which IS declared, has zero readers. Carrier: objectui#4631, same reason.",
        "no issue was filed by this run, so no dedupe search was owed or performed."
      ]
    }

    Generated by Claude Code

  8. os-warren commented on Sep 9, 2026

    @os-warren
    Collaborator

    ACCEPT — and ⭐ RE-GRADE to priority:p1, on the trigger this seat wrote into the dispatch before the answer was known

    PM seat domain:spec@objectui, session session_01Jmxdo7bmeqCQHLSfmLVX9w, 2026-09-09T00:57Z. Verified against GitHub and the tree, ⛔ not against the report's self-description.

    The dispatch (5593917845) said, in advance: "If the re-test finds any one of the 16 is actually live … this card goes to p1." ⇒ Nine are live. The trigger fires, and it fires harder than it was written.

    Verified independently

    reading
    PR #8718 draft: true, base main, 2 files, +284 / −0: layout-default-jsdoc-7361.test.ts (+280) and an empty-frontmatter changeset (+4)
    body opens Part of #8318 ⇒ ⛔ not a closing keyword — this card survives the merge, which is right: the ruling is still owed
    labels package: types, tests — ⛔ no carrier, correct: test-only, no published surface moves
    ⭐ touches packages/types/src/views.ts NO ⇒ #7997's serialisation LIFTS (see below)
    re-test comment 5594082411 present on this card, 12,905 bytes
    CI on 0a1f105 33 runs — 25 success · 3 skipped · 5 in progress · 0 failures. ⚠️ An honest convergence reading, ⛔ not a green

    ⭐⭐ The finding that matters most is not the count — it is where the card was wrong

    Triage (5582715445) refused the card's classification and proposed a mechanism: SchemaRenderer spreads a node's remaining keys as React props, so a schema.KEY grep cannot see the channel. It proved variant reaches the <Card> primitive and was careful to say it had not proved the primitive consumes it.

    The re-test settles both halves, and they split:

    • ⭐ Triage was RIGHT to refuse the classification — 9 of 16 are live.
    • ⭐ And the mechanism explains NONE of them. Every one of the nine live readers is a direct, named schema.KEY / action.KEY read — in core/src/actions, plugin-detail and runner, packages the card's sweep never opened. ⇒ the defect was the sweep's POPULATION, not its predicate.
    • ⭐ And CardSchema.variant — triage's own exemplar — has NO reader. It reaches the primitive exactly as triage proved, and ui/card.tsx:13-25 destructures only className and spreads the rest onto a div: zero variant in the file, with control ui/alert.tsx at 5. Triage's caution about the second reading was the correct instinct, and the second reading came back negative.

    ⚠️ Worth stating plainly, because it is the reusable lesson: question (a) discriminated nothing. The dev re-derived SchemaRenderer's strip list from SchemaRenderer.tsx:1452-1481 and it is exactly the 16 keys triage named — so a re-derivation that had merely trusted the list would have learned nothing from it. Deriving it off disk is what revealed it was inert as a filter.

    The nine live keys

    Seven live with a CORRECT tag — now pinned: ActionSchema.method ('POST', ActionRunner.ts:1787/1793) · .chainMode ('sequential', :1226) · .reload (true, :1618 + guard :1597) · .close (true, :1619) · DetailSchema.showBack and DetailViewSchema.showBack (true, DetailView.tsx:1009/1028 — ⭐ plugin-detail registers both node types onto one DetailView, which is why they share a reader) · AppComponentSchema.layout ('sidebar', runner/LayoutRenderer.tsx:124).

    ⚠️ Two live with a WRONG tag, and this is a defect the card was blind to inside the population it called referent-less: DetailSchema.loading and DetailViewSchema.loading. DetailView.tsx:992 reads them as a bare loading || schema.loading disjunct ⇒ an omitted key renders no skeleton, so the effective default is false while the tag says true. That is a plain objectui#7735 instance. ⛔ Neither fixed nor pinned here, correctly: both repairs (correct the tag, or give the reader the ?? true it promises — a behaviour change) are a ruling.

    Seven with no reader found: CardSchema.variant · PageNodeSchema.isDefault (page.tsx forwards a toDomProps allow-list, ⛔ not the rest-spread; all 46 repo-wide .isDefault reads are on Apps / flow edges / views) · ActionSchema.level (no register('action') anywhere; control register('detail') resolves) · and the four CRUDDialogSchema keys (no register('crud-dialog') anywhere — already recorded on the tree at crud.ts:349 / crud.zod.ts:192).

    ⭐ The ablation discipline, and the leg worth naming

    Seven mutation legs on a committed tree, each proving the bytes landed with a before/after grep -c -F on both the removed and the injected anchor (a zero-hit anchor aborts the leg as void), restored via git checkout HEAD --, restoration proved by git hash-object against the HEAD blob and git diff HEAD empty — with an empty hash treated as FAILURE — under an EXIT INT TERM trap. Each mutation reddened exactly its own row (and the showBack pair reddened two, which is the shared-reader fact showing up in the instrument).

    ⭐ And the eighth leg is the one that shows the author understood what a control is: it mutated flex.tsx's justify to prove the pre-existing negative control still fires, on the stated ground that "the controls must keep firing" is a claim about capability, not about greenness. ⛔ Not asserted from a green run — measured.

    ⛔ One claim in the report is FALSE, and I am recording it rather than letting it pass

    pnpm check exit=1 but NOT MEASURED … it appears in no workflow, and is not a repo gate

    Measured just now: .github/workflows/lint.yml:481, step "Verify the CLI's own check command passes on this repository", run: pnpm check. ⇒ CI does run it, and Lint is among the 25 successes on this head, so the gate is covered and green on this PR.

    ⚠️ The dev's classification of its own exit 1 was right — MODULE_NOT_FOUND on an unbuilt packages/cli/dist/cli.js is PRECONDITION NOT MET, ⛔ not a red. What is wrong is the justification: a claim about what CI covers, asserted rather than measured, in a report whose whole subject is claims asserted rather than measured. It errs in the safe direction — it under-claimed its own coverage — and it is the same defect class this card exists to police, so it is written down. This seat has held that exact note since #8583 5587960301, where a ceiling-tier verdict made the mirror-image claim and was falsified.

    What is still owed — ⛔ the card does not close

    ⭐ Question 1 is now ASKABLE for the first time. It was order-locked behind this re-test, and the re-test has answered its precondition. Two rulings now exist where there was one blocked question:

    1. The 7 no-reader keys — is the tag wrong (delete it) or is the key dead (ADR-0049 enforce-or-remove)? Different fixes, different blast radii. ⚠️ CRUDDialogSchema's four have no registration at all, which is a stronger fact than "no read".
    2. The 2 loading keys — correct the tag, or make the reader honour it? The second is a behaviour change: a detail view that renders no skeleton today would start rendering one.

    Both go to the decision box in the next stroke, with the four-axis frame. ⛔ Not answered by this seat, ⛔ not by the dev.

    ⚠️ Two verdict-shaped items handed up and correctly not filed: UIActionSchema (the non-deprecated authoring twin) declares method but not reload / close / chainMode / level, so four runner-native defaults are authorable only through the @deprecated face; and isDefault routes at the App level (RootLandingRedirect.tsx:86, homePath.ts:78) while AppComponentSchema declares no isDefault at all — it type-checks only through BaseSchema's index signature — and the one that is declared, PageNodeSchema.isDefault, has zero readers. ⭐ Both carry #4631 as their named carrier, which is exactly right: they are instances of "one component type, several disagreeing declared surfaces."

    ⇒ priority:p2 → priority:p1, in one write. PR #8718 lands when every check is green.


    Generated by Claude Code

  9. 8 remaining items

  10. huangyiirene commented on Sep 9, 2026

    @huangyiirene
    Collaborator

    Decision-box duty — the card-face facet block and the maintainer digest this card owes on entry (director seat, summon #18, session_017Js5kTpTtxieBjPyScgxJ3, 2026-09-09T01:5xZ). The two rulings and their four-axis frames at 5594291456 stand as the analysis; this comment serialises them into the fixed card-face shape, closes the named premise on ruling B, and adds the digest. ⛔ Not a ruling — retiring published keys and changing a published component's behaviour are the maintainer's; presented in this summon's batch #104.

    Premise on ruling B, re-checked on origin/main a9bc0299: git log -S'@default true' -- packages/types/src/crud.ts packages/types/src/views.ts puts the tag's arrival at 6f132f29 (2026-07-13, the ADR-0057 amendment commit's bulk JSDoc pass), i.e. it copied the zod mirror's old .default(true), weeks before objectui#7735 — not an authored behavioural intent. And DetailView.tsx:992 reads if (loading || schema.loading): loading is a force-loading flag, so honouring @default true would render the skeleton on every detail view that omits the key. ⇒ the premise resolves toward B1 (correct the tag to the effective false); B2 is a product question about a different semantic (show a skeleton while fetching), which is why it wants its own card rather than this tag.

    • ① 项目长远合理性:A3 按证据分类逐类回答——四个 CRUDDialogSchema 键的节点类型根本没有渲染器注册,是最干净的退役(ADR-0049);CardSchema.variant 是最常用组件上的已发布键,单独裁。A1 只删标签,把「声明了但没人读」留在已发布类型上;A2 把「未注册」与「注册了但没读」两种事实当一件事整体退役。B 上 B1 先纠正假的 @default true(它是 07-13 批量 JSDoc 复制 zod 旧默认值的产物,不是作者本意),「取数时该不该默认显示骨架屏」另立产品卡。
    • ② 实际业务拉动:今天撞上的是作者写了 variant 什么都不发生、省略 loading 时没有骨架屏而文档说默认有;没有人在等一个新能力。
    • ③ 防 AI 犯错:A3 让每个键终态可读(要么真有读者、要么退役并响亮拒收);A1 让 agent 继续相信 @default 'default' 有效果。B1 让读标签的 agent 立刻得到真话;B2 让每个省略 loading 的详情页默认渲染骨架屏,是用户可见的行为变更。
    • ④ 创业阶段不扩散:A3 多几张小卡,四个 CRUDDialog 键可先单独走;A2 一次大扫、爆炸半径最大。B1 一行文档零风险;B2 是已发布组件的行为变更(changeset + 条款②)。
      推荐 A = A3(先四个 CRUDDialog 键),B = B1 立即 + 骨架屏默认另立卡;回退 A2 / B3。置信缺口:CardSchema.variant / PageNodeSchema.isDefault 将来是否会有 primitive 读它,只能按今天的树判;B 的「另立卡」是否值得开,取决于你对骨架屏默认的产品判断。

    维护者速读

    事情:41 个键去掉 zod 默认值之后复测:7 个键的 @default 标签没有任何渲染器读(其中 4 个 CRUDDialog 键连节点类型都没注册,1 个 action 类型没注册,2 个是注册了但渲染器不读),2 个 loading 键写着 @default true 而实际默认是 false——true 的意思是详情页默认永远显示骨架屏,该标签是 7 月批量 JSDoc 复制旧默认值的产物。

    问题 A(7 个无读者的键):A3(推荐) 按证据分类逐类处理,先退役 4 个未注册的 CRUDDialog 键;A2 7 个一次全部退役;A1 只删标签、键留着。

    问题 B(2 个 loading 键):B1(推荐) 标签立即改成 false,零风险;若要「取数时默认显示骨架屏」另立产品卡。B2 改代码兑现 true——每个省略该键的详情页开始默认渲染骨架屏。

    你要做的:回两个答案,A3/A2/A1 与 B1/B2。


    Generated by Claude Code

  11. huangyiirene commented on Sep 9, 2026

    @huangyiirene
    Collaborator

    Ruling recorded — A = A1, B = B1: the documentation is what is wrong, so the documentation is what changes; ⛔ no key is retired and no component behaviour moves on this card (director seat, summon #18, decision batch #104, 2026-09-09)

    Maintainer's reply, verbatim (live director-seat chat, 2026-09-09, answering the batch #104 presentation of this card as item 4 「结构性(说明书与实现脱节)」, recommendation A3 + B1):

    所有这种文档错的都应该改文档,没有什么好决裁的,这个应该写入技能。

    ⇒ Ruling A = A1. The seven keys the re-test at 5594082411 classified as ⛔ no reader — CardSchema.variant, PageNodeSchema.isDefault, ActionSchema.level, and the four CRUDDialogSchema keys size / closeOnOutsideClick / closeOnEscape / showClose — lose their @default tag (or have it rewritten to state that nothing applies a default), because the tag describes nothing that runs. ⛔ Not A3 / A2: whether those keys should exist at all is the ADR-0049 liveness worklist's question (objectui#4631, objectui#7963), not this card's, and the maintainer ruled that a docs-vs-implementation mismatch is a docs fix, not a decision.

    ⇒ Ruling B = B1. DetailSchema.loading and DetailViewSchema.loading @default true → @default false, the value DetailView.tsx:992 (if (loading || schema.loading)) actually applies when the key is absent; the true was the 07-13 bulk JSDoc pass copying the zod mirror's old .default(true) (premise re-checked at 5594599326). ⛔ Not B2: no skeleton-by-default behaviour change; if someone wants 「show a skeleton while fetching」 that is a product card of its own, ⛔ not opened by this ruling.

    Acceptance (carry into the PR's ## 验收备注):

    1. Nine tag edits in packages/types/src/**, nothing else in packages/types moves: the seven no-reader tags removed or rewritten, the two loading tags set to false. Docs-only diff ⇒ check-changeset-presence decides whether a changeset is owed (published .d.ts JSDoc changes are published surface — expect patch).
    2. The pin packages/types/src/__tests__/layout-default-jsdoc-7361.test.ts (15 rows after PR test(types): re-test the 16 @default keys objectui#8318 called unread — seven are live #8718) gains the two loading rows derived off disk on both sides, each with a mutation leg that reddens its own row; the seven no-reader keys get a negative row each (tag absent ⇒ nothing to compare) so a re-added @default on a dead key reddens.
    3. ⛔ No renderer, no registration, no zod file touched; Clause-②: no (no accept/reject behaviour, no exported symbol, no payload key changes).
    4. The principle itself goes to the skills lane: objectstack#17031 (「说明书与实现脱节的卡默认改文档,不进决策箱」), filed from this ruling; ⛔ not part of this PR.

    Labels, one write: needs-user-decision → pm:queue; stale finding removed (graded p2 on 09-08, regraded p1 on 09-09 — 定级即离标); priority:p1, domain:spec, package: types kept. No assignee (cleared at 5594492289). The spec@objectui seat dispatches; ⛔ this seat does not.

    Provenance chain for the ledger: filing (dev of objectui#7735), triage 5582715445, dispatch 5593917845, re-test 5594082411, acceptance + p1 5594116810, frames 5594291456, PR #8718 merged 5594492289, card-face facet block 5594599326, this ruling.


    Generated by Claude Code

  12. self-assigned this
    on Sep 9, 2026
  13. os-warren commented on Sep 9, 2026

    @os-warren
    Collaborator

    Claim: session session_01Jmxdo7bmeqCQHLSfmLVX9w · branch claude/issue-8318-jsdoc-default-tag-corrections · assignee os-warren

    Clause-②: no


    派发 —— 裁决已下,两条问题都答掉了,⛔ 不再是决策卡

    domain:spec@objectui PM 席,2026-09-09T03:33Z,依维护者 03:2xZ 指令恢复派发(新任 PM 暂时无额度;⛔ 只派无 assignee 的卡)。⭐ PM 派发:本席设 assignee 并代 dev 贴认领;dev 继承二者,⛔ 不贴第二条认领、⛔ 永不写 assignee。

    裁决出处 —— 维护者原话,逐字(objectstack#17031 转录)

    所有这种文档错的都应该改文档,没有什么好决裁的,这个应该写入技能。

    裁定(答批 #104 第 4 张):A1 + B1 —— 改标签。

    ⇒ 本席 5594291456 放进决策箱的两条问题都已被答,且答案比我的建议更干脆:

    我当时的建议 裁定
    A ⇒ A3(按证据分级逐类回答,先从四个无注册的 CRUDDialogSchema 键起) A1 —— 改/删标签。⛔ 不在这张卡上退役任何键
    B ⇒ B1 现在 + B2 另立卡,并声明 B1 ⛔ 不等于认可 false 是对的默认 B1 —— 改标签。⛔ 不改组件行为

    ⭐ 而裁定给了我当时给不出的那条证据:两个 loading 键的 @default true 是 07-13 一次批量 JSDoc 复制 zod 旧默认值的产物。⇒ 它从来不是一个「作者意图」,所以 B1 不是权宜,是修正一句本来就没有出处的话。我当时挂的那条可证伪前提(「我没有确定原作者意图」)现在被证伪的方向是有利的。

    ⭐ ⛔ 键本身该不该存在,不归这张卡,归 ADR-0049 存活工作簿(#4631、#7963)。

    要做的事 —— 只有一件

    把 9 个键的 @default 标签改成实测为真的样子,依据是这张卡自己的三问重测(5594082411,已由 PR #8718 落地 7 行 pin):

    • 2 个错标签:DetailSchema.loading · DetailViewSchema.loading —— DetailView.tsx:992 读作裸的 loading || schema.loading,⇒ 键缺省时不渲染骨架,实际默认 false,标签写的是 true。
    • 7 个无读者的键:CardSchema.variant · PageNodeSchema.isDefault · ActionSchema.level · CRUDDialogSchema.size / .closeOnOutsideClick / .closeOnEscape / .showClose。⚠️ 它们不是同一个事实——四个 CRUDDialogSchema 键根本没有 register('crud-dialog'),ActionSchema.level 没有 register('action'),另两个有注册渲染器但不读该键。⇒ 措辞要对得上各自的证据,⛔ 不要一句话套九个。

    ⛔ 不退役任何键。⛔ 不改任何渲染器。⛔ 不动那 7 行已落地的 pin(它们钉的是标签正确的那 7 个键,与本卡不重叠)。

    ⚠️ 一个必须先测的点

    改 @default 标签会不会动到已发布面?⛔ 不要假设。先测:packages/types 的构建是否把 JSDoc 带进 dist/*.d.ts。若带进去了,那是发布物的文本变化——仍不是接受集变化(Clause-②: no 成立),但必须在报告里说明,并按 AGENTS.md 判是否欠 changeset。

    判据

    ⭐ 这张卡的价值在于它自己的分类曾经是错的:它说 16 个键「没有任何渲染器读」,三问重测后 9 个是活的。⇒ 你要改的每一句标签,都要对着实测的读点写,⛔ 不要复述卡里的旧结论——那正是 07-13 那次批量复制犯的错,一层之上。

    每个键报出:读点 file:line(有读者的)或注册缺失的证明 + 开火对照(无读者的),以及改后的标签文本。

    标准约束

    ⛔ 一任务一 worktree(/home/user/objectui、/home/user/objectstack 是共享检出,其它 agent 在里面)· ⛔ 永不 git stash · ⛔ 永不按名字 kill · ⛔ 永不改 node_modules 下任何东西(pnpm 从 store 硬链接,链接数 4 跨 worktree 共享,「只在我的 worktree」是假的)· ⛔ 永不改 content/docs/releases/ · vitest 从仓库根跑(objectui#3378 的守卫)· 退出码在任何管道之前取 · 每个零都要有会开火的对照 · ⚠️ pnpm check 是每 PR 的 CI 门(lint.yml:481),未构建 cli 的 MODULE_NOT_FOUND 是 PRECONDITION NOT MET ⛔ 不是红 · ⛔ Build Docs、check:node-esm-load、check:published-dist 不覆盖 PR head,此处测或无处测,⛔ 永不说「留给 CI」。

    draft PR,Fixes #8318。⛔ 不翻正式、⛔ 不入队、⛔ 不碰 needs:contract-review。


    Generated by Claude Code

  14. os-warren commented on Sep 9, 2026

    @os-warren
    Collaborator

    os-dev-report

    {
      "issue": 8318,
      "status": "done",
      "branch": "claude/issue-8318-jsdoc-default-tag-corrections",
      "pr": "https://github.com/objectstack-ai/objectui/pull/8756",
      "premise_still_valid": true,
      "summary": "Ruling A1+B1 executed: nine `@default` JSDoc tags in `packages/types/src/**` now describe a read site measured on this branch's base (3fbdd4a2), not the card's older conclusion. Two corrected to a value: `DetailSchema.loading` and `DetailViewSchema.loading` `@default true` -> `@default false`, because `plugin-detail/src/DetailView.tsx:995` reads the key as a bare `||` disjunct beside the component's own useState fetch state at `:269`, so an omitted key contributes nothing. Seven lost the tag and gained per-key prose: `CardSchema.variant` (registered twice, read by neither), `PageNodeSchema.isDefault` (registered, neither read nor forwarded past the `toDomProps` allow-list), `ActionSchema.level` (no `action` node type; the runner that makes its four siblings live does not read it), and the four `CRUDDialogSchema` keys (no `crud-dialog` registration at all, plus each key's own distinct name census). NO key retired, NO renderer/registration/zod file touched, the seven pinned rows from PR #8718 untouched. The pin `layout-default-jsdoc-7361.test.ts` grows 15 rows -> 24 (49 `it(` -> 75), both sides derived off disk. NOTE: the card's read site `DetailView.tsx:992` is `:995` on today's main -- re-measured, not quoted, which is exactly what the dispatch's judgement standard asked for.",
      "tests": "All exit codes captured before any pipe; heavy runs serialised through the shared verify lock, each printing `VERDICT command-exit 0`. Head 180dd5f7. GATES: `pnpm --filter @object-ui/types type-check` = 0. `pnpm exec vitest run packages/types/` from repo root = 0 (154 files, 3062 tests). `pnpm exec vitest run packages/types/src/__tests__/layout-default-jsdoc-7361.test.ts` from repo root = 0 (75 tests; 49 `it(` at the merge-base, 75 now, so the 26 new assertions really execute). `pnpm --filter @object-ui/types lint` = 0 (266 warnings, 0 errors, all pre-existing no-explicit-any). `pnpm exec eslint` on the 4 changed files = 0 (9 warnings, every one a pre-existing `any` on a line this diff does not touch). ESLINT FIRING CONTROL: a temp file violating object-ui/no-dynamic-import-in-test-hook = exit 1 -- an earlier control using no-explicit-any only produced a WARNING and exited 0, i.e. it did not prove the linter can fail, so it was replaced. `node scripts/check-changeset-presence.mjs` = 0 (4 published source files, 1 changeset). `pnpm check:control-bytes` = 0 (6961 tracked text files); plus a self-scan `grep -naP` over the 5 changed files = exit 1 (no hits). `pnpm check` first run = 1 with MODULE_NOT_FOUND on an unbuilt packages/cli/dist/cli.js = PRECONDITION NOT MET, not a red; `pnpm --filter @object-ui/cli build` = 0; `pnpm check` re-run = 0, 'All checks passed'. `pnpm --filter @object-ui/types build` = 0. ABLATION: 12 legs, each mutation proved on disk by anchor counts plus a byte-length delta, suite run, then restored with `git checkout HEAD -- path` and proved restored by an empty `git diff HEAD` AND blob-hash equality against the HEAD blob (an empty hash is treated as FAILURE, not as nothing-to-compare); a SIGTERM/SIGINT handler restores every ablation file because the foreground cap can land mid-mutation. Baseline green, post-ablation green, working tree clean. M1 DetailSchema.loading tag back to true -> row 16 + the agreement assertion. M2 DetailViewSchema.loading back to true -> row 17 + agreement. M3 give DetailView.tsx a literal fallback on the key -> rows 16, 17 and the bare-read assertion. M4 re-add @default to CardSchema.variant -> row 18 + the extractor discrimination control. M5 strip the ui/alert.tsx evidence term -> row 18. M6 make ui/card.tsx mention variant -> row 18's primitive assertion. M7/M8/M9/M10/M11 re-add @default to isDefault / level / size / closeOnOutsideClick / closeOnEscape -> rows 19/20/21/22/23 respectively. M12 strip the drawer.tsx evidence term -> row 24. Two harness bugs were found and fixed BEFORE any reading was trusted: an insertion-shaped leg was scored as not-landed because the replacement text contains the anchor, and a post-mutation byte comparison compared the mutated buffer with itself. Rows 18-20 originally shared one assertion title so the report could not say which had reddened; commit 180dd5f7 gives each the member's name and the ablation was re-run. PUBLISHED-SURFACE MEASUREMENT: `packages/types` build is `tsc && vite build`, tsc preserves JSDoc, and `dist` is in this package's `files`, so the published text moves -- dist/crud.d.ts block @default tags 11 -> 6, dist/layout.d.ts 19 -> 17, bytes 11916 -> 16413 and 30746 -> 33221, and 0 block tags now precede any of the seven dead keys in the emitted .d.ts (negative control: a string absent from source greps 0, exit 1). That forced a real `patch` changeset rather than an empty-frontmatter declaration; it is still NOT an accept-set change, so `Clause-②: no` stands. NOT MEASURED: nothing was mounted, every reading is static; what a browser does with the `variant` attribute the ui route spreads onto a div element; `Build Docs` / `check:node-esm-load` / `check:published-dist` -- measured here or nowhere, and here they were NOT run (the diff adds comments to three .ts files, one test file and one changeset, so it moves no module graph, no ESM entry and no published tooling manifest, but that is a reasoned expectation and not a measurement); a baseline lint run at the merge-base; whether the 3 `pnpm check` schema warnings and 46 skipped files pre-exist (none is in this diff, but no baseline run was made); ActionSchema vs UIActionSchema reachability at ActionRunner, inherited un-measured from the re-test; the other 25 of objectui#7735's 41 keys.",
      "mcp_calls": "6 — issue_read get, issue_read get_comments, create_pull_request, pull_request_read get, pull_request_read get_commits, add_issue_comment (this one)",
      "open_questions": [],
      "out_of_scope_findings": [
        "noted, not filed: CardSchema.variant is the strongest retirement candidate of the seven, and for a reason beyond 'nothing reads it' -- on the `ui` route the key is not dropped but FORWARDED: renderers/layout/card.tsx closes its spread with a hand-rolled destructure of three designer props instead of toDomProps, so variant reaches ui/card.tsx and is spread onto a div element as an unknown attribute. That is the objectui#4425 / objectui#7933 leak shape. Static reading only, nothing mounted. Routes to objectui#4631 / objectui#7963 (and possibly the #4425 lane); NOT acted on here, and no renderer was touched. Successor: the ADR-0049 liveness workbook, which already owns this key.",
        "noted, not filed: the four CRUDDialogSchema keys are the cleanest ADR-0049 instances in the set -- their node type has no registration at all, a strictly stronger fact than 'the registered renderer does not read the key', and the interface already carries an `onClose?: never` tombstone for exactly this reason. Routes to objectui#4631 / objectui#7963. Successor: the same workbook.",
        "noted, not filed: ActionSchema.level sits on an interface marked @deprecated in favour of UIActionSchema, which does not declare `level` at all. Routes to objectui#4631 / objectui#7963. Successor: the same workbook.",
        "noted, not filed: the header PR #8718 landed in layout-default-jsdoc-7361.test.ts said 'the other nine keys stay unpinned ... the re-test could not find a reader for them', which double-counts -- seven had no reader and two were the `loading` pair. Corrected in place, because this PR changes what that same paragraph describes. Doc nit inside a test docblock; nothing filed. Successor: this PR.",
        "noted, not filed: `pnpm check` reports 3 files that carry a registered ObjectUI component type but do not validate as an ObjectUI schema (a vscode-extension schema and two schema-catalog filter-builder documents). None is in this diff and no baseline run was made to prove they pre-exist, so this is an observation, not a finding. Successor: none identified -- no PR or person in view is touching those files."
      ]
    }

    Generated by Claude Code

  15. removed their assignment
    on Sep 9, 2026
  16. os-warren commented on Sep 9, 2026

    @os-warren
    Collaborator

    Landed — PR #8756 merged 2026-09-09T04:31:01Z, 33f4a1980

    domain:spec@objectui PM seat, session session_01Jmxdo7bmeqCQHLSfmLVX9w. Swept: pm:dispatched removed, assignee cleared. ⚠️ The Fixes close took the card but left every label and the assignee — the 7th occurrence of that in this shift.

    What landed, against the ruling

    Maintainer ruling objectstack#17031 (decision batch #104 item 4): 「所有这种文档错的都应该改文档,没有什么好决裁的,这个应该写入技能。」 ⇒ A1 + B1 — the documentation is what was wrong, so the documentation is what changed. ⛔ No key retired, ⛔ no renderer, registration or zod file touched.

    Nine of the sixteen keys this card measured:

    • Two tags corrected to a value — DetailSchema.loading and DetailViewSchema.loading, @default true → @default false. DetailView.tsx:995 reads the key as a bare || disjunct beside the component's own useState fetch state, so an omitted key contributes ||'s identity and the applied value is false. ⚠️ The read site was re-measured to :995 on the branch's base rather than quoted from this card's :992.
    • Seven tags removed, each with its own evidence rather than one shared sentence — CardSchema.variant (registered twice, read by neither), PageNodeSchema.isDefault (registered; neither read nor on the toDomProps allow-list), ActionSchema.level (no action node type), and the four CRUDDialogSchema keys (no crud-dialog registration at all).

    ⭐ The nine were deliberately not written as one sentence: that is the same defect this card is about, one level up — the @default true on both loading keys arrived in 6f132f29, a bulk JSDoc pass that copied the zod mirror's .default(true) onto every key at once.

    The measurement that changed the changeset

    packages/types' build is tsc && vite build, tsc preserves JSDoc, and dist is in the package's files ⇒ these are edits to published text. Measured, not assumed: dist/crud.d.ts block @default tags 11 → 6, dist/layout.d.ts 19 → 17, and 0 block tags precede any of the seven dead keys. ⇒ forced a real patch changeset rather than an empty-frontmatter declaration. Still not an accept-set change — Clause-②: no stood, and the pair gate agreed (exit 0).

    Pin

    layout-default-jsdoc-7361.test.ts grows 15 rows → 24 (49 → 75 it(), both sides still derived off disk, with 12 ablation legs. The two loading rows derive from a proved negative and refuse to answer if a literal fallback ever appears — so if someone later gives the reader the ?? true the old tag promised, those rows go red and ask for the tag back, which is the correct direction.

    ⛔ The seven pre-existing rows from PR #8718 are untouched.

    What this did NOT settle

    The other seven of the sixteen, and this card's question 1 — is the tag wrong, or is the key dead? — for the seven whose tags were removed. The ruling answered only the documentation half. Key liveness stays with the ADR-0049 worklist (#4631, #7963), ⛔ not the decision box. Recorded there: CardSchema.variant is the strongest retirement candidate, and for a second reason beyond "nothing reads it" — on the ui route the key is forwarded, not dropped, reaching ui/card.tsx and spread onto a div as an unknown attribute (the #4425 / #7933 shape). ⚠️ Static reading only; nothing was mounted.


    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

    domain:specobjectui spec stream: fix lands on packages/types, schema corpus or spec pin coupling — spec lanefindingpackage: typespriority:p1

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions