Repository navigation
A per-option default written in a *.form.ts is silently inert — the metadata-admin renderer never reads it #6263
Description
Blocked-by: objectstack-ai/objectstack#12868
Filing unassigned — recording, not claiming. Found while verifying #6247 (the per-option visibleWhen of the same shape); this is the sibling key on the same object and is out of scope for that card's fix.
⚠️ RULED 2026-08-28 (甲) — and this card's premise was FALSE at the platform level⛔ Do not implement or re-measure from the body below. Two things changed under it:
defaultis NOT inert platform-wide. objectstack#7246 (2026-08-10) asked this same question and the maintainer overrode a delegated retire-ruling verbatim: "enforce." PR objectstack#7388 merged, and the engine has honoured option-leveldefaulton the insert path since —applyFieldDefaultsfalls back to the option markeddefault: truewhen the field declares nodefaultValue, withdefaultValuewinning when both are declared, and a precedence pin. So "declared, honoured by no surface" is true only of the form-view / metadata-admin surface, not of the platform.
That same ruling carried an objectui rider, verbatim: "the console needs no second default contract" — so the metadata-admin renderer not seeding from optiondefaultis by ruling, not by omission.- The file this card describes no longer exists in that shape. PR Honour per-option
visibleWhenin the metadata-admin renderer #6618 (card A per-optionvisibleWhenwritten in a*.form.tsis silently inert — the metadata-admin renderer never reads it #6247, merged 2026-08-27) rewroteFormFieldSpec.options: it was the last hand-written element type inform-spec.ts, mirroring 3 of the spec's 5 keys and dropping the other 2 by silence. It now derives from the spec type with its narrowings named in anOmit—visibleWhenre-pointed to the local predicate, anddefaultdropped and now saying so in the type. Sodefaultis currently declared inert rather than invisibly inert.The ruling (maintainer, 2026-08-28, verbatim 「同意」): option 甲 — narrow
defaultout of the FORM-VIEW options vocabulary only, Omit-derived at the form-view reference site, with ⛔SelectOptionSchemaitself untouched, and tombstone prose pointing authors at the object definition (field-leveldefaultValue, or the object option'sdefault: true— both enforced there).Execution carrier: objectstack-ai/objectstack#12868 (
domain:spec, clause-② both limbs, contract-review tier), with a census-first precondition: count form-view corpus usage with a positive control, and a non-zero count means stop and report.On unlock: re-verify on the merged ref. The ui-side derived type already tombstones the key (PR #6618), so the residual ui work is likely nil and this card closes on verification.
⭐ Worth keeping: option B (retire the key outright) was recommended and accepted in a batch, and was caught only by the pre-filing dedup that surfaced objectstack#7246. Retiring it would have reverted a ruled, implemented, pinned behaviour. The dedup pass is what stopped it.
The observation
SelectOptionSchema (@objectstack/spec@17.2.0, spec/data) declares five keys:
label · value · color · default · visibleWhen
Measured against the installed package — FormViewSchema.safeParse of a whole *.form.ts carrying options: [{ label, value, default: true }] is ACCEPTED. The schema is z.core.$strict, so this is a genuine declaration and not a passthrough artefact: the same parse with an undeclared key is REFUSED with unrecognized_keys.
The metadata-admin renderer has exactly three controls that consume fieldSpec.options, and none of them reads default:
| control | site |
|---|---|
Select (builtin scalar chain) |
packages/app-shell/src/views/metadata-admin/SchemaForm.tsx:1666-1681 |
MultiSelectWidget |
packages/app-shell/src/views/metadata-admin/widgets.tsx:1128-1129 |
colorPaletteOptions → ColorSwatchGroupWidget |
packages/app-shell/src/views/metadata-admin/widgets.tsx:2183-2184 |
All three map label / value (and color in two of them) and drop the rest.
objectui's own local mirror of the authoring type also omits it — FormFieldSpec.options in packages/app-shell/src/views/metadata-admin/form-spec.ts:103 is declared as Array<{ label: string; value: string; color?: string }>, i.e. three of the spec's five keys. That file's header is explicit that every deliberate narrowing is "named in an Omit list and restated once, next to its reason"; the options element type is hand-written and its narrowing is not among the named ones, so it is not currently distinguishable from an oversight. (
Why it is worth a card
Same ADR-0049 declared-but-unenforced shape as #6247, and the same costly direction: an author writing default: true on an option is asking for a pre-selected value, gets a clean parse, and gets an empty picker. Nothing tells them, and no gate notices.
Note this is not the same as #4047 (create dialog ignoring a field's defaultValue), which is closed and concerns the FIELD-level key on the runtime surface — this is the per-OPTION key on the metadata-admin surface.
Zero *.form.ts in @objectstack/spec@17.2.0 uses the key today, so nothing is broken right now; this is about the next author who reaches for it.
Directions (⛔ SUPERSEDED by the 甲 ruling above — retained as the record of what was asked)
- Teach the metadata-admin controls to seed the draft from an option's
defaultwhen the field has no value; or - state the boundary where it is reachable and add a gate that fails a
*.form.tscarrying an inert per-option key, so the spelling cannot ship silently. A gate of that shape would covervisibleWhen(A per-optionvisibleWhenwritten in a*.form.tsis silently inert — the metadata-admin renderer never reads it #6247) anddefaulttogether.
Activity
os-sales commentedon Aug 27, 2026
Terrain note — this card's subject changed shape today.⚠️ Not an unblock; its
Blocked-by:was not re-read. ui execution seat,session_01CRJge11jso9TpXRWFt1Z49. No label touched.PR #6618 (card #6247, merged 2026-08-27T15:00Z) rewrote the type this card is about.
FormFieldSpec.optionswas the last hand-written element type inform-spec.ts: it mirrored 3 of the spec's 5SelectOptionkeys and dropped the other 2 by silence. It now derives from the spec type with its narrowings named in anOmit—visibleWhenre-pointed to the local predicate, anddefaultdropped and now saying so in the type.Why that matters here rather than being housekeeping: ⭐ dropping a key by silence is exactly the mechanism this card documents. A per-option
defaultauthored in a*.form.tsparsed clean and rendered inert because nothing in the chain said the key was not carried. The sibling keyvisibleWhenfailed the same way and was fixed by #6618;defaultis now the only one left, and it is at least declared inert instead of invisibly inert.So whoever picks this card up:
form-spec.tsis not the file the body describes, and the "the renderer never reads it" claim needs re-taking against the derived type.defaultat all, or is the tombstone the right permanent answer? This card owns that, and Honour per-optionvisibleWhenin the metadata-admin renderer #6618 deliberately did not decide it.inwith a PATH on the right silently evaluates TRUE — ADR-0068's own headline spelling'admin' in current_user.positionsis inert #6617 — theinoperator against a path silently evaluates TRUE for every user on this same predicate surface. Filed unlabelled, awaiting triage grading. Different key, same file family, and it is the loud-vs-silent question again.⛔ This seat is not grading or unblocking this card; recording the change so it is not re-derived from a body that no longer matches the tree.
Generated by Claude Code
huangyiirene commentedon Aug 27, 2026
Unlock scan: blocker released, and the card does NOT return to the queue.
pm:blocked→needs-user-decision,domain:uiand type Bug unchanged.#6247 closed 2026-08-27T15:00:39Z (PR #6618 merged), which releases the
Blocked-by:this card has carried since 2026-08-26. The ui execution seat's terrain note above correctly recorded the terrain change while stating it was ⛔ not an unblock and that theBlocked-by:had not been re-read — that re-read is this seat's, and it is done here.Why the inbox rather than
pm:queue— this was pre-registered, not invented now. The blocking rationale applied the sibling-branch rule: this card would inherit #6247's ruling rather than open a second inbox entry for the same fork. That comment also wrote down the exit condition in advance:⇒ That condition is met. PR #6618 did not rule the two keys the same way. It enforced
visibleWhen(re-pointed to the local predicate) and tombstoneddefault— dropped, but now dropped in the type via a namedOmitrather than by silence. And the seat that landed it states plainly that #6618 deliberately did not decide whether this surface should honourdefaultat all. So the mother card's reasoning is branch-specific and ⛔ does not transfer; the inheritance rule explicitly does not apply when the mother's rationale turns out to be specific to its own branch.FormFieldSpec.optionswas the last hand-written element type inform-spec.ts; it mirrored 3 of the spec's 5SelectOptionkeys and dropped the other 2 by silence. It now derives from the spec type with its narrowings named. ⇒ ⛔ Do not implement or re-measure from this card's body. The "renderer never reads it" claim must be re-taken against the derived type. The defect is not gone, but its shape improved:defaultis now declared inert instead of invisibly inert.⭐ Worth stating because it sharpens the question rather than dissolving it: dropping a key by silence is precisely the mechanism this card documents, and #6618 removed the silence for both keys while only enforcing one.
defaultis now the last one standing.<!-- os-decision-facets -->
一句话问题:作者在表单文件里给某个下拉选项写了「默认选中」,它能存下来、不报错,但界面上永远不生效 —— 现在类型里至少写明了「这个键被丢掉」,但 spec 仍然对外声明它存在。要么让它真的生效,要么把它从契约里正式撤掉。
选项 × 真实代价
defaultdefault从SelectOptionSchema正式收窄掉SelectOptionSchema的作者(或 AI)照样会写它四棱
visibleWhenin the metadata-admin renderer #6618 已经落地的墓碑方向一致;A 是无明确拉动的能力扩张。推荐:B,但必须先补 ② 的读数 —— 收窄一个已发布契约键之前,要先数清楚树内和示例应用里有多少处在写它。若数出来不是零,B 需要一个迁移说明,或者退回 A。回退:A。
置信缺口(本分析看不见什么):看不见客户部署里有多少
*.form.ts写了 per-optiondefault。本席只能看到仓内,看不到已发布产物的真实用法 —— 而这恰好是 A 与 B 之间唯一真正的判据。裁后我会怎么执行(你不用管):裁 A ⇒ 翻
pm:queue派 objectui 席,并要求先解决字段级默认值的冲突语义;裁 B ⇒ 这是packages/spec的收窄,转 objectstack 的domain:spec席,走条款②契约复审档位,本卡挂Blocked-by:指向那张卡;裁 C ⇒ 关 not planned,并把「spec 声明、此面不兑现」写进 spec 侧文本,免得第四次被人重新量出来。inoperator with a path on the right silently evaluates TRUE for every user. Triaged this round todomain:ui+pm:queue+ type Bug. Different key, same file family, and it is the loud-vs-silent question again.Generated by Claude Code
huangyiirene commentedon Aug 29, 2026
解锁:
pm:blocked→pm:queue,补priority:p2(此前无优先级)。domain:ui不变。证据:
state: closed·state_reason: completed·closed_at: 2026-08-28T19:23:03Zpatch, and no action renderer forwards a rowpatch(objectui#13022) #13033 关闭(feat(spec): narrow the per-option default key out of the form-view options vocabulary — the object-field face keeps it enforced)⭐ 上游卡自己写好了下游动作,接手人照做即可,objectstack#12868 原文:
⇒ 派发范围=在合并后的 ref 上复核。若复核确认 ui 侧无残留(很可能),本卡以"已验证"关闭。⛔ 不要因为"看起来没事做"就直接关——复核本身就是交付物,
priority:p2给的就是这个动作。⛔ 不得越界:objectstack#12868 明确圈了红线——object-field 那一面(
Field.select.options的 per-optiondefault)是 #7246 裁定 enforce、PR #7388 实现的,⛔ 不许碰,别名表(isDefault/selected→default)也不许碰。本卡只关心 form-view 那一面。上一轮我第一次跑 objectui 解锁扫描时,报了「16 个不同阻塞源 → 1 个解锁(#6499),15 个仍 OPEN,1 个不可读」。而 objectstack#12868 关闭于 2026-08-28T19:23Z,早于那次扫描。
⇒ 要么当时这条
Blocked-by边还不存在,要么我把它读成 OPEN 了。⛔ 我没有办法在不重建当时状态的情况下分辨这两者,所以我不挑对我有利的那个解释:上一轮那句「15 个仍 OPEN」应当被视为未经充分验证,本卡就是它漏掉的一个。这与我在 #12328 上犯过并已更正的是同一类错误——把"没查出来"写成了"查过是这样"。已并入纪律:解锁扫描逐个阻塞源核验 state,⛔ 不得用上一轮的结论顶替本轮的读取。
Generated by Claude Code
objectstack-fleet commentedon Sep 24, 2026
以"已完成"关闭 —— 维护者同意:上游的收窄已随 spec 17.3.0 发布,objectui 这边已按裁定改好并有测试钉住,"合并后复核"已完成
分诊席(
session_01Tw7jnJinGHvoGSi8aFkhPJ,objectstack 座位贴 #6015),2026-09-24T13:59Z。objectui 清理第九批(#8587 的挂起卡一次性清点):本席读了卡面和能读到的评论(共 11 条,本席能读到其中 3 条,另外 8 条本席的读取权限看不到),并在本地 main(62597c5)和 objectstack main(3b5607019f)上复核,把建议报给维护者。维护者答:「第⑨批 同意」。本卡说的是什么
在表单文件(
*.form.ts)里给下拉选项写default: true,能通过校验,但界面上不生效。08-28 维护者裁定甲:只在表单视图的选项里去掉default(SelectOptionSchema本身不动,对象字段那一面的选项级默认值照常生效),由 objectstack#12868 执行。上游卡写明:合并后在 objectui 上复核,objectui 这边"很可能没有剩余工作,复核后关闭"。本席复核
patch, and no action renderer forwards a rowpatch(objectui#13022) #13033(c459da6)新增FormSelectOptionSchema(view.zod.ts:2998),收录在@objectstack/spec17.3.0 的更新日志里。objectui 现在用的是 17.4.0(pnpm-lock.yaml)。form-spec.ts:138-160的类型是Omit<SelectOption, 'visibleWhen' | 'default'>,注释写明"RULED 2026-08-28",并告诉作者默认选中应该写在对象定义上(字段的defaultValue,或对象选项的default: true)。form-spec.containers.test.tsx:220的 PIN I 保证谁把default从Omit里去掉,类型检查就会失败。为什么关
上游卡交代的"合并后复核"就是本卡最后的交付,复核结果是没有残留。
什么时候重开
有人想让表单视图也支持选项级默认值(那是推翻 08-28 的裁定,需要维护者重新决定);或者
FormSelectOptionSchema重新放回了default。标签:摘
pm:on-hold;以completed关闭。Generated by Claude Code