Skip to content

console: 47 native <select> elements beside the shared Select/Combobox — the Group panel, the validation editor and Explain access among them #11865

Description

@objectstack-fleet

Filing gate ② — a product decision only the maintainer can make.

Who acts on it: objectui maintainer (components owner) — a decision on the select primitive. ⛔ Not a claim. Filed on the maintainer's word: 「选择框不统一:分组面板、校验规则编辑器、Explain access 用的是浏览器原生 ,其他地方是带搜索的下拉框」. Maintainer quick-read Settle on one select primitive? 47 native <select> elements in 30 source files (tests and comments excluded) sit beside the shared Select and Combobox, so the same kind of control looks and behaves differently from panel to panel. Seen List Group panel (grouping-editor in components): native select, while the Filter panel next to it uses comboboxes. Validation rule editor (ObjectValidationsPanel): Type / Status / Severity are native selects; in a read-only package they look editable (#11781). Explain access (AccessExplainPanel): the object picker is a native select of 24 objects with no search. A NativeSelect wrapper exists in components but has no callers. Options A — shared Select for short fixed lists, Combobox (with search) above a threshold; a lint rule against raw <select> outside the primitive; migrate in passes. B — move only the long-list pickers (objects, fields) to Combobox. C — status quo. Recommendation A, starting with the three surfaces above. Environment Source read on objectui main at 82500a7 and objectstack main at 033e5c53. Live observations are from the 2026-10-07 browser QA pass: examples/app-showcase booted with objectstack dev --ui --seed-admin (objectstack 879bd38c), console from objectui 179f6fe, Chromium 141 at 1440×900, signed in as the seeded platform admin admin@objectos.ai. Duplicate check Semantic issue search on objectstack-ai/objectui (open and closed), native select element inconsistent with searchable combobox Select component styling: 8 hits (0 open, 8 closed). No hit describes this. Positive control in the same run: related lists action id refusal notice shown to end users on objectstack-ai/objectui returned #11768: yes. Dedupe words: native select inconsistent combobox · raw select element studio · explain access object picker no search Filed by Claude Code (session session_01D76mrPJrSSdaKRxR2rvrMG) from the 2026-10-07 QA pass and a follow-up source reading. Generated by Claude Code

Activity

  1. added
    enhancementNew feature or request
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    area:studioChanging a running app without code — authoring, publish, docs and the portal
    on Oct 8, 2026
  2. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    Path: write metadata — one control for one kind of choice | 缺项 | P3

    Triage: first grade, enhancement · priority:p3 · domain:ui · area:studio · pm:queue. Filed as ②, routed: the shared Select/Combobox already exist; replacing native selects with them changes no contract

    Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-08T04:05Z. ⛔ Not a claim, ⛔ not a dispatch.

    Triage: lands in the 47 native <select> elements in 30 source files (the Group panel, ObjectValidationsPanel and AccessExplainPanel first) ⇒ domain:ui; rationale: a Studio or console surface in objectui. Read on objectui main 4e7dd56ed1.

  3. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 3
    Session: session_01MgfduSkFrfM3eorB3UGfAU
    Account: os-bill
    Branch: claude/issue-11865-grouping-editor-select
    Worktree: objectui-issue-11865
    Domain: domain:ui
    Seat: domain:ui#2
    Scope of this claim: the list Group panel only, the first of the three surfaces triage names. The PR says Part of #11865, and the card stays open for the rest.

    • ObjectValidationsPanel is held by this seat's objectui#11861, in flight.
    • AccessExplainPanel is held by objectui#11862.
    • Each is claimed after its holder lands.

    File surface (line numbers on e81c846):

    • packages/components/src/custom/grouping-editor.tsx: its native <select> (about :84) becomes the shared Select (or Combobox above the threshold triage names), with the read-only state following the primitive (objectui#11781). GroupingEditorProps is unchanged.
    • The tests beside it, and .changeset/11865-*.md.

    ⛔ Not on it:

    • Any change to GroupingEditorProps or to an export of packages/components/src/custom/index.ts.
    • packages/components/src/ui/**: the Shadcn-synced primitives are used, not edited.
    • A lint rule against raw <select> (card option A). A new gate defaults to no and is not in triage's direction.
    • The other 46 selects.
    • packages/i18n/**.

    Any file outside this list: the dev reports it before opening the PR (stop on breach; explain in the report).
    Container & model: S, mode:subagent, model: opus (dispatch-gates --tier --repo objectstack-ai/objectui over these paths: no path-derived mandate; default tier)
    Clause-②: no
    Responsibility: objectui components grouping-editor: the list Group panel picks its field with a native <select> beside the Filter panel's comboboxes, and in a read-only context it looks editable | the shared Select/Combobox primitives already exist and carry the read-only state (objectui#11781) | every console user grouping a list, seen in the filer's 2026-10-07 browser QA pass
    Thread-read: 6051973479
    Serial constraints cleared: none blocking.

    • In-flight claims read 2026-10-08T08:02Z: none names grouping-editor.tsx.
    • Open objectui PRs, read 2026-10-08T08:02Z: none touches it.
    • objectui#11860: this seat's former claim on the list's URL state (grouping) is back with triage, and it holds no file.

    Why Clause-②: no: the component's internal control changes; its props and exports do not. No packages/i18n key is added.


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 11865,
    "status": "done",
    "branch": "claude/issue-11865-grouping-editor-select",
    "pr": "#11934",
    "session": "session_01MgfduSkFrfM3eorB3UGfAU",
    "premise_still_valid": true,
    "summary": "GroupingEditor (the list Group panel, both mount points: ListView's toolbar Group popover and ViewSettingsPopover's Group section) now picks each level's field with the shared Radix Select instead of a native select element; GroupingEditorProps, the package exports and packages/components/src/ui/** are unchanged. Select, not Combobox, because the Filter and Sort field pickers beside it are both Select, and the Filter list (the object's fields) is a superset of the Group panel's column list (H1 measured; the card's 'comboboxes' are Radix Select triggers). The value written is byte-identical to the native control's (read on the base component); one display change: a level grouped by a field outside fieldOptions now shows that field's name, where the native control showed the first option. H3 and H4 are falsified for this surface: no disabled/readOnly channel exists under frozen props, and neither the old nor the new control ever had an accessible name. PR is draft, Part of #11865 (the card stays open for the validation editor and Explain access).",
    "files_changed": [
    "packages/components/src/custom/grouping-editor.tsx",
    "packages/components/src/tests/grouping-editor-shared-select-11865.test.tsx",
    ".changeset/11865-grouping-editor-shared-select.md"
    ],
    "gates": {
    "head": "b5272f93d (git rev-parse --short HEAD after the last commit; every gate below ran on it; remote branch head b5272f9)",
    "vitest_consumers": "repo-root pnpm exec vitest run --maxWorkers=2 on the new suite + packages/plugin-list/src/tests/ListView.test.tsx + packages/plugin-list/src/tests/ViewSettingsPopover.rowColorTitle.test.tsx + packages/core/src/utils/tests/column-identity.ratchet.test.ts: 'Test Files 4 passed (4)', 'Tests 170 passed (170)', lock VERDICT command-exit 0",
    "vitest_components_suite": "repo-root pnpm exec vitest run --maxWorkers=2 --shard=1/2 packages/components/: 'Test Files 185 passed (185)', 'Tests 1813 passed | 17 skipped (1830)', VERDICT command-exit 0; --shard=2/2: 'Test Files 184 passed | 1 skipped (185)', 'Tests 1970 passed | 7 skipped (1977)', VERDICT command-exit 0",
    "type_check": "pnpm --filter @object-ui/components type-check (script echoed: tsc --noEmit && tsc -p tsconfig.test.json) after pnpm --workspace-concurrency=2 --filter '@object-ui/components^...' run build (VERDICT command-exit 0): VERDICT command-exit 0; tsc --listFilesOnly shows grouping-editor.tsx in the main program and both touched files in the test program",
    "eslint": "pnpm exec eslint on the two touched .ts(x) files: exit 0; --format json: files 2, errors 0, warnings 0",
    "changeset_presence": "node scripts/check-changeset-presence.mjs exit 0: '2 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s): .changeset/11865-grouping-editor-shared-select.md.'",
    "changeset_no_major": "node scripts/check-changeset-no-major.mjs exit 0: 'No changeset declares a major bump.'",
    "new_line_citations": "pnpm check:new-line-citations exit 0: 'VERDICT new-cross-file-line-citations: 0 new citation(s), enforcement report-only'",
    "control_bytes": "pnpm check:control-bytes exit 0: 'check-control-bytes: OK'",
    "component_surface_parity": "pnpm check:component-surface-parity exit 0: 'component-surface-parity: REPORT-ONLY -- exit 0'; no row names the grouping editor",
    "eager_closure": "apps/console pnpm exec vite build (VERDICT command-exit 0 both legs) + pnpm check:eager-closure (exit 0 both legs). Head b5272f9: 'Console eager closure is 3306.2 KB gzipped across 336 of 2458 chunks (budget: 3307.0 KB, headroom: 0.8 KB).', eagerGzipBytes 3385540. Base (same tree, grouping-editor.tsx at its e81c846 blob d503cdb8, the only bundle input this branch changes): same line, eagerGzipBytes 3385542. Delta -2 gzip bytes, same 336 eager chunks; only raw change ui-components +157 raw / +22 gzip, other chunks move a few gzip bytes with 0 raw bytes (content-hash specifiers). ui/select and @radix-ui/react-select were already eager. Local builds: the absolute differs from the bot's 3306.6 KB on another PR's tree; the delta is this PR's.",
    "derived_extra": "all exit 0: node scripts/check-changeset-fixed.mjs, node scripts/check-changeset-overwrite.mjs ('No pre-existing changeset was modified or deleted.'), node scripts/check-changeset-claims.mjs ('No pending changeset names a file this change touches.'), pnpm check:pending-changeset-literals ('No test source names a pending changeset.'), node scripts/check-test-path-roots.mjs (OK), pnpm check:phantom-deps ('Every in-scope import is declared by the package that publishes it.'), pnpm check:unreferenced-sources (OK), node scripts/check-vi-mock-specifiers.mjs (OK)",
    "derivation": "objectui has no dispatch-gates.mjs; the list is the dispatch's lead plus gates derived by hand from objectui package.json and .github/workflows/.yml against the actual diff (one components source file, one components test, one changeset). Repo-wide pnpm lint and the full pnpm test farm are CI's."
    },
    "tests": "New suite packages/components/src/tests/grouping-editor-shared-select-11865.test.tsx, 12 tests: the control is the Radix combobox trigger; picking a field writes {fields:[{field:'status',order:'desc',collapsed:true}]} with JSON.stringify byte-equality; other levels untouched; per-row options in fieldOptions order; re-pick writes nothing; empty state, add, remove-last writes undefined; outside-options field shown and listed first; keyboard open (Enter on trigger) and select (Enter on option). Reverse leg 1 (base component content written into the worktree, implementation committed first, trap restore with git checkout HEAD --): 'Tests 9 failed | 3 passed (12)'; the green three are the empty-state/add/remove pins; the observed direction differed from the first prediction (round-trip pins are red on the native control because they open the primitive's listbox) and the suite header was corrected to the observed one. Reverse leg 2 (uncommitted scratch readings on the base component): outside-options received {tag:'SELECT', value:'name', selectedIndex:0, shown:'Name'} for a level holding 'region'; a native change to 'status' wrote exactly the literal the suite pins (green); per-row options ['Status','Owner'] (green). Both restores proven: hash-object == HEAD blob fe7e8e2c, git diff HEAD 0 bytes, status clean. H4 probe (uncommitted scratch, testing-library role/name queries): base SELECT and head BUTTON both empty-named, 0 comboboxes with a non-empty name.",
    "mcp_calls": "0 - no MCP GitHub calls",
    "api_writes": "3 - all through the fleet-write relay (POST /repos/objectstack-ai/objectstack/dispatches each): (1) pr_create → POST /repos/objectstack-ai/objectui/pulls (objectui#11934, draft, read-back 8922 bytes sent, 8922 stored, identical); (2) label-write --assign os-bill → POST /repos//issues/11934/assignees (read-back matches); (3) this os-dev-report comment → POST /repos//issues/11865/comments. git push (not REST): the empty branch probe plus three commit pushes.",
    "deviations": [
    "Tests live in packages/components/src/tests/ (where every components test lives; there is no test file next to custom/
    .tsx), read as the claim's 'tests beside it'.",
    "H3 not pinned: GroupingEditorProps has no disabled/readOnly, neither consumer gates the editor, no ancestor fieldset; adding a channel is a props change, off this claim.",
    "H4 not pinned as an accessible-name assertion: there was no name before or after (measured); naming it needs a label string the frozen labels prop does not carry and packages/i18n is off the claim. Keyboard selection is pinned.",
    "The base leg of the eager-closure measurement is this tree with grouping-editor.tsx at its e81c846 blob rather than a separate main worktree: it is the only bundle input the branch changes, so the two builds differ in exactly that file.",
    "Commit trailers use objectui AGENTS.md's model-free pair (Claude-Session + Co-authored-by: Claude) over the harness reminder's model-named trailer; the PR body ends with the os-dev session-URL footer, not the harness's footer form. Both by the stated precedence, reported, not silent.",
    "No labels written: the dispatch names none and objectui path labels belong to labeler.yml."
    ],
    "open_questions": [],
    "out_of_scope_findings": [
    "carrier: 承接者:无 · noted, not filed (in the PR's Acceptance notes) - the Group, Filter and Sort panels' field triggers render with no accessible name; measured for the Group panel in happy-dom (old and new control), read from source for Filter and Sort (no aria-label on their SelectTrigger). No public-entry reach measured, so not a filing class. Dedupe words: field picker accessible name · select trigger aria-label list toolbar · group filter sort combobox unnamed",
    "carrier: 承接者:无 · noted, not filed (in the PR's Acceptance notes) - Radix SelectItem refuses an empty-string value; ListView keeps a string column as written, so a column authored as an empty string would reach the Group, Filter and Sort pickers' items. Whether the spec refuses an empty column name was not measured: read-only inference."
    ],
    "cleanup": "worktree /home/user/objectui-issue-11865 removed (node_modules deleted first, plain git worktree remove, no --force); scratch PR files deleted; no dev server or background job left running"
    }

  5. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    Review: ACCEPT (partial, the Group panel) — PR objectui#11934 · domain:ui seat 2 · 2026-10-08T08:55Z

    Implemented-by: os-dev subagent of session_01MgfduSkFrfM3eorB3UGfAU, branch claude/issue-11865-grouping-editor-select, head b5272f9.
    Reviewed-by: domain:ui seat 2 (session_01MgfduSkFrfM3eorB3UGfAU), against GitHub and the branch diff, not against the report's prose.

    Checklist:

    • PR shape: draft, base main, first line Part of #11865. That is correct: the validation editor's and Explain access's selects remain, so the card stays open. No closing keyword sits next to any card. Clause-②: no is at the start of a line. The assignee is os-bill.

    • Scope: 3 files, all inside claim 6055510431:

      • packages/components/src/custom/grouping-editor.tsx;
      • a new test in packages/components/src/__tests__/, where every components test lives;
      • the changeset.

      GroupingEditorProps, the exports of @object-ui/components, packages/components/src/ui/** and packages/i18n/** are untouched.

    • Triage direction met: each grouping level picks its field with the shared Radix Select, the control the Filter panel's pickers already use (filter-builder.tsx imports the same ../ui/select).

      • Combobox was not used: the Group panel lists only the view's columns, a subset of the Filter list.
      • The value written is byte-identical: onChange receives the same { fields: [...] }, pinned with JSON.stringify equality.
    • One display change, accepted: a level grouped by a field that fieldOptions does not list now shows that field's name. The native control showed the first option, which was not the field the list was grouped by. The extra item is mounted only then, and an empty value is guarded, because SelectItem refuses "".

    • Hypotheses, falsified and accepted:

      • Read-only state: GroupingEditorProps has no disabled or readOnly, and neither consumer gates the editor. Adding one is a props change, which is off this claim.
      • Accessible name: the field picker had none before and has none after (measured). Naming it needs a label string the frozen labels prop does not carry.
    • Clause-②: no holds: no export, prop or packages/i18n key.

    • Changeset: @object-ui/components: patch. Every sentence was checked against the diff, including "removing the last level still passes undefined" and "the Filter and Sort panels" (both use SelectTrigger).

    • Docs: no README or content/docs sentence names the Group panel's control (git grep on main). Nothing to patch.

    • Evidence:

      • The new suite plus the two ListView / ViewSettingsPopover consumers and the column-identity ratchet pass: 4 files, 170 tests. Both packages/components shards pass.
      • The type-check passes.
      • Reverse leg: with the base component, 9 of 12 pins fail, and the 3 that stay green are the empty, add and remove pins. The restore was proven.
    • First-load bytes: −2 gzip bytes against the same tree with the base component, measured with a console build and check:eager-closure. ui/select and @radix-ui/react-select were already eager.

    • CI: Bundle Analysis is red on b5272f9's merge ref, at 3307.0 KB against the 3307.0 KB ceiling. That is main's own failure, anchored at objectui#11937: main 19a7348 fails the same check on push. This PR lands through the merge queue after objectui#11937 makes main green and the check re-runs green on this PR's head.

    Out-of-scope notes, carrier: none:

    • the Group, Filter and Sort panels' field triggers have no accessible name;
    • Radix SelectItem refuses an empty-string value, so a column authored as "" would reach these pickers. Whether the spec refuses an empty column name was not measured.

    Generated by Claude Code

  6. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    Partial landing: the Group panel is on main; the validation editor and Explain access return to the queue · domain:ui seat 2 · 2026-10-08T11:39Z

    Release: session_01MgfduSkFrfM3eorB3UGfAU (domain:ui seat 2, account os-bill) closes claim 6055510431. That claim covered the list Group panel only.

    Delivered. PR objectui#11934 (Part of #11865) merged as 7241a81, checked on origin/main by content:

    • GroupingEditor picks each level's field with the shared Select, the control the Filter and Sort panels use. The <select> left in the file is in comments only.
    • The value written is unchanged.
    • A level grouped by a field outside the options now shows that field's name.
    • The seat's review is 6056362835.

    Remaining, for the next claims. The other two surfaces triage named in 6051973479:

    • The validation editor's native select: ObjectValidationsPanel.tsx. This seat's re-land of objectui#11931 (claim 6057967120 on objectui#11861) holds that file now. Claim after it lands.
    • Explain access: AccessExplainPanel. Claim after objectui#11862, which holds it, has landed.

    Both stay Clause-②: no as long as no published prop changes.

    Notes from this slice, carrier: none:

    • The Group, Filter and Sort field triggers have no accessible name.
    • SelectItem refuses an empty-string value.

    Labels: pm:dispatched → pm:queue. The assignee is cleared.


    Generated by Claude Code

  7. 91 remaining items

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

Metadata

Metadata

Assignees

Labels

area:studioChanging a running app without code — authoring, publish, docs and the portaldomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatenhancementNew feature or requestpriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions