Repository navigation
finding: the docs gallery fixture declares users.role / users.status as select with no options, so any form bound to them renders "No options available" #6317
Description
Activity
- addeddomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatobjectui ui stream: fix lands on the published library or apps — objectui execution seatand removed
on Aug 25, 2026 os-support-ai commented
on Aug 26, 2026 CollaboratorAuthorMore actionsDispatch order —
domain:uiseat, PM sessionsession_011SfZeFWrhGLHmfq61xbz4q. Branch:claude/issue-6317-gallery-select-options.Triage's ruling, verbatim
Triage: promoted
finding→pm:queue(keepingdomain:ui, lands inapps/site/app/components/galleryDataSource.tsand its catalog-test mirror), type Task. Fix as suggested: declareoptionson both fields with labels matching the values already inUSERS_ROWS, keep the fixture and mirror in step, and take the before/after on theplugin-grid/plugin-viewtiles deliberately (select cells will start rendering labels instead of raw values — that is the intended behaviour of the component the docs exist to show, so the text change is an improvement to accept, not a regression to avoid). Afterwards the #6167 entries can stop steering aroundrole/status. Size/model suggestion: S, sonnet.The values, measured rather than assumed
Read on
origin/mainat831be7285.roleis declared atgalleryDataSource.ts:76andstatusat:86; the fiveUSERS_ROWS(lines 100–104) carry exactly:role:admin,member,viewer— 3 distinctstatus:active,invited,suspended— 3 distinct
Re-derive these yourself from the rows rather than trusting this list, and derive them structurally (walk the rows) rather than by a shaped grep. Two demo-shaped censuses on a recent card in this lane each undercounted a fixture because they were grepped rather than walked, and a count that is wrong in a plausible direction is worse than one that is obviously wrong. If your walk disagrees with the three-and-three above, your walk wins — say so in the report.
The tile text change is expected — do not treat it as a regression
Once
optionsare declared, select cells render labels instead of raw values, so theplugin-grid/plugin-viewgallery tiles will change text. Triage has already ruled that deliberate: it is the intended behaviour of the very component these docs exist to demonstrate. Take the before/after consciously and show it in the report. ⛔ Do not add a workaround to keep the old raw-value text, and do not narrow the labels to equal the values just to keep a snapshot green — that would reintroduce the defect while appearing to fix it.Keep the mirror in step
The fixture and its catalog-test mirror must move together. A mirror left behind is the failure mode this card is a member of: two declarations of the same fixture disagreeing. If you find the mirror already disagrees with the fixture in some other way, file that, do not fold it.
File face
apps/site/app/components/galleryDataSource.tsand its catalog-test mirror, plus any pin you add. Nothing else.The
#6167follow-on is out of scope. Triage notes that afterwards those entries can stop steering aroundrole/status— that is a consequence to record, not work to do here. File it; do not fold it in.In-flight, do not touch
#6515 · #6519 · #6329 · #6343 — all in
packages/app-shell, measured disjoint from your face inapps/site.Reverse verification is required: write the assertion first, observe it fail against the untouched fixture, and prove the mutation on disk by counting the changed text rather than trusting an editor's exit code.
Generated by Claude Code
os-support-ai commented
on Aug 26, 2026 CollaboratorAuthorMore actionsClaim — dev seat, session
session_011SfZeFWrhGLHmfq61xbz4q, branchclaude/issue-6317-gallery-select-options.Re-read the comments before starting: no earlier claim from a different session, so this card is free. Assignee set.
Early premise note (full evidence to follow in the report): on
origin/mainat831be7285the fixtureapps/site/app/components/galleryDataSource.tsalready declaresoptionson bothroleandstatus— they came in with4b0b12630(#5113 / #5857, 2026-08-23), 330 commits before the branch point. The optionless declaration survives in exactly one place, the catalog-test mirror (examples/schema-catalog/test/catalog-gallery-render.test.tsx:322,324). Verifying that fully before writing code.
Generated by Claude Code
Generated by Claude Code
os-support-ai commented
on Aug 26, 2026 CollaboratorAuthorMore actionsos-dev-report
{ "issue": 6317, "status": "done", "branch": "claude/issue-6317-gallery-select-options", "pr": "https://github.com/objectstack-ai/objectui/pull/6539", "premise_still_valid": false, "summary": "The issue's stated location is dead; the defect it describes is alive one file over. Verified on origin/main @ 831be7285 BEFORE writing code: apps/site/app/components/galleryDataSource.ts ALREADY declares options on role and status, and has since 4b0b12630 (#5113/#5857, 2026-08-23) — 330 commits before the branch point and two days before this issue was filed. `git grep -n \"label: 'Role', type: 'select'\" origin/main` returns exactly one hit repo-wide, the catalog-test mirror at examples/schema-catalog/test/catalog-gallery-render.test.tsx:322. So the host fixture is untouched by this PR (nothing was owed) and the whole change is the mirror plus pins. That is the card's own named failure mode — two declarations of one fixture disagreeing — running in the direction the existing host-parity case cannot see: it pins the host's METHOD NAMES, so the field declarations drifted for 330 commits with all 572 cases in the file green. The PR declares the options on the mirror to match the host byte-for-byte, and adds four cases: every select field must declare options whose values EQUAL the distinct values its own rows carry (both directions), a vacuity guard, and a whole-field-surface comparison against the host source (brace-matched then JSON-ified, so a broken extraction fails loudly rather than comparing less than it claims). SCOPE CORRECTION worth PM's attention: triage ruled the plugin-grid/plugin-view tile text would change and pre-approved it — measured, it does NOT change, byte-identical across all seven users-bound entries, because ObjectGrid SYNTHESISES options for an option-less select from the distinct row values (packages/plugin-grid/src/ObjectGrid.tsx: fieldMeta.options = uniqueValues.map(v => ({ value: v, label: humanizeLabel(String(v)) }))), so 'admin' already printed as 'Admin'. The grid has a fallback; the form path has none, and that is where the change is real. No labels were narrowed to keep a snapshot green and no workaround was added — the labels are the Title Case ones triage specified, and they coincide with humanizeLabel's output by construction, which is the reason the grid text is unchanged rather than evidence the change is inert.", "tests": "STRUCTURAL CENSUS (walked, not grepped): a tsx script imported the host fixture and called its own find('users'), then walked every returned record and every key on it with no field list decided up front — 'rows returned = 5, total reported = 5; role distinct=3 admin(x1) member(x3) viewer(x1); status distinct=3 active(x3) invited(x1) suspended(x1)'. Agrees with the dispatch's 3-and-3. The same walk over the MIRROR's own rows (brace-matched out of the .tsx, then parsed and walked as data) returns the same two sets, so the mirror declared select-with-no-options over rows carrying three values each. BEFORE/AFTER: a throwaway vitest probe (examples/schema-catalog/test/zz-tmp-6317-tile-text.test.tsx, deleted before commit, absent from the diff) rendered every users-bound entry through the mirror's own render path under both schema states — both states read off the two real source files rather than hand-copied. All 7 entries CHANGED: false, byte-identical text and control values. The synthetic shape the issue itself measured — object-form over users with no `fields` restriction — is the one that moves: before \"NameEmailRoleNo options availableDepartmentStatusNo options availableCancelUpdate\" (byte-identical to the string in the issue body, so the probe measures the reported defect) -> after \"NameEmailRoleAdminAdminMemberViewerDepartmentStatusActiveActiveInvitedSuspendedCancelUpdate\"; controls [\"Alice Johnson\",\"alice@example.com\",\"Engineering\"] -> [\"Alice Johnson\",\"alice@example.com\",\"admin\",\"Engineering\",\"active\"]. REVERSE VERIFICATION: assertions written first and run against the untouched fixture — '576 tests | 3 failed | 572 skipped', with 'AssertionError: `role` is declared type: select with no options ... expected undefined to be truthy', the same for `status`, and the parity case printing the host's option arrays as Received against the mirror's absence. Predicted direction (turn red) matched. MUTATION PROVED ON DISK BY COUNTING, not by an editor's exit code: optionless role decl 1->0, optionless status decl 1->0, option pairs 0->6, file sha ef0d4294->0a12070a, and the mutated USERS_SCHEMA region printed back off disk. GATES, all on final commit a9494bc32 (working tree clean; tested blob cad7f9d7 equals HEAD:'s blob, so the green union is on the committed tree), each quoting the gate's own verdict line, exit codes captured before any pipe: vitest examples/schema-catalog/test/catalog-gallery-render.test.tsx -> 'Test Files 1 passed (1)' / 'Tests 576 passed (576)', VITEST_EXIT=0; pnpm --filter @object-ui/example-schema-catalog run type-check -> exit 0 with the script name echoed (no zero-match green), run AFTER building the dependency closure (pnpm --workspace-concurrency=2 --filter '@object-ui/example-schema-catalog^...' build) because a fresh worktree otherwise yields TS2307 missing-dist noise that is not a measurement; NOT-MEASURED trap checked: tsc -p tsconfig.test.json --listFiles lists catalog-gallery-render.test.tsx (1 hit), so the type-check genuinely covers the edited file; eslint . in examples/schema-catalog -> LINT_EXIT=0 (it caught one real problem in my first draft — preserve-caught-error, no `cause` on a rethrow — now fixed); node scripts/check-changeset-presence.mjs -> exit 0, 'No source of a released package changed in this range, so no changeset is owed'; node scripts/check-control-bytes.mjs -> exit 0, 'OK (scanned 5394 tracked text file(s); skipped 85 binary)', plus a targeted grep -naP control-byte scan of the edited file (no hits). DECLARED NARROWING: the repo-wide eslint . scan was not run. What ran is this package's own lint command in full — 17 files, which is eslint's own enumeration for examples/schema-catalog read from --format json, not a guess — so there is no narrowing within the package; for untouched files elsewhere, eslint.config.js sets no parserOptions.project / projectService, so type-aware linting is off and a one-file diff cannot move any other file's verdict. CI runs the full farm regardless. NO CHANGESET, on the gate's own verdict: apps/site and examples/schema-catalog are both private and @object-ui/site / @object-ui/example-* are in the changeset ignore list. All heavy runs went through scripts/pm/os-verify-lock.sh; queue waits reached 6m45s and were spent on lock-free work, never on a stalled turn.", "open_questions": [], "out_of_scope_findings": [ "filed as #6537: the two plugin-form catalog entries (object-form-record, object-form-tabbed-sections) author `fields` lists omitting role/status — the #6167 follow-on triage named. Labelled `finding` + `domain:ui`, unassigned, carries `Blocked-by: #6317`. Not folded in: its file face is the entry JSON, not the fixture." ] }
Generated by Claude Code
Generated by Claude Code
- added a commit that references this issue
on Aug 26, 2026 - added a commit that references this issue
on Sep 1, 2026
Filed unassigned while implementing #6167 (PR #6314). Not claiming.
What was measured
apps/site/app/components/galleryDataSource.tsserves one object,users, and itsUSERS_SCHEMAdeclares:Neither declares
options, while every row carries a value for them (admin/member/viewer,active/invited/suspended).examples/schema-catalog/test/catalog-gallery-render.test.tsxmirrors the same fixture.ObjectFormcopies the field's options through verbatim —formField.options = field.options || [](packages/plugin-form/src/ObjectForm.tsx) — so a form bound to those fields renders an empty select. Measured through the gallery's own render path, anobject-formoveruserswith nofieldsrestriction:Two of the five visible controls are an empty picker with the placeholder "No options available", on a docs page whose purpose is to show the component working.
Why it is worth recording
The
plugin-gridandplugin-viewentries do not expose it: a grid renders a select-typed cell as its raw value, soadmin/activesimply print. It surfaces the moment an entry puts those fields in a FORM, which is what #6167's newplugin-formentries do. Those two entries dodge it by authoringfields: ['name', 'email', 'department'](pluscreated_at), so nothing is broken today — but the reason they avoidroleandstatusis this, and that is a constraint on the fixture rather than on the examples.Not fixed in PR #6314 because the fixture is shared: it is mirrored in the catalog test, and adding
optionswould change whatselect-typed cells render in theplugin-grid/plugin-viewtiles (label instead of raw value), which is outside that card's declared surface and wants its own before/after.Possible fix
Declare the options on both fields in
galleryDataSource.tsand in the mirror, with labels matching the values already inUSERS_ROWS, then re-measure theplugin-grid/plugin-viewtiles for text changes.Severity left to triage.