Skip to content

The record page's lifecycle path looks like a control and is inert: stageField renders as a segmented button group with no handler, no cursor, and no tab stop #5768

Description

@os-zhuang

Transferred from objectstack-ai/objectstack#11243 (triage 2026-08-23, file-at-destination: this is generic record chrome in the console/record page — the body stands with objectstack removed). Original author: os-zhuang, filed 2026-08-23. Original body verbatim below.


The record page's lifecycle path looks like a control and is inert: it renders stageField as a segmented button group with no handler, no cursor, and no tab stop

Measured on 17.1.0 (open edition, objectstack dev), HotCRM crm_quote — but it is the generic record chrome, so it applies to every object that declares a stageField.

What renders

The record page draws the object's lifecycle across the top:

[ 草稿 ] [ 审核中 ] [ 已提交 ] [ 已接受 ] [ 已拒绝 ] [ 已过期 ]
   ▲ current, filled

Pill-shaped segments, the current one filled in the accent colour, the rest in a resting state — visually a segmented button group, and positioned exactly where a CRM user expects the stage control to be.

What it is

document.querySelectorAll('main [role="listitem"]')   // ← listitem, not button/tab
getComputedStyle(el).cursor        // "auto"
el.getAttribute('tabindex')        // null
el.closest('button,[role=button],[role=tab],a')   // null

A full pointer sequence (pointerdown → mousedown → pointerup → mouseup → click) on the target segment changes nothing; the record's status is still draft afterwards. Advancing the record needs 编辑 → the status field → 更新, or a bulk action.

Why it is worth changing

The path is the affordance every CRM user reaches for first — in the products this chrome is modelled on, clicking the next segment is how you advance a record. Rendering it as buttons and wiring nothing is the shape that costs a user two or three clicks before they conclude it is decoration, and it teaches them to distrust the rest of the chrome.

Two directions, either defensible:

  1. Make it a control. Clicking a segment writes stageField, subject to the same permission/validation path an edit takes (so a locked or unauthorised record refuses with the normal envelope). This is what the visual promises.
  2. Make it look like a readout. If advancing must stay in the form, drop the button styling — a progress rail without pill-and-fill reads as status, not as a control, and nobody loses a click to it.

What it should not keep doing is looking like (1) and behaving like (2).

Accessibility note

The segments carry role="listitem" with no tabindex, so a keyboard user cannot reach them at all — which is correct for a readout and wrong for a control. Whichever direction is taken, the role should follow: listitem for a readout, tab/button with a tab stop for a control.

Metadata

Metadata

Assignees

Labels

domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatenhancementNew feature or requestpm:queue

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions