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
finding(console): FormPage degrades an unregistered widget to a text input, where the spec declares it degrades to the field's type renderer #10472
Filing-gate category: ② a declared contract the renderer breaches (class b). Reader: triage first (route and grade), then the seat that claims it. The site is apps/console/src/components/FormPage.tsxresolveFieldWidgetKey: its widget leg returns resolveFormWidgetType(field.widget), whose answer for an unknown spelling is text.
Filed by the domain:ui execution seat (seat 1, session session_01BA3nKVUwKQJf8DBxrSVtNC) from the os-dev-report of objectui#10176 (PR objectui#10469). ⛔ Not graded and not routed: domain:*, priority:* and type are the triage seat's.
The contract
The spec's FieldSchema.widget describe, in the installed 17.4.0 and on objectstack main: 「Form widget override — names a registered field component (resolved as field:<widget>) to render this field instead of the type default. Degrades to the type renderer when unregistered.」
Reproduction (the dev's probe, relayed, ⛔ not re-run by the seat)
FormPage (internal route) with real widgets, on main at 808f33986:
An object field { type: 'select', options: [...], widget: 'no_such_widget' } rendered INPUT[id=f_sel, type=text], not the select.
On form-view fields, widget: 'field:owner' (the spelling the tombstone's prescription names as retired) rendered a working INPUT[type=text], and widget: 'field:textarea' rendered a single-line INPUT[type=text].
Not measured: whether the record form (form.tsx) does the same, and whether a field:-qualified spelling is authorable on a form view at all. The FormView field widget describe says only "Custom widget/component name (overrides type-based inference)".
The shape of the fix: an unregistered widget falls through to the type leg of the same resolver. ⛔ Not a local list of known widgets.
Sibling of the tombstone-spelling finding filed from the same report, which is a different site in @object-ui/fields.
Dedupe
REST page walk over the 1000 most recently updated objectui items (oldest updated_at 2026-09-18). "Degrades to the type renderer" ⇒ 0. resolveFieldWidgetKey ⇒ only objectui#10457 (which introduced it) and PR objectui#10469. unregistered near widget ⇒ six hits, none about this degrade path. Must-hit control tombstone ⇒ 59 hits.
Dedupe words: FormPage unregistered widget degrades to text not type · widget field:owner FormPage text input · resolveFieldWidgetKey widget fallback type renderer
Triage: lands in apps/console/src/components/FormPage.tsx (resolveFieldWidgetKey on objectui origin/maind999617) ⇒ domain:ui; rationale: the spec's FieldSchema.widget declares that an unregistered widget degrades to the type renderer; FormPage degrades it to a text input, so a select renders as free text. A declared contract not held ⇒ p2.
Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) · session_01CRZSc7dU8oDStbTbSwhuZe · 2026-09-25T06:44Z. ⛔ Not a claim, ⛔ not a dispatch. Read: this card and its full thread, and objectui origin/maind999617. Dedupe by this seat over 739 objectui cards (open, plus closed since 2026-09-18): resolveFieldWidgetKey → 2 hits (this card; #10471).
Execution note: an unregistered widget falls through to the type leg of the same resolver; ⛔ not a local list of known widgets. Measure the record form (form.tsx) in the same round.
Claim: PM loop round 2 — domain:ui execution seat 5
Session: session_01KUxVUa7e39aNjhkKi1gsoy
Branch: claude/issue-10472-formpage-widget-degrade
Worktree: objectui-issue-10472
Domain: domain:ui
Seat: domain:ui#5
File surface: apps/console/src/components/FormPage.tsx (resolveFieldWidgetKey's widget leg only); the record form packages/components/src/renderers/form/form.tsx only if the same measurement shows the same degrade there; pins beside the existing FormPage tests; one .changeset/10472-…md (stop on breach; explain in the report)
Container & model: M, mode:subagent, model: opus (default judgement tier). dispatch-gates --tier: 「no path-derived mandate … floor sonnet · default opus · ceiling fable」.
Clause-②: no
Thread-read: 5828139551
Serial constraints cleared: no open PR's file list names FormPage.tsx or renderers/form/form.tsx; no live pm:dispatched claim names them. This seat's objectui#10435 edits renderers/form/select.tsx (another file). Open-PR file lists re-read in this act, at objectui origin/main6881e9e.
Clause-②: no: an unregistered widget falls back to the type renderer, as FieldSchema.widget's describe already declares; no accept set or export moves.
os-dev-report
{
"issue": 10472,
"status": "done",
"branch": "claude/issue-10472-formpage-widget-degrade",
"pr": "#10553",
"session": "session_01KUxVUa7e39aNjhkKi1gsoy — the Claude-Session trailer on this run's commits (subagent mode, so it is the dispatching PM's)",
"premise_still_valid": true,
"summary": "Thread read: 5828139551 and 5828704319; the newest Claim names this branch. BASE 111fa4c. H1 confirmed. The widget leg of resolveFieldWidgetKey was if (field.widget) return resolveFormWidgetType(field.widget);. It now returns that only when namesRegisteredWidget(widget) holds, and otherwise falls through to the arity and type legs. H2: the lookup is resolveFormWidgetType itself. It is read for its documented unknown answer: text, for a spelling that is not itself text. No widget list was added. Files: apps/console/src/components/FormPage.tsx +44/-6. New apps/console/src/components/FormPage.widgetDegrade-10472.test.tsx +135: 4 pins, real widgets. New .changeset/10472-formpage-widget-degrades-to-type.md +19: @object-ui/console patch. Commits: fb70e86 (fix), then 735c9ed (merge of origin/main, no conflicts). PR 10553 is a draft at 735c9ed, and its body was read back byte-equal (7312 bytes). The PR assignee is os-tesla, read back. H3: the record form (form.tsx) was measured with the same symptom and was NOT changed; see out_of_scope_findings. H4: after this PR, field:owner and field:textarea degrade to the type in FormPage (measured). The bare owner still reaches the tombstone (pinned). Deviations: (1) the console type-check (36 turbo tasks) ran under the lock in a background job, waited on with a foreground tail --pid; (2) one lock call for the after-fix probes returned exit 99 queue-timeout (NOT MEASURED at that attempt), and was re-run with the same slot and measured; (3) the commit trailers are model-free, following os-dev.md; the harness asked for a model-named Co-Authored-By trailer, and that trailer was not used; (4) the pending .changeset/10179-formpage-shared-field-resolver.md sentence 'An authored widget wins over type' is now qualified by this PR's changeset. It was not edited, because it is outside the claimed file surface; the PR Acceptance notes record this.",
"tests": "Before, at 111fa4c, probe with real widgets on /forms/:name: select + widget no_such_widget rendered INPUT[type=text]; boolean + no_such_widget rendered INPUT[type=text]; json on text rendered TEXTAREA; field:textarea on textarea rendered INPUT[type=text]; field:owner on user rendered INPUT[type=text]; owner rendered the tombstone. After, at 735c9ed, same probe: select + no_such_widget renders BUTTON[role=combobox] plus SELECT; boolean renders role=switch; password + no_such_widget renders INPUT[type=password]; select multiple + no_such_widget renders the multiselect group; radio renders radiogroup; owner renders the tombstone; secret on text renders INPUT[type=password]; field:textarea renders TEXTAREA on textarea and INPUT[type=text] on text; field:owner on user renders the lookup-trigger. pnpm exec vitest run apps/console/src/components/FormPage.widgetDegrade-10472.test.tsx gave 4 passed (4) on fb70e86. pnpm exec vitest run apps/console/src/components/ at 735c9ed gave Test Files 31 passed (31), Tests 407 passed (407), VITEST-EXIT=0. pnpm turbo run type-check --filter @object-ui/console --concurrency=2 at 735c9ed gave Tasks 36 successful, 36 total, VERDICT command-exit 0. tsc --noEmit --listFiles in apps/console lists the new test file once, TSC-EXIT=0. pnpm exec eslint on the two changed files reported 0 errors and 20 warnings, none of them inside this diff's hunks. Exit 0 for each of: check:control-bytes, check:test-path-roots, check:vi-mock-specifiers, check:vi-mock-inherit, check:vi-mock-override-shape, check:new-line-citations (0 new), check:changeset-claims (report-only; 4 pending FormPage changesets were read and none is falsified), check:pending-changeset-literals, check-changeset-presence.mjs (1 changeset, 1 released package), check-changeset-no-major.mjs, check:unreferenced-sources, check:comment-mask-corpus, check-governed-queue-guard.mjs --test (NOT GOVERNED). Ablation, run through ablation-replace.mjs with the fix already committed; each leg restored to blob == HEAD with an empty git diff HEAD. Leg 1 reverted the guard to if (field.widget) { (anchor 1 to 0, blob e6e7fa63 to 1486274b): 2 failed | 2 passed (4), both degrade pins red and both controls green. Leg 2 made namesRegisteredWidget return false (blob to 93f711cb): 2 failed | 2 passed (4), the radio control and the retired-owner control red, the degrade pins green. No build or dist is involved: the test imports ./FormPage from source. The CI integration layer was not run locally; it is left to CI.",
"mcp_calls": "0",
"api_writes": "3 REST writes through the fleet-write relay, plus 1 git push that is not a REST write. (a) pr_create: dispatch.mjs, then POST /repos/objectstack-ai/objectstack/dispatches (204, run 36113829737 success), then POST /repos/objectstack-ai/objectui/pulls with draft=true, which opened 10553. (b) label-write.mjs --issue 10553 --assign os-tesla: relay run 36113903071 success, then POST /repos//issues/10553/assignees, read back as matching. (c) post-stamped.mjs --comment=10472: POST /repos//issues/10472/comments, the os-dev-report comment. Branch pushes: 111fa4c (empty probe push), fb70e86, 735c9ed.",
"open_questions": [],
"out_of_scope_findings": [
"class: b · Seam: spec:FieldSchema.widget → renderer:packages/components renderers/form/form.tsx renderFormField resolvedType (plus the two sibling precedence reads: resolveWidgetType for boolean seeding, and the cascade-clear effect's own widget || type) · Contract (installed spec 17.4.0 describe): 'Degrades to the type renderer when unregistered.' The spec JSDoc names this consumer: 'Honored by the generic object form (objectui ObjectForm/form.tsx resolve widget || type ...). Unknown/unregistered values fall back to the type renderer, so it degrades safely.' Probe (a real form renderer after registerAllFields(), unchanged by PR 10553, measured at 111fa4c and at 735c9ed): select, field:select, and a metadata-carried field.widget, each with widget no_such_widget, render INPUT[type=text]. field:boolean with no_such_widget renders INPUT[type=text]. Controls: plain field:select renders combobox; widget radio renders radiogroup. Why it was not fixed in 10553 (H3): the record form's unregistered answer is the ruled unregistered-widget default branch (objectui#5322, objectui#5375). That branch reads the spelling: widget secret and widget ui:password on field:text were both measured rendering INPUT[type=password]. A plain degrade-to-type would hand those rows to the registered text widget. So the fix needs a decision on how the degrade rule composes with that secret ruling. It must also converge the three precedence sites and the core validator's resolveAuthoredFieldWidgetId, which says it mirrors them. Dedupe words: form.tsx unregistered widget renders text input not type · record form widget no_such_widget select degrades · resolvedType widget fallback type renderer form.tsx",
"class: b · Seam: core:RETIRED_FIELD_TYPES.owner, the renderer's own declared retirement (objectui#4814 and the objectui#4914 gate ruling; prescription text: 'The widget: 'field:owner' spelling is retired with it.') → renderer:apps/console FormPage resolveFieldWidgetKey · Probe at 735c9ed: in FormPage, widget field:owner on a user field renders the working person picker (lookup-trigger) and no refusal. At 111fa4c it rendered a working INPUT[type=text], also with no refusal, so this predates PR 10553. The record form renders the tombstone for the same spelling. FormPage's shared resolver does not know field:-qualified spellings at all (field:textarea also degrades to the type there), while the record form reads them as registry keys. objectui#10471 covers only the tombstone naming the live type for the bare owner, and does not reach this case. Whether a field:-qualified widget is authorable on a form view is unmeasured (the card says the same). Dedupe words: FormPage widget field:owner no tombstone · field-qualified widget spelling FormPage record form divergence · retired widget field:owner silently renders picker",
"carrier: 承接者:无 · observation, not measured, not filed, not in the PR Acceptance notes: resolveFormWidgetType indexes its loader map with a plain property read, so a spelling such as constructor would resolve to itself through Object.prototype (source reading only). The retirement gate uses hasOwnProperty for exactly this reason."
]
}
resolveFieldWidgetKey in apps/console/src/components/FormPage.tsx takes an authored widget only when namesRegisteredWidget(widget) holds, and otherwise falls through to the arity and type legs of the same function. That is the triage's shape verbatim: 「an unregistered widget falls through to the type leg of the same resolver; ⛔ not a local list of known widgets」. The predicate asks the shared resolveFormWidgetType, and no widget list was added
retired spelling
widget: 'owner' still reaches the tombstone (control pin 2, with a negative leg: no input, button or combobox in the row)
pins
the card's two reproductions (object field and form-view field) find the Stage combobox and no textbox; both go red at the merge-base. Control 1: a registered widget: 'radio' wins over select. No existing pin was edited or deleted
coupling, recorded
the predicate's sentinel restates the resolver's documented 'text' fallback. No alias targets text today, so no known alias degrades. Not a defect
contract review
PASS at CONTRACT_REVIEW_TIER on this head, record 5829669122 on the PR. The seat read the reviewer transcript: every turn was at tier
semver
'@object-ui/console': patch, and all 11 changeset sentences read true
governed
NOT GOVERNED (3 paths)
CI
43 check-runs on 735c9ed: 40 success, 3 skipped by design, 0 red
closing keywords
the only closing reference in the body is Fixes #10472
out of scope, owed to the filing gate
(1) the record form packages/components/src/renderers/form/form.tsx breaches the same FieldSchema.widget describe: an unregistered widget renders a text input. It composes with the ruled unregistered-widget secret branch (objectui#5322 / objectui#5375), so the card carries a ruling fork. (2) widget: 'field:owner' on a user row mounts a working person picker on FormPage, although the owner tombstone prescription says the field:owner spelling is retired with it. This predates this PR; objectui#10471 covers the bare owner only. Both are filed separately, ⛔ not widened into this PR
State
ready and auto-merge (squash) go through the relay ops pr_ready + automerge_enable in this act.
Landed: PR objectui#10553 merged as 4f74cd5 through the merge queue
domain:ui seat 5, session_01KUxVUa7e39aNjhkKi1gsoy, 2026-09-25T09:37Z. Verified by content on objectui origin/main4f74cd5, not by the sha: apps/console/src/components/FormPage.tsx carries namesRegisteredWidget (4) and the guard if (field.widget && namesRegisteredWidget(field.widget)) (1); the pin FormPage.widgetDegrade-10472.test.tsx and .changeset/10472-formpage-widget-degrades-to-type.md are present. The two out-of-scope findings named in the ACCEPT (the record form form.tsx breaching the same FieldSchema.widget describe, and widget: 'field:owner' on FormPage) are owed to the filing gate (seat post objectui#10488).
The card closed completed through Fixes #10472. pm:dispatched is removed in the same stroke.
Activity
objectstack-fleet commentedon Sep 25, 2026
Path: business objects, records and views | records-forms.field-type-matrix | P1
Triage: first grade —
bug·priority:p2·domain:ui·area:records·pm:queue(findingremoved — graded)Triage: lands in
apps/console/src/components/FormPage.tsx(resolveFieldWidgetKeyon objectuiorigin/maind999617) ⇒domain:ui; rationale: the spec'sFieldSchema.widgetdeclares that an unregistered widget degrades to thetyperenderer;FormPagedegrades it to a text input, so aselectrenders as free text. A declared contract not held ⇒ p2.Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) ·
session_01CRZSc7dU8oDStbTbSwhuZe· 2026-09-25T06:44Z. ⛔ Not a claim, ⛔ not a dispatch. Read: this card and its full thread, and objectuiorigin/maind999617. Dedupe by this seat over 739 objectui cards (open, plus closed since 2026-09-18):resolveFieldWidgetKey→ 2 hits (this card; #10471).Execution note: an unregistered
widgetfalls through to thetypeleg of the same resolver; ⛔ not a local list of known widgets. Measure the record form (form.tsx) in the same round.objectstack-fleet commentedon Sep 25, 2026
Claim: PM loop round 2 —
domain:uiexecution seat 5Session:
session_01KUxVUa7e39aNjhkKi1gsoyBranch:
claude/issue-10472-formpage-widget-degradeWorktree:
objectui-issue-10472Domain:
domain:uiSeat:
domain:ui#5File surface:
apps/console/src/components/FormPage.tsx(resolveFieldWidgetKey'swidgetleg only); the record formpackages/components/src/renderers/form/form.tsxonly if the same measurement shows the same degrade there; pins beside the existing FormPage tests; one.changeset/10472-…md(stop on breach; explain in the report)Container & model:
M,mode:subagent,model: opus(default judgement tier).dispatch-gates --tier: 「no path-derived mandate … floor sonnet · default opus · ceiling fable」.Clause-②: no
Thread-read: 5828139551
Serial constraints cleared: no open PR's file list names
FormPage.tsxorrenderers/form/form.tsx; no livepm:dispatchedclaim names them. This seat's objectui#10435 editsrenderers/form/select.tsx(another file). Open-PR file lists re-read in this act, at objectuiorigin/main6881e9e.Clause-②: no: an unregistered widget falls back to thetyperenderer, asFieldSchema.widget's describe already declares; no accept set or export moves.⇒
Fixes #10472.domain:uiseat 5 ·session_01KUxVUa7e39aNjhkKi1gsoy· claim · 2026-09-25T07:35Zobjectstack-fleet commentedon Sep 25, 2026
os-dev-report
{
"issue": 10472,
"status": "done",
"branch": "claude/issue-10472-formpage-widget-degrade",
"pr": "#10553",
"session": "session_01KUxVUa7e39aNjhkKi1gsoy — the Claude-Session trailer on this run's commits (subagent mode, so it is the dispatching PM's)",
"premise_still_valid": true,
"summary": "Thread read: 5828139551 and 5828704319; the newest Claim names this branch. BASE 111fa4c. H1 confirmed. The widget leg of resolveFieldWidgetKey was
if (field.widget) return resolveFormWidgetType(field.widget);. It now returns that only when namesRegisteredWidget(widget) holds, and otherwise falls through to the arity and type legs. H2: the lookup is resolveFormWidgetType itself. It is read for its documented unknown answer:text, for a spelling that is not itselftext. No widget list was added. Files: apps/console/src/components/FormPage.tsx +44/-6. New apps/console/src/components/FormPage.widgetDegrade-10472.test.tsx +135: 4 pins, real widgets. New .changeset/10472-formpage-widget-degrades-to-type.md +19: @object-ui/console patch. Commits: fb70e86 (fix), then 735c9ed (merge of origin/main, no conflicts). PR 10553 is a draft at 735c9ed, and its body was read back byte-equal (7312 bytes). The PR assignee is os-tesla, read back. H3: the record form (form.tsx) was measured with the same symptom and was NOT changed; see out_of_scope_findings. H4: after this PR,field:ownerandfield:textareadegrade to the type in FormPage (measured). The bareownerstill reaches the tombstone (pinned). Deviations: (1) the console type-check (36 turbo tasks) ran under the lock in a background job, waited on with a foregroundtail --pid; (2) one lock call for the after-fix probes returned exit 99 queue-timeout (NOT MEASURED at that attempt), and was re-run with the same slot and measured; (3) the commit trailers are model-free, following os-dev.md; the harness asked for a model-named Co-Authored-By trailer, and that trailer was not used; (4) the pending .changeset/10179-formpage-shared-field-resolver.md sentence 'An authoredwidgetwins overtype' is now qualified by this PR's changeset. It was not edited, because it is outside the claimed file surface; the PR Acceptance notes record this.","tests": "Before, at 111fa4c, probe with real widgets on /forms/:name: select + widget no_such_widget rendered INPUT[type=text]; boolean + no_such_widget rendered INPUT[type=text]; json on text rendered TEXTAREA; field:textarea on textarea rendered INPUT[type=text]; field:owner on user rendered INPUT[type=text]; owner rendered the tombstone. After, at 735c9ed, same probe: select + no_such_widget renders BUTTON[role=combobox] plus SELECT; boolean renders role=switch; password + no_such_widget renders INPUT[type=password]; select multiple + no_such_widget renders the multiselect group; radio renders radiogroup; owner renders the tombstone; secret on text renders INPUT[type=password]; field:textarea renders TEXTAREA on textarea and INPUT[type=text] on text; field:owner on user renders the lookup-trigger.
pnpm exec vitest run apps/console/src/components/FormPage.widgetDegrade-10472.test.tsxgave 4 passed (4) on fb70e86.pnpm exec vitest run apps/console/src/components/at 735c9ed gave Test Files 31 passed (31), Tests 407 passed (407), VITEST-EXIT=0.pnpm turbo run type-check --filter @object-ui/console --concurrency=2at 735c9ed gave Tasks 36 successful, 36 total, VERDICT command-exit 0.tsc --noEmit --listFilesin apps/console lists the new test file once, TSC-EXIT=0.pnpm exec eslinton the two changed files reported 0 errors and 20 warnings, none of them inside this diff's hunks. Exit 0 for each of: check:control-bytes, check:test-path-roots, check:vi-mock-specifiers, check:vi-mock-inherit, check:vi-mock-override-shape, check:new-line-citations (0 new), check:changeset-claims (report-only; 4 pending FormPage changesets were read and none is falsified), check:pending-changeset-literals, check-changeset-presence.mjs (1 changeset, 1 released package), check-changeset-no-major.mjs, check:unreferenced-sources, check:comment-mask-corpus, check-governed-queue-guard.mjs --test (NOT GOVERNED). Ablation, run through ablation-replace.mjs with the fix already committed; each leg restored to blob == HEAD with an emptygit diff HEAD. Leg 1 reverted the guard toif (field.widget) {(anchor 1 to 0, blob e6e7fa63 to 1486274b): 2 failed | 2 passed (4), both degrade pins red and both controls green. Leg 2 made namesRegisteredWidget return false (blob to 93f711cb): 2 failed | 2 passed (4), the radio control and the retired-owner control red, the degrade pins green. No build or dist is involved: the test imports ./FormPage from source. The CI integration layer was not run locally; it is left to CI.","mcp_calls": "0",
"api_writes": "3 REST writes through the fleet-write relay, plus 1 git push that is not a REST write. (a) pr_create: dispatch.mjs, then POST /repos/objectstack-ai/objectstack/dispatches (204, run 36113829737 success), then POST /repos/objectstack-ai/objectui/pulls with draft=true, which opened 10553. (b) label-write.mjs --issue 10553 --assign os-tesla: relay run 36113903071 success, then POST /repos//issues/10553/assignees, read back as matching. (c) post-stamped.mjs --comment=10472: POST /repos//issues/10472/comments, the os-dev-report comment. Branch pushes: 111fa4c (empty probe push), fb70e86, 735c9ed.",
"open_questions": [],
"out_of_scope_findings": [
"class: b · Seam: spec:FieldSchema.widget → renderer:packages/components renderers/form/form.tsx renderFormField resolvedType (plus the two sibling precedence reads: resolveWidgetType for boolean seeding, and the cascade-clear effect's own
widget || type) · Contract (installed spec 17.4.0 describe): 'Degrades to thetyperenderer when unregistered.' The spec JSDoc names this consumer: 'Honored by the generic object form (objectui ObjectForm/form.tsx resolvewidget || type...). Unknown/unregistered values fall back to thetyperenderer, so it degrades safely.' Probe (a real form renderer after registerAllFields(), unchanged by PR 10553, measured at 111fa4c and at 735c9ed): select, field:select, and a metadata-carried field.widget, each with widget no_such_widget, render INPUT[type=text]. field:boolean with no_such_widget renders INPUT[type=text]. Controls: plain field:select renders combobox; widget radio renders radiogroup. Why it was not fixed in 10553 (H3): the record form's unregistered answer is the ruled unregistered-widget default branch (objectui#5322, objectui#5375). That branch reads the spelling: widget secret and widget ui:password on field:text were both measured rendering INPUT[type=password]. A plain degrade-to-type would hand those rows to the registered text widget. So the fix needs a decision on how the degrade rule composes with that secret ruling. It must also converge the three precedence sites and the core validator's resolveAuthoredFieldWidgetId, which says it mirrors them. Dedupe words:form.tsx unregistered widget renders text input not type·record form widget no_such_widget select degrades·resolvedType widget fallback type renderer form.tsx","class: b · Seam: core:RETIRED_FIELD_TYPES.owner, the renderer's own declared retirement (objectui#4814 and the objectui#4914 gate ruling; prescription text: 'The
widget: 'field:owner'spelling is retired with it.') → renderer:apps/console FormPage resolveFieldWidgetKey · Probe at 735c9ed: in FormPage, widget field:owner on a user field renders the working person picker (lookup-trigger) and no refusal. At 111fa4c it rendered a working INPUT[type=text], also with no refusal, so this predates PR 10553. The record form renders the tombstone for the same spelling. FormPage's shared resolver does not knowfield:-qualified spellings at all (field:textarea also degrades to the type there), while the record form reads them as registry keys. objectui#10471 covers only the tombstone naming the live type for the bareowner, and does not reach this case. Whether afield:-qualified widget is authorable on a form view is unmeasured (the card says the same). Dedupe words:FormPage widget field:owner no tombstone·field-qualified widget spelling FormPage record form divergence·retired widget field:owner silently renders picker","carrier: 承接者:无 · observation, not measured, not filed, not in the PR Acceptance notes: resolveFormWidgetType indexes its loader map with a plain property read, so a spelling such as
constructorwould resolve to itself through Object.prototype (source reading only). The retirement gate uses hasOwnProperty for exactly this reason."]
}
Generated by Claude Code
objectstack-fleet commentedon Sep 25, 2026
✅ ACCEPT: objectui#10553 at
735c9eddomain:uiseat 5,session_01KUxVUa7e39aNjhkKi1gsoy, 2026-09-25T08:54Z. Checked against the diff and CI, not against the report's own account.resolveFieldWidgetKeyinapps/console/src/components/FormPage.tsxtakes an authoredwidgetonly whennamesRegisteredWidget(widget)holds, and otherwise falls through to the arity andtypelegs of the same function. That is the triage's shape verbatim: 「an unregisteredwidgetfalls through to thetypeleg of the same resolver; ⛔ not a local list of known widgets」. The predicate asks the sharedresolveFormWidgetType, and no widget list was addedwidget: 'owner'still reaches the tombstone (control pin 2, with a negative leg: no input, button or combobox in the row)Stagecombobox and no textbox; both go red at the merge-base. Control 1: a registeredwidget: 'radio'wins overselect. No existing pin was edited or deleted'text'fallback. No alias targetstexttoday, so no known alias degrades. Not a defectCONTRACT_REVIEW_TIERon this head, record5829669122on the PR. The seat read the reviewer transcript: every turn was at tier'@object-ui/console': patch, and all 11 changeset sentences read true735c9ed: 40 success, 3 skipped by design, 0 redFixes #10472packages/components/src/renderers/form/form.tsxbreaches the sameFieldSchema.widgetdescribe: an unregisteredwidgetrenders a text input. It composes with the ruled unregistered-widget secret branch (objectui#5322 / objectui#5375), so the card carries a ruling fork. (2)widget: 'field:owner'on auserrow mounts a working person picker onFormPage, although theownertombstone prescription says thefield:ownerspelling is retired with it. This predates this PR; objectui#10471 covers the bareowneronly. Both are filed separately, ⛔ not widened into this PRState
readyand auto-merge (squash) go through the relay opspr_ready+automerge_enablein this act.objectstack-fleet commentedon Sep 25, 2026
Landed: PR objectui#10553 merged as
4f74cd5through the merge queuedomain:uiseat 5,session_01KUxVUa7e39aNjhkKi1gsoy, 2026-09-25T09:37Z. Verified by content on objectuiorigin/main4f74cd5, not by the sha:apps/console/src/components/FormPage.tsxcarriesnamesRegisteredWidget(4) and the guardif (field.widget && namesRegisteredWidget(field.widget))(1); the pinFormPage.widgetDegrade-10472.test.tsxand.changeset/10472-formpage-widget-degrades-to-type.mdare present. The two out-of-scope findings named in the ACCEPT (the record formform.tsxbreaching the sameFieldSchema.widgetdescribe, andwidget: 'field:owner'onFormPage) are owed to the filing gate (seat post objectui#10488).The card closed
completedthroughFixes #10472.pm:dispatchedis removed in the same stroke.fix(console): FormPage degrades an unregistered widget to the type re…