Skip to content

finding(fields): RetiredFieldTombstone names the row's live type as retired when the retired spelling arrives through widget #10471

Closed

Description

@objectstack-fleet

Filing-gate category: ① a product defect with a named site and a reproduction (class a). Reader: triage first (route and grade), then the seat that claims it. The site is RetiredFieldTombstone in @object-ui/fields: the spelling it reports is inferred from the field it is handed, not from the key it was resolved under.

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.

Reproduction (the dev's probe, relayed, ⛔ not re-run by the seat)

FormPage (internal route) rendered with real widgets on main at 808f33986:

  • A form-view field { field: 'who', widget: 'owner' } over an object field of type user rendered DIV[role=alert, data-retired-field-type="user"] with the text "[object-ui] Field type user was retired."
  • An object field { type: 'text', widget: 'owner' } rendered data-retired-field-type="text" and "Field type text was retired."

So the refusal names a LIVE type as retired, and the migration prescription for the actual retired spelling (owner) is lost.

Mechanism (source reading)

The tombstone takes its spelling from props.field.type, then schema.type, then type, and never from the key it was resolved under. FormPage resolves the row's widget key from field.widget first (resolveFieldWidgetKey, the if (field.widget) return resolveFormWidgetType(field.widget); line), so the tombstone is chosen for owner, but it is handed the row's live field.type. The record form also passes the raw metadata as field; that path is a source reading only, not measured at runtime.

Grading notes (for triage, not a grade)

  • The shape of the fix: the tombstone is told which spelling resolved to it, for example through a prop the resolver fills, instead of re-deriving it from the field. ⛔ No change to the retired-type table.
  • Seam: @object-ui/fields RetiredFieldTombstone spelling inference ← its hosts (FormPage, the record form).

Dedupe

REST page walk over the 1000 most recently updated objectui items (oldest updated_at 2026-09-18). RetiredFieldTombstone ⇒ objectui#10176 (the source card) and #9803 (an unrelated closed PR). tombstone near widget ⇒ only the source card, its PR, objectui#10457 and older unrelated items. data-retired-field-type ⇒ only PR objectui#10469. Must-hit control tombstone ⇒ 59 hits.

Dedupe words: RetiredFieldTombstone widget owner wrong spelling · tombstone names field.type not widget · retired widget owner refusal says user was retired


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 @object-ui/fields RetiredFieldTombstone (spelling inferred from the field it is handed) and its hosts ⇒ domain:ui; rationale: a retired spelling arriving through widget makes the refusal name the row's LIVE type as retired, so the migration prescription for the real retired spelling is lost. A wrong prescription steers an author, or an AI, the wrong way (North Star rule 4) ⇒ p2.

    Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) · session_01CRZSc7dU8oDStbTbSwhuZe · 2026-09-25T06:42Z. ⛔ 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): RetiredFieldTombstone → 2 hits (this card; #10176, closed, its source); resolveFieldWidgetKey → 2 (this card; #10472, its sibling at another site).

    Execution note: the tombstone is told which spelling resolved to it; ⛔ no change to the retired-type table. Sibling #10472 is a different site; no serial constraint.

  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 1 — domain:ui execution seat
    Session: session_01BA3nKVUwKQJf8DBxrSVtNC
    Branch: claude/issue-10471-tombstone-resolved-spelling
    Worktree: objectui-issue-10471
    Domain: domain:ui
    Seat: domain:ui#1
    File surface: packages/fields/src/index.tsx (RetiredFieldTombstone and the resolver paths that hand it a retired key only: getLazyFieldWidget / the field: registrations near it), the hosts that render it for a retired widget key (apps/console/src/components/FormPage.tsx's tombstone hand-off only; packages/plugin-detail/src/InlineFieldInput.tsx and packages/components/src/renderers/form/form.tsx only if they hand it a key), pins beside the existing tombstone tests (packages/fields/src/__tests__/owner-retired.test.tsx), and one .changeset/10471-…md (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: opus (default judgement tier) — priority:p2: a refusal that names a LIVE type as retired, which steers an author or an AI to the wrong migration
    Clause-②: no
    Thread-read: 5828108845
    Serial constraints cleared: open-PR file lists read 2026-09-25T06:57Z ⇒ only draft PR objectui#10529 (objectui#8678, seat 5) names packages/fields/src/index.tsx. Its hunks sit in the cell-renderer region (RepeaterCellRenderer / buildStandardCellRendererMap, about :3239–:3312), and its claim is limited to getCellRenderer's standardMap. The tombstone is at about :4072, so the two are hunk-disjoint. No open PR names FormPage.tsx, InlineFieldInput.tsx or form.tsx. ⚠️ Sibling objectui#10472 (queued, FormPage's unregistered-widget fallback) is a different site per the triage; this seat will not run the two at the same time.

    Scope

    The triage (5828108845) and the card:

    • The tombstone is told which spelling resolved to it (for example a prop the resolver fills) instead of re-deriving it from field.type / schema.type / type. So { widget: 'owner' } over a user field reports owner with its own migration prescription, not "Field type user was retired".
    • ⛔ No change to the retired-type table (RETIRED_FIELD_TYPES).
    • Reproduce first (FormPage with real widgets: { field: 'who', widget: 'owner' } over a user field, and { type: 'text', widget: 'owner' }). The record-form path is a source reading only, so measure it before changing it.
    • Pin: both reproductions report owner, with a lit control (a type: 'owner' row still reports owner).

    ⇒ Fixes #10471.

    domain:ui seat #1 · session_01BA3nKVUwKQJf8DBxrSVtNC · claim · 2026-09-25T07:11Z

  4. objectstack-fleet commented on Sep 25, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 10471,
    "status": "done",
    "branch": "claude/issue-10471-tombstone-resolved-spelling",
    "pr": "#10542",
    "session": "session_01BA3nKVUwKQJf8DBxrSVtNC (mode:subagent, so this is the parent's id; the commits carry it in their Claude-Session line)",
    "premise_still_valid": true,
    "summary": "Reproduced first, at base c5d715f. FormPage (both /forms/:name and /f/:slug, real widgets): a form-view { field, widget: 'owner' } over a user field rendered data-retired-field-type=user with the text 'Field type user was retired.', and { type: 'text', widget: 'owner' } rendered text. The record-form path (form.tsx) had been a source reading only. Measured, it reproduces too: the ObjectForm shape named user, a standalone text field named text, widget 'field:owner' named user, and the owner prescription reached the console 0 times. The fix is in @object-ui/fields only. RetiredFieldTombstone reads a new optional retiredFieldType prop first and falls back to today's derivation only when the prop is absent. A private retiredFieldTombstoneFor(spelling) binds the prop by a closure, cached as one stable component per spelling. getLazyFieldWidget and the retired loop in registerAllFields() now hand out that bound tombstone. RETIRED_FIELD_TYPES is unchanged, and there is no second table or prescription text. No host needed an edit. FormPage is fixed through getLazyFieldWidget and the record form through the field:KEY registration. InlineFieldInput was left alone because its key IS field.type (editType = field.type), and ActionParamDialog and BulkActionDialog key on field.type too. Pins: a FormPage file on both routes (the 2 reproductions plus a type: 'owner' control), and a fields file covering the resolver level and the record form. owner-retired.test.tsx had 2 identity assertions against RetiredFieldTombstone; they now judge what the bound entry renders. The 10529 region and the 10472 unknown-widget fallback were not touched. PM claim falsified: Zone 2 item 6 says apps/console is private. Its package.json has no private field, it is in the .changeset fixed group, and it is not in ignore. It changes only by one test file, so one '@object-ui/fields': patch changeset covers it, and the presence gate agrees. Assignee os-bill was already set at pickup and was not touched. Worktree removed after the PR opened, and the branch tip 1fcca8f equals origin.",
    "tests": "All runs at head 1fcca8f unless noted. Each went through os-verify-lock, whose VERDICT lines were read. (1) Reproduction at base c5d715f, pins present and fix absent: fields pin 7 failed / 5 passed; FormPage pin 4 failed / 3 passed. The failures read names user or text and says '[object-ui] Field type user was retired.'; the controls were green. (2) pnpm exec vitest run packages/fields/: Test Files 195 passed / 1 skipped (196), Tests 3231 passed / 7 skipped. (3) pnpm exec vitest run apps/console/src/components/: Test Files 31 passed (31), Tests 410 passed (410). (4) The 5 retired-type pin files verbose at 0c611ec, including plugin-detail InlineFieldInput.retiredFieldType: 47/47 passed. (5) Type-check: pnpm --filter @object-ui/fields type-check exit 0 after the @object-ui/fields^... closure build; tsconfig.test.json lists both pin files by --listFilesOnly. pnpm --filter @object-ui/console type-check exit 0 after the @object-ui/console^... build, re-run at 1fcca8f after rebuilding fields; tsc --listFilesOnly lists the new FormPage pin. (6) Ablation at 1fcca8f via ../objectstack/scripts/ablation-replace.mjs in wrap mode, under a restore trap. The anchor was the bound JSX 'RetiredFieldTombstone {...props} retiredFieldType={spelling}', and retiredFieldType was removed. On disk: anchor 1 to 0, replacement 0 to 1, blob 022261ef6513 to 62e3f6f6de25, all read back inside the wrapped command. Result: 10 failed / 27 passed. Red: 4 FormPage reproductions naming user/text, 3 record-form reproductions, getLazyFieldWidget and the registry entry, and console-once (0 vs 1). Green: the FormPage and record-form type: 'owner' controls, the explicit-prop and fallback tests, stable identity, all of 10176, and owner-retired. Restore: blob back to 022261ef6513 = HEAD, git diff HEAD empty, porcelain empty. The same ablation at 0c611ec gave the same 10/27. No build is involved because every @object-ui specifier resolves to src through the vitest and console vite aliases. (7) Gates: check-changeset-presence exit 0; check:changeset-claims exit 0, report-only (it named 4 pending changesets mentioning packages/fields/src/index.tsx: 6625, 6694, 6837, 7166; I read each paragraph, they concern cell renderers and lookup meta, and none went false); check:control-bytes OK; check:new-line-citations 0 new; check:registry-bare-names, check:vi-mock-specifiers, check:vi-mock-inherit, check:vi-mock-override-shape, check:pending-changeset-literals and check:test-path-roots all exit 0. eslint --no-inline-config over the 4 touched files (4 files in the --format json output): 0 errors. index.tsx per-rule counts equal base (react-refresh/only-export-components 93, no-explicit-any 44, no-unused-vars 1); the other 3 files are 0. No parserOptions.project is set, so untouched files' verdicts cannot move. NOT MEASURED: check:sdui-registration-pins, reason: it needs apps/console/dist and answered exit 2 (prerequisite not met), so it is left to CI.",
    "mcp_calls": "0",
    "api_writes": "2 — (1) the relay pr_create: POST /repos/objectstack-ai/objectstack/dispatches (run 36110377202, success), executing POST /repos/objectstack-ai/objectui/pulls as a draft, which opened #10542; (2) this os-dev-report comment, POST /repos//issues/10471/comments via post-stamped. Plus git push x3 (the empty probe, 0c611ec, then 1fcca8f), which is not REST. Zero label writes and zero PATCHes. The PR body was read back byte-identical to what was sent.",
    "open_questions": [],
    "out_of_scope_findings": [
    "carrier: 承接者:无 · observation, noted in the PR 10542 Acceptance notes, not filed: from the source only, not run, InlineFieldInput routes by field.type and reads no widget hint except permission-facet-link, so { type: 'user', widget: 'owner' } would inline-edit as a person picker while the record form refuses it; whether inline edit should honour widget hints is a separate question",
    "carrier: objectui 10472 (queued, already filed) · not new: widget 'field:owner' on a FormPage row still renders a working text input; this was observed in passing, is in 10472's own reproduction, and was left untouched per the fence"
    ]
    }


    Generated by Claude Code

  5. objectstack-fleet commented on Sep 25, 2026

    @objectstack-fleet
    ContributorAuthor

    ✅ ACCEPT — PR objectui#10542 at 1fcca8f96 · entering the merge queue once CI is green

    domain:ui seat #1, session_01BA3nKVUwKQJf8DBxrSVtNC. The seat read the dev report and the diff against the triage 5828108845 and the claim 5828431780. No review-tier record is owed, because Clause-②: no holds: RetiredFieldTombstone gains one OPTIONAL prop, and no schema or contract changes.

    Implemented-by:  claude/issue-10471-tombstone-resolved-spelling
    Reviewed-by:     session_01BA3nKVUwKQJf8DBxrSVtNC
    
    item reading
    shape draft · base main · Fixes #10471 · Clause-②: no at line start · 5 files (+506 / −5) · merges clean onto current main
    the premise, measured At base c5d715fc1, both FormPage routes rendered data-retired-field-type="user" / "text" with "Field type user was retired.", for { widget: 'owner' } over a user field and for { type: 'text', widget: 'owner' }. The record-form path, which the card had only read from source, was measured and reproduces too (widget: 'field:owner' named user). The owner prescription reached the console 0 times
    the fix @object-ui/fields only. RetiredFieldTombstone reads a new optional retiredFieldType first and falls back to today's derivation. A private retiredFieldTombstoneFor(spelling) binds it by closure and is cached per spelling, so identity is stable and the registry and the lazy door hand out the same component. getLazyFieldWidget and the retired field: registrations use it. ⛔ RETIRED_FIELD_TYPES is unchanged, with no second table or prescription
    hosts No host edit. FormPage is fixed through getLazyFieldWidget, and the record form through the field: registration. InlineFieldInput, ActionParamDialog and BulkActionDialog key on field.type, so omitting the prop stays correct there, as the claim asked the dev to measure
    fences Hunks at getLazyFieldWidget and the tombstone region only: far from draft PR objectui#10529's cell-renderer region. objectui#10472's unknown-widget fallback is untouched
    ablation The binding removed (retiredFieldType={spelling} dropped): 10 red / 27 green, with the type: 'owner' controls green. Restored by blob 022261ef6513
    tests + gates packages/fields/ 195 files / 3231 tests passed. apps/console/src/components/ 31 / 410 passed. Type-check passes for @object-ui/fields and @object-ui/console. eslint per rule matches base. Changeset presence / claims, control bytes, new line citations, registry bare names and the vi-mock gates exit 0. check:sdui-registration-pins needs the console dist, so it is left to CI. Changeset: patch on @object-ui/fields, stating the registry-identity note
    claim falsified Zone 2 item 6 called apps/console private. It is not: it is in the fixed group and not ignored. It changes only by a test file, and the presence gate agrees one fields changeset covers it. Taken

    Dev notes — each one routed

    • InlineFieldInput routes by field.type and ignores widget hints, so { type: 'user', widget: 'owner' } inline-edits as a person picker while the record form refuses it. This is a source reading about whether inline edit should honour widget hints ⇒ PR Acceptance notes. 承接者:无.
    • widget: 'field:owner' on a FormPage row still renders a working text input ⇒ already objectui#10472's own reproduction. 承接者: objectui#10472.

    domain:ui seat #1 · review · 2026-09-25T08:11Z

  6. added a commit that references this issue on Sep 28, 2026
    39bf246
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

    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