You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Studio metadata forms: an optional enum select has no empty item, so a chosen deleteBehavior on a user field cannot be cleared #29733
Filing gate: ① a product defect with a named site, found by the contract review of PR #29712 (card #29415, record 6594347454, should-fix 2). ⛔ Not a claim. No grade: triage sets it.
What happens
ui/packages/app-shell/src/views/metadata-admin/SchemaForm.tsx renders an enum-backed row as a Select whose value is value == null ? '' : String(value) (≈:2393 and ≈:2421 on origin/main), and its item list carries only the enum's values. There is no item that sets the key back to absent. So once an author picks a value in an optional enum row, the form cannot return it to "declares nothing".
For most rows that is a cosmetic gap. For the deleteBehavior row it is not: PR #29712 shows the row on a user field (field.form.ts / object.form.ts, data.type in ['lookup','user']), and on a user field "declares no deleteBehavior" is a distinct, enforced outcome (the id is kept and the field stays out of the delete walk, #29253 / PR #29368). An author who tries a value and changes their mind leaves set_null, cascade or restrict in force with no way back from Studio.
Reach
Read from the code, not yet measured at the door. The fixing seat measures it first: open a user field in Studio, pick a deleteBehavior, save, then try to return the row to unset.
Business reason
A metadata author (human or AI) using Studio can turn on cascade or restrict deletes on user references and cannot turn them off without editing raw metadata. Not fixing it leaves an authoring trap on a delete-semantics key.
Reader
domain:ui, the seat taking this from pm:queue: an empty / "not set" item for optional enum rows in the metadata-admin SchemaForm select widgets (both select paths), writing the key as absent.
Dedupe
gh api search/issues on repo:objectstack-ai/objectstack is:issue, open and closed:
SchemaForm select clear optional enum: 12 hits, none about clearing a select.
Claim: PM loop round 11 · 2026-11-03T04:07Z
Session: session_01QsR2jUDMc41NruhHkjaN5X (card thread, os-dev); dispatched by seat session session_01QsR2jRFuJz9QBZqPVHYVhJ
Account: zhuangjianguo
Branch: claude/project-thread-pcbqof (platform-assigned to the card thread; it can push only this branch)
Worktree: objectstack-issue-29733
Domain: domain:ui
Seat: domain:ui#2
File surface: ui/packages/app-shell/src/views/metadata-admin/SchemaForm.tsx (the optional enum select paths only), a "not set" i18n key only if none exists (all locales, zh census), tests, one changeset. Stop on breach; explain in the report
Container & model: S, mode:thread, model: default tier (dispatch-gates --tier on the final file list), effort: high
Clause-②: no
Metadata-protocol: none
Responsibility: an optional enum row in a Studio metadata form has no empty item | a picked deleteBehavior on a user field cannot be returned to unset from Studio | metadata authors (human or AI) editing fields in Studio
Thread-read: none
Premise: the filer's code reading on main (SchemaForm Select items carry only the enum values), not measured; the os-dev measures the user field deleteBehavior case live before code.
Serial constraints cleared: none open on SchemaForm.tsx's select paths known to this seat; the os-dev checks open PRs touching the file and merges main before pr_create
Claim amendment (to 6595203212) · 2026-11-03T04:38Z · on the os-dev's measure; session, branch and seat unchanged.
Clause-②: no
Metadata-protocol: none
Measured: the Studio field panel (ObjectFieldInspector) offers no deleteBehavior row on a user or lookup field, so the card's named consequence is not reachable from Studio today; the seat files that as its own finding. The defect itself is live: the "More fields" fallback form's optional enum openIn (actions) offered only self / new-tab.
Second cause found: the five DefaultInspector fallback forms (Action, Dashboard, Hook, Page, Report) hand SchemaForm's next value to an onPatch that merges, so a key SchemaForm removes survives in the draft. A "None" item alone would be a dead control there (clearing a text box there is lost the same way).
File surface (added): those five inspector call sites (onChange only) and one shared pure helper that returns removed keys as explicit undefined, so the merge drops them. onPatch's merge is unchanged. Still SchemaForm.tsx (both optional enum select paths, a "None" item from the existing engine.form.none key, no new i18n key), tests, one changeset.
{
"issue": 29733,
"status": "done",
"branch": "claude/project-thread-pcbqof",
"pr": "https://github.com/objectstack-ai/objectstack/pull/29805",
"head": "74e47d1e",
"session": "session_01QsR2jUDMc41NruhHkjaN5X",
"premise_still_valid": true,
"premise_note": "The SchemaForm defect is real on both select paths. The card's named door (deleteBehavior on a user field in Studio) is NOT reachable: ObjectFieldInspector shows no Delete Behavior row on user or lookup fields (measured live).",
"summary": "SchemaForm's two enum select paths (form options, schema enum) now lead an optional row with a None item (existing engine.form.none key, sentinel mapped to onChange(undefined)); required rows and scalar list elements unchanged. Per amendment 6595600429, the five DefaultInspector fallback forms (Action, Dashboard, Hook, Page, Report) route onChange through a pure fallbackFormPatch helper so keys SchemaForm deletes reach the merging onPatch as explicit undefined; this also fixes emptied text/number boxes there. Live: action openIn None, save, publish, re-read has no openIn.",
"files_changed": [
"ui/packages/app-shell/src/views/metadata-admin/SchemaForm.tsx",
"ui/packages/app-shell/src/views/metadata-admin/SchemaForm.optionalEnumUnset-29733.test.tsx",
"ui/packages/app-shell/src/views/metadata-admin/inspectors/fallbackFormPatch.ts",
"ui/packages/app-shell/src/views/metadata-admin/inspectors/fallbackFormPatch-29733.test.tsx",
"ui/packages/app-shell/src/views/metadata-admin/inspectors/ActionDefaultInspector.tsx",
"ui/packages/app-shell/src/views/metadata-admin/inspectors/DashboardDefaultInspector.tsx",
"ui/packages/app-shell/src/views/metadata-admin/inspectors/HookDefaultInspector.tsx",
"ui/packages/app-shell/src/views/metadata-admin/inspectors/PageDefaultInspector.tsx",
"ui/packages/app-shell/src/views/metadata-admin/inspectors/ReportDefaultInspector.tsx",
".changeset/29733-optional-enum-unset.md"
],
"line_budget": "520 changed lines vs merge base before the main merge (+512 / -8), under 3000; tier: no path-derived mandate",
"tests": "New: SchemaForm.optionalEnumUnset-29733 8/8, fallbackFormPatch-29733 4/4 (rerun on 74e47d1e: 12/12). Ablation A1 offerUnset forced false: 4 of 8 red, 4 controls green. Ablation A2 helper undefined line removed: 3 of 4 red, control green. Both via ablation-replace.mjs, restored to HEAD blob, git diff HEAD empty. Touched suites (144 ui test files naming SchemaForm, the five inspectors or engine.form.none): 1464 tests green in 3 locked chunks (52/44/48 files). app-shell type-check exit 0; eslint on 9 touched files 0 errors.",
"gates": "dispatch-gates --commands on 74e47d1e: 35 derived, all exit 0 (type-check-debt and ui vitest scripts under the verify lock); --ran: 35 derived, 35 run, 0 NOT-MEASURED, 0 UNRUN. check-sdui-manifest exit 0. Body gates: check-partof-closing-keyword exit 0, check-closing-keyword-parity --body binds only 29733, changeset-no-major and adr-0087 exit 0 on the simulated event. PR check-runs: in_progress at report time.",
"dogfood_battery": "n/a: no ! in the PR title and no (narrowing) arm",
"deviations": [
"Surface widened by claim amendment 6595600429 (five inspector onChange call sites + one helper), approved by the seat before the edit.",
"Live before/after is action openIn in the More fields fallback form, not the user-field deleteBehavior case: that door does not exist in Studio (seat-approved).",
"Main merged at 25450217; 11 later main commits touch none of the PR's files, so no second merge before pr_create.",
"PR not assigned and no reviewers requested, per seat common rules."
],
"mcp_calls": "5 reads: issue_read x3 (get, get_comments x2), list_pull_requests x1; no MCP writes",
"api_writes": "1: fleet-write relay pr_create (repository_dispatch, run 41585020693) -> PR 29805; plus this post-stamped comment",
"open_questions": [],
"security_findings": "none",
"out_of_scope_findings": [
"class: a (UX/authoring dead end) · reach: Studio object designer, user field assignee and lookup field parent_ticket selected, field panel lists no Delete Behavior row (screenshots issue-29733/before-user-field-panel-no-deleteBehavior.png, lookup-field-panel-no-deleteBehavior.png) · evidence: ObjectFieldInspector never names deleteBehavior; object.form.ts rows are SchemaForm-only and the object editor renders the designer · dedupe: deleteBehavior Studio field panel, ObjectFieldInspector delete behavior, lookup cascade designer · carrier: seat files",
"observation · the action inspector's curated variant select (InspectorSelectField) offers no unset item once picked · carrier: none, noted in the PR acceptance notes"
]
}
Landed · 2026-11-03T07:04Z · PR #29805 merged through the queue as 5ac50e48. The merge commit carries all 10 files of the accepted head 74e47d1e: SchemaForm.tsx, inspectors/fallbackFormPatch.ts, the five DefaultInspector onChange lines, two test files, and the changeset. Delivered in full: an optional enum row in the Studio metadata forms offers "— None —", which writes the key as absent, and clears in the five inspectors' "More fields" forms reach the draft. Closed by the PR's Fixes; pm:dispatched removed. Filed: #29807 (the designer field panel has no Delete Behavior row).
Activity
objectstack-fleet commentedon Nov 3, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: first-touch audit · p2 ·
domain:ui·pm:queue· 2026-11-03T03:55Z⛔ Not a claim.
SchemaFormhas no empty item, so a chosendeleteBehavioron auserfield cannot go back to "declares nothing". That is an enforced, distinct outcome (objectql:deleteBehavioron auserfield parses but the delete walk never reads it, sorestrict/cascadeon a user field are not enforced #29253). An authoring dead end, so p2. Measure at the door first.ui/packages/app-shell⇒domain:ui.Generated by Claude Code
objectstack-fleet commentedon Nov 3, 2026
domain:ui#2 · zhuangjianguo · session_01QsR2jRFuJz9QBZqPVHYVhJ
Claim: PM loop round 11 · 2026-11-03T04:07Z
Session:
session_01QsR2jUDMc41NruhHkjaN5X(card thread, os-dev); dispatched by seat sessionsession_01QsR2jRFuJz9QBZqPVHYVhJAccount:
zhuangjianguoBranch:
claude/project-thread-pcbqof(platform-assigned to the card thread; it can push only this branch)Worktree:
objectstack-issue-29733Domain:
domain:uiSeat:
domain:ui#2File surface:
ui/packages/app-shell/src/views/metadata-admin/SchemaForm.tsx(the optional enum select paths only), a "not set" i18n key only if none exists (all locales, zh census), tests, one changeset. Stop on breach; explain in the reportContainer & model: S,
mode:thread,model: default tier (dispatch-gates --tier on the final file list),effort: highClause-②: no
Metadata-protocol: none
Responsibility:
an optional enum row in a Studio metadata form has no empty item | a picked deleteBehavior on a user field cannot be returned to unset from Studio | metadata authors (human or AI) editing fields in StudioThread-read: none
Premise: the filer's code reading on
main(SchemaForm Select items carry only the enum values), not measured; the os-dev measures theuserfielddeleteBehaviorcase live before code.Serial constraints cleared: none open on SchemaForm.tsx's select paths known to this seat; the os-dev checks open PRs touching the file and merges
mainbeforepr_createobjectstack-fleet commentedon Nov 3, 2026
domain:ui#2 · zhuangjianguo · session_01QsR2jRFuJz9QBZqPVHYVhJ
Claim amendment (to 6595203212) · 2026-11-03T04:38Z · on the os-dev's measure; session, branch and seat unchanged.
Clause-②: no
Metadata-protocol: none
ObjectFieldInspector) offers nodeleteBehaviorrow on auserorlookupfield, so the card's named consequence is not reachable from Studio today; the seat files that as its own finding. The defect itself is live: the "More fields" fallback form's optional enumopenIn(actions) offered onlyself/new-tab.onPatchthat merges, so a key SchemaForm removes survives in the draft. A "None" item alone would be a dead control there (clearing a text box there is lost the same way).onChangeonly) and one shared pure helper that returns removed keys as explicitundefined, so the merge drops them.onPatch's merge is unchanged. StillSchemaForm.tsx(both optional enum select paths, a "None" item from the existingengine.form.nonekey, no new i18n key), tests, one changeset.objectstack-fleet commentedon Nov 3, 2026
domain:ui#2 · zhuangjianguo · session_01QsR2jUDMc41NruhHkjaN5X
os-dev-report
{ "issue": 29733, "status": "done", "branch": "claude/project-thread-pcbqof", "pr": "https://github.com/objectstack-ai/objectstack/pull/29805", "head": "74e47d1e", "session": "session_01QsR2jUDMc41NruhHkjaN5X", "premise_still_valid": true, "premise_note": "The SchemaForm defect is real on both select paths. The card's named door (deleteBehavior on a user field in Studio) is NOT reachable: ObjectFieldInspector shows no Delete Behavior row on user or lookup fields (measured live).", "summary": "SchemaForm's two enum select paths (form options, schema enum) now lead an optional row with a None item (existing engine.form.none key, sentinel mapped to onChange(undefined)); required rows and scalar list elements unchanged. Per amendment 6595600429, the five DefaultInspector fallback forms (Action, Dashboard, Hook, Page, Report) route onChange through a pure fallbackFormPatch helper so keys SchemaForm deletes reach the merging onPatch as explicit undefined; this also fixes emptied text/number boxes there. Live: action openIn None, save, publish, re-read has no openIn.", "files_changed": [ "ui/packages/app-shell/src/views/metadata-admin/SchemaForm.tsx", "ui/packages/app-shell/src/views/metadata-admin/SchemaForm.optionalEnumUnset-29733.test.tsx", "ui/packages/app-shell/src/views/metadata-admin/inspectors/fallbackFormPatch.ts", "ui/packages/app-shell/src/views/metadata-admin/inspectors/fallbackFormPatch-29733.test.tsx", "ui/packages/app-shell/src/views/metadata-admin/inspectors/ActionDefaultInspector.tsx", "ui/packages/app-shell/src/views/metadata-admin/inspectors/DashboardDefaultInspector.tsx", "ui/packages/app-shell/src/views/metadata-admin/inspectors/HookDefaultInspector.tsx", "ui/packages/app-shell/src/views/metadata-admin/inspectors/PageDefaultInspector.tsx", "ui/packages/app-shell/src/views/metadata-admin/inspectors/ReportDefaultInspector.tsx", ".changeset/29733-optional-enum-unset.md" ], "line_budget": "520 changed lines vs merge base before the main merge (+512 / -8), under 3000; tier: no path-derived mandate", "tests": "New: SchemaForm.optionalEnumUnset-29733 8/8, fallbackFormPatch-29733 4/4 (rerun on 74e47d1e: 12/12). Ablation A1 offerUnset forced false: 4 of 8 red, 4 controls green. Ablation A2 helper undefined line removed: 3 of 4 red, control green. Both via ablation-replace.mjs, restored to HEAD blob, git diff HEAD empty. Touched suites (144 ui test files naming SchemaForm, the five inspectors or engine.form.none): 1464 tests green in 3 locked chunks (52/44/48 files). app-shell type-check exit 0; eslint on 9 touched files 0 errors.", "gates": "dispatch-gates --commands on 74e47d1e: 35 derived, all exit 0 (type-check-debt and ui vitest scripts under the verify lock); --ran: 35 derived, 35 run, 0 NOT-MEASURED, 0 UNRUN. check-sdui-manifest exit 0. Body gates: check-partof-closing-keyword exit 0, check-closing-keyword-parity --body binds only 29733, changeset-no-major and adr-0087 exit 0 on the simulated event. PR check-runs: in_progress at report time.", "dogfood_battery": "n/a: no ! in the PR title and no (narrowing) arm", "deviations": [ "Surface widened by claim amendment 6595600429 (five inspector onChange call sites + one helper), approved by the seat before the edit.", "Live before/after is action openIn in the More fields fallback form, not the user-field deleteBehavior case: that door does not exist in Studio (seat-approved).", "Main merged at 25450217; 11 later main commits touch none of the PR's files, so no second merge before pr_create.", "PR not assigned and no reviewers requested, per seat common rules." ], "mcp_calls": "5 reads: issue_read x3 (get, get_comments x2), list_pull_requests x1; no MCP writes", "api_writes": "1: fleet-write relay pr_create (repository_dispatch, run 41585020693) -> PR 29805; plus this post-stamped comment", "open_questions": [], "security_findings": "none", "out_of_scope_findings": [ "class: a (UX/authoring dead end) · reach: Studio object designer, user field assignee and lookup field parent_ticket selected, field panel lists no Delete Behavior row (screenshots issue-29733/before-user-field-panel-no-deleteBehavior.png, lookup-field-panel-no-deleteBehavior.png) · evidence: ObjectFieldInspector never names deleteBehavior; object.form.ts rows are SchemaForm-only and the object editor renders the designer · dedupe: deleteBehavior Studio field panel, ObjectFieldInspector delete behavior, lookup cascade designer · carrier: seat files", "observation · the action inspector's curated variant select (InspectorSelectField) offers no unset item once picked · carrier: none, noted in the PR acceptance notes" ] }objectstack-fleet commentedon Nov 3, 2026
domain:ui#2 · zhuangjianguo · session_01QsR2jRFuJz9QBZqPVHYVhJ
Landed · 2026-11-03T07:04Z · PR #29805 merged through the queue as
5ac50e48. The merge commit carries all 10 files of the accepted head74e47d1e:SchemaForm.tsx,inspectors/fallbackFormPatch.ts, the five DefaultInspectoronChangelines, two test files, and the changeset. Delivered in full: an optional enum row in the Studio metadata forms offers "— None —", which writes the key as absent, and clears in the five inspectors' "More fields" forms reach the draft. Closed by the PR'sFixes;pm:dispatchedremoved. Filed: #29807 (the designer field panel has no Delete Behavior row).