Skip to content

finding(fields): SelectCellRenderer's own emptiness guard is blind to [], so every surface without an upstream pre-check paints a blank cell #8481

Description

@os-justin

Found while implementing objectui#8474 (the shared hasCellValue calling [] a value). Filed unassigned, not claimed. Out of that card's fence: objectui#8474 fixes the record page's upstream predicate in @object-ui/plugin-detail; this is the defect in the cell renderer itself, in @object-ui/fields, on every other surface.

DOM shapes below are spelled out in words rather than written literally — tag-shaped fragments are eaten from GitHub bodies even inside backticks (AGENTS.md, "六种已实测的改写" ①).

What

SelectCellRenderer (packages/fields/src/index.tsx, the renderer registered for select, status, multiselect, radio, checkboxes and tags) opens with:

if (value == null || value === '') return <EmptyValue />;

[] passes that guard. Execution then reaches the array branch:

if (Array.isArray(value)) {
  return (
    <div className={cn('flex flex-wrap', appearance === 'dot' ? 'gap-x-3 gap-y-1' : 'gap-1')}>
      {value.map((val, idx) => renderOne(val, idx))}
    </div>
  );
}

…which maps over zero entries. The renderer's whole output for [] is a childless flex-wrap DIV — measured directly on main at c90395b20, rendering the renderer on its own:

SelectCellRenderer([]) html: DIV with class "flex flex-wrap gap-1" and no children

No EmptyValue, no em-dash, no aria-label. A visually blank cell — and unlike the plugin-detail case, this one is inside the shared renderer, so it is the same blank cell on every surface that uses it.

Why objectui#8474 does not close this

objectui#8474 narrows hasCellValue so the record page never hands [] to a cell renderer at all — the row takes the placeholder branch upstream. RelatedList has its own isValueEmpty doing the same job for its grid (objectui#8459 / PR #8476). Both are upstream pre-checks in @object-ui/plugin-detail. The renderer they protect is still wrong, and every consumer that does not pre-check reaches it directly.

ObjectGrid is one such consumer, verified on c90395b20: packages/plugin-grid/src/ObjectGrid.tsx resolves a renderer through getCellRenderer(...) and calls it with the raw value (lines 2478, 2496, 2534, 2677, 2701) with no emptiness test in between. Its one EmptyValue fallback (line ~2537) is the no-renderer default path only, and its guard is value != null && value !== '' — the same hole, one branch over. So a multiselect column holding [] in an ObjectGrid paints a blank cell today.

Other registered consumers of getCellRenderer worth checking in the same sweep: packages/plugin-list (ObjectGallery), packages/plugin-kanban (ObjectKanban), packages/plugin-tree (ObjectTree), packages/plugin-dashboard (ObjectDataTable, DatasetWidget). Neither ObjectGallery nor ObjectKanban mentions EmptyValue or any local emptiness predicate at all (grep, c90395b20).

Shape of a fix (not settled — this is a finding, not a plan)

The narrow version is one clause in SelectCellRenderer: treat an array with zero entries the same as null. That is the same one-case widening objectui#8474 applied one layer up, and it is where it belongs — a renderer that has nothing to draw should say so itself rather than depend on every caller remembering to ask first.

A wider version would look at the other renderers' opening guards, several of which are spelled value == null || value === '' and share the blindness (JsonCellRenderer is the interesting counter-example: it draws the literal two-character text for [] and for the empty object, so it is not blank and should probably be left alone — objectui#8474 measured that).

Whatever the shape: the pin has to assert the rendered outcome, and it needs a non-regression axis that stays green for a populated array, otherwise a renderer that draws the placeholder for every array would pass.

Dedup

Searched objectstack-ai/objectui via MCP search_issues (REST is 403 from the dev container). Query 1 — the blank-cell-for-empty-array wording — returned objectui#8474 and objectui#8475 as control hits, so the channel is live in this session. Query 2, scoped to the @object-ui/fields guard wording, returned zero. Nothing covers the renderer-level hole: objectui#8474 is the plugin-detail predicate, objectui#8459 is RelatedList, objectui#8475 is RelatedList's hand-rolled em-dash.

Filed by an ObjectUI dev agent working objectui#8474, session session_01YBWFb5YgMU5dw8p2VKj16S, from branch claude/issue-8474-hascellvalue-empty-array. (Attribution written as prose deliberately: a footer block is stripped from issue bodies on create.)

Activity

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

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions