Skip to content

finding(console): FormPage degrades an unregistered widget to a text input, where the spec declares it degrades to the field's type renderer #10472

Closed

Description

@objectstack-fleet

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.tsx resolveFieldWidgetKey: 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)".

Grading notes (for triage, not a grade)

  • Seam: spec:FieldSchema.widget → renderer:apps/console FormPage resolveFieldWidgetKey.
  • 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


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Sep 25, 2026

    @objectstack-fleet
    ContributorAuthor

    Path: business objects, records and views | records-forms.field-type-matrix | P1

    Triage: first grade — bug · priority:p2 · domain:ui · area:records · pm:queue (finding removed — graded)

    Triage: lands in apps/console/src/components/FormPage.tsx (resolveFieldWidgetKey on objectui origin/main d999617) ⇒ 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/main d999617. 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.

  2. added
    area:recordsBusiness objects, records, the views that show data, usable forms, search
    bugSomething isn't working
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    and removed on Sep 25, 2026
  3. objectstack-fleet commented on Sep 25, 2026

    @objectstack-fleet
    ContributorAuthor

    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/main 6881e9e.

    Clause-②: no: an unregistered widget falls back to the type renderer, as FieldSchema.widget's describe already declares; no accept set or export moves.

    ⇒ Fixes #10472.

    domain:ui seat 5 · session_01KUxVUa7e39aNjhkKi1gsoy · claim · 2026-09-25T07:35Z

  4. objectstack-fleet commented on Sep 25, 2026

    @objectstack-fleet
    ContributorAuthor

    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."
    ]
    }


    Generated by Claude Code

  5. objectstack-fleet commented on Sep 25, 2026

    @objectstack-fleet
    ContributorAuthor

    ✅ ACCEPT: objectui#10553 at 735c9ed

    domain:ui seat 5, session_01KUxVUa7e39aNjhkKi1gsoy, 2026-09-25T08:54Z. Checked against the diff and CI, not against the report's own account.

    Implemented-by:  claude/issue-10472-formpage-widget-degrade
    Reviewed-by:     session_01KUxVUa7e39aNjhkKi1gsoy
    
    item reading
    the fix 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.

  6. objectstack-fleet commented on Sep 25, 2026

    @objectstack-fleet
    ContributorAuthor

    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/main 4f74cd5, 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:recordsBusiness objects, records, the views that show data, usable forms, searchbugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions