Skip to content

feat(tables): add select & multi-select column types#5873

Open
TheodoreSpeaks wants to merge 7 commits into
stagingfrom
feat/table-enum-type
Open

feat(tables): add select & multi-select column types#5873
TheodoreSpeaks wants to merge 7 commits into
stagingfrom
feat/table-enum-type

Conversation

@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator

Summary

  • Add select (single) and multiselect enum column types to Tables — a column declares a fixed set of options and every cell is constrained to them, rendered as pills
  • Cells store stable option ids (a string for select, string[] for multiselect) so renaming an option never rewrites row data; no DB migration (option set rides in the existing schema JSONB)
  • Server enforces membership and tolerantly maps an option name → its id for tool/import writes; empty/invalid optional cells coerce to null
  • Options are managed in the column-config sidebar; every select cell offers a "None" entry and empty cells read as "None"
  • Options render as neutral grey pills for now — the color field + full palette stay in the model/contract so a picker can be re-added later with no migration
  • Wired into all three edit surfaces (inline cell, expanded popover, row modal); ChipDropdown gains defaultOpen/onOpenChange for inline editing

Known limitations

  • Mothership can't create these columns yet — the copilot tool catalog doesn't expose options (follow-up)

Type of Change

  • New feature

Testing

Tested manually. bun run lint clean, check:api-validation:strict passed, check:react-query passed; 335 table + 14 emcn unit tests green (incl. new validation.test.ts covering membership, name→id coercion, and option-set validation)

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

TheodoreSpeaks and others added 4 commits July 22, 2026 14:31
Adds two enum-style column types where the column declares a fixed set of
options (stable id + name + palette color) and every cell is constrained to
them.

- COLUMN_TYPES gains `select` / `multiselect`; SELECT_COLORS is a fixed,
  theme-aware palette mapping 1:1 to Badge color variants (no raw hex)
- ColumnDefinition.options carries the option set. Cells store option *ids*
  (a string for select, string[] for multiselect) so renaming or recoloring
  an option never rewrites row data
- Row validation enforces membership; coercion tolerantly maps an option
  *name* to its id for tool/import writes and drops unmatched entries
- validateColumnDefinition enforces non-empty, unique-id, unique-name and
  valid-color option sets, and rejects options on non-select columns
- Contract gains selectOptionSchema plus a cross-field refine requiring
  options exactly on select types; routes thread options through type
  changes and a new options-only updateColumnOptions path

No migration: column config already lives in the user_table_definitions
schema JSONB blob.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014e4MvFV1szLhNLap1CCNUd
Surfaces the new enum column types in the tables grid.

- New `select-field/` module: SelectPill (colored option via the shared Badge
  palette), SelectValueEditor (one ChipDropdown-backed picker reused by every
  edit surface), SelectOptionsEditor (add/rename/recolor/remove)
- Option colors are picked from inline squircle swatches — no labels, no
  nested dropdown. Each swatch's fill is a Badge in that variant, so the
  palette stays single-sourced and theme-aware
- Cells render option pills; an empty select cell shows a muted "None" so it
  reads as a dropdown. The single-select menu always offers "None" to clear
- Wired into all three edit surfaces (inline cell, expanded popover, row
  modal) plus the type picker and column-type icons
- ChipDropdown gains `defaultOpen`/`onOpenChange` so the inline cell editor
  can open on mount and commit when the menu closes; open state is now
  controlled in both single and multi modes

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014e4MvFV1szLhNLap1CCNUd
Column auto-resize measured `String(val)` for every non-json/date column,
which for a select cell is the opaque option id (and for multiselect the
comma-joined id array). Selecting auto-fit on a select column therefore
sized it to ids the user never sees. Measure the resolved option names
instead, matching what the pills actually render.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014e4MvFV1szLhNLap1CCNUd
Removes the per-option swatch picker and defaults every option to the
neutral gray pill. The `color` field stays in the data model and the
SELECT_COLORS palette/contract are untouched, so a picker can be re-added
later as a pure UI change with no migration.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014e4MvFV1szLhNLap1CCNUd
@vercel

vercel Bot commented Jul 23, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Jul 23, 2026 2:35am

Request Review

@cursor

cursor Bot commented Jul 23, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Touches table schema validation, column PATCH routing, and row write/coercion paths; changes are well-tested but affect core table data semantics and multiple edit surfaces.

Overview
Adds select and multiselect column types: each column declares a fixed option set (stored in schema JSONB); cells persist stable option ids (string vs string[]) so renames don’t rewrite rows.

Server & API: New updateColumnOptions, extended addTableColumn / updateColumnType, row/column validation and coercion (id or display name → id), and type-change compatibility checks against the target option set. Column PATCH routes (app + v1) route options-only updates separately from updateColumnType when the type is unchanged, so options aren’t dropped. Contracts and normalizeColumn now carry options.

UI: Column sidebar manages options; grid shows pill badges (stale ids as gray fallback), with pickers in inline edit, expanded popover, and row modal. ChipDropdown gains defaultOpen / controlled open + onOpenChange for inline select editing. Grid uses structural equality for multiselect saves and resolves option labels for column auto-fit.

Reviewed by Cursor Bugbot for commit 4db5e88. Bugbot is set up for automated code reviews on this repo. Configure here.

Comment thread apps/sim/lib/table/columns/service.ts Outdated
@greptile-apps

greptile-apps Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds select and multiselect columns to Tables. The main changes are:

  • Stable option IDs stored in row data.
  • Server-side option validation and name-to-ID coercion.
  • Option management in the column configuration sidebar.
  • Pill rendering and editing across all table views.
  • Required-value guards for single-select and multiselect editors.

Confidence Score: 5/5

The latest fixes look safe to merge.

  • Required single-select fields no longer expose a clear option.
  • Required multiselect fields no longer allow the final option to be removed.
  • Type conversion now checks existing values against the target option set.

Important Files Changed

Filename Overview
apps/sim/lib/table/columns/service.ts Adds option-aware column creation, type conversion checks, and option-set updates.
apps/sim/lib/table/validation.ts Adds select value resolution, coercion, membership validation, and option-set validation.
apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/select-field/select-value-editor.tsx Adds the shared select editor and prevents users from explicitly clearing required selections.
apps/sim/lib/api/contracts/tables.ts Extends table API contracts with select types, bounded option lists, and cross-field validation.

Reviews (4): Last reviewed commit: "fix(tables): expanded multiselect string..." | Re-trigger Greptile

Comment thread apps/sim/lib/table/columns/service.ts Outdated
Addresses review findings on the select/multiselect column types:

- Column type conversion now checks each existing value against the target
  option set (resolve by id or name); a `select`/`multiselect` change is
  blocked when values don't fit, instead of accepting shapes that later
  coercion would strand or silently drop
- Inline select editor discards its draft on Escape (matching the text/date
  editors) rather than committing on menu close
- A required single-select no longer offers "None" — clearing to null could
  never be committed

Exports resolveSelectOptionId from validation for the conversion gate.
@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator Author

@greptile

@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/sim/app/api/table/[tableId]/columns/route.ts
Round 2 review fixes:

- A required multiselect can no longer be emptied — the toggle that would
  remove the last option is ignored, since an empty selection can't be
  committed (server rejects it). Mirrors the required single-select "None"
  guard.
- Inline cell save now compares old vs new value structurally, so a no-op
  edit (e.g. opening a multiselect and closing it unchanged, producing a new
  array reference) no longer writes a row update or pushes an undo entry.
  Uses JSON compare for arrays/objects, matching the existing optionsEqual
  convention; primitives keep the === fast path.
@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator Author

@greptile

@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/sim/app/api/table/[tableId]/columns/route.ts
Round 3 review fixes:

- Expanded-cell popover normalizes a multiselect's initial value with
  toSelectedIds, so a single option-id string (the normal shape right after a
  select->multiselect conversion, before the row is rewritten) is no longer
  collapsed to an empty array and cleared on save.
- Column PATCH now only routes to updateColumnType on a real type change.
  updateColumnType early-returns on an unchanged type, so a payload repeating
  the current type together with new options previously applied neither; an
  unchanged type with options now routes to the options-only update. Fixed in
  both the internal and v1 column routes.
@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator Author

@greptile

@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 4db5e88. Configure here.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant