RFC: Inline Cell Editing for SDTF Tables
RFC: Inline Cell Editing for SDTF Tables
- Authors: Prathik Shetty(@pshettydev)
- Date: 2025-12-15
- Status: Accepted
Abstract
This RFC proposes an Airtable/Teable-like inline editing experience for SDTF-rendered tables in the frontend.
The goal is to allow users to click to activate a cell, double-click to edit (for supported field types), and auto-save on blur/Enter with Esc to cancel — while keeping the pinned (first) data column sheet-only.
This RFC covers:
- Frontend interaction and UI behavior
- Schema & API requirements needed to support inline editing across core + dynamic tables
Motivation
SDTF tables currently render dynamic data effectively, but editing is primarily performed via a side sheet. This creates extra steps for high-volume workflows where users need to update many fields rapidly.
Inline editing improves:
- Speed: fewer context switches compared to opening the sheet.
- Usability: spreadsheet-like workflow familiar to Airtable users.
- Consistency: a single “edit-in-place” pattern across native and custom fields.
At the same time, the pinned (first) column remains a reliable entry-point into the existing sheet experience (details, activity, multi-tab layouts).
Approaches
1) Keep sheet-only editing (status quo)
- Pros: no new interaction complexity; existing validation/UI already works.
- Cons: slow for bulk edits; feels non-spreadsheet-like.
2) Make the existing DynamicCell component editable directly
- Pros: minimal component surface area.
- Cons:
DynamicCellis already a large, display-focused renderer; mixing editing concerns risks becoming difficult to maintain.
3) Add a dedicated inline editing layer (choosing this approach)
Introduce a wrapper component that:
- Renders
DynamicCellin read-only mode - Switches to field-type-specific editors when the cell is in “editing” state
This keeps DynamicCell mostly render-only and isolates editing logic.
Proposal
UX / Interaction Rules
- Single click: activates (highlights) a cell.
- Double click: enters edit mode only for supported field types.
- Commit: blur or Enter auto-saves.
- Cancel: Esc restores original value.
- Clear semantics: clearing a value writes
null.
Interaction Sequence Diagram
Pinned Column Behavior
- The pinned (first) data column is sheet-only.
- Single click on pinned column opens the sheet.
- Double click on pinned column does not enter inline edit.
Scope of Editable Field Types
Easy
SHORT_TEXT,LONG_TEXTNUMBER,CURRENCY,PERCENTCHECKBOXDATE,DATETIME
Medium
SINGLE_SELECTMULTI_SELECT
Hard
FORMULA(read-only)USER,ATTACHMENTLINK_TO_ANOTHER_RECORDeditor- Spreadsheet keyboard navigation (arrow/tab)
Component / State Design
1) Centralize cell interaction in row rendering
Update the table row rendering so that click/double-click behavior is handled in one place, avoiding duplicated click handlers.
Maintain two pieces of UI state:
activeCell: { rowId; fieldId } | nulleditingCell: { rowId; fieldId } | null
Only one cell should be edited at a time (at least for now).
2) Introduce EditableDynamicCell
A new component (e.g. apps/frontend/web/src/components/dynamic/TableCell/EditableDynamicCell.tsx) will:
- Render
DynamicCellwhen not editing - Render a type-specific editor when editing
Suggested props:
field,row,value,schemaFieldsisActive,isEditingdraftValue,onDraftChangeonCommit(draftValueOrNull),onCancel()
3) Type-specific editors using shadcn primitives
Use shadcn/radix primitives already present under:
apps/frontend/web/src/components/primitives/*
Editor mapping:
- Text:
Input - Long text:
Textarea - Number/currency/percent:
Input type="number"(with safe parsing) - Checkbox:
Checkbox - Date/DateTime:
Popover+Calendar - Single select:
Select(and optionallyCommandfor search later) - Multi select: a popover list UI; optionally reuse/extend existing
MultiSelectprimitive
Saving Behavior (frontend wiring)
Inline editing should reuse the same “save path” as sheet editing.
- The table-level handler (currently
handleValueChangeinapps/frontend/web/src/components/dynamic/DynamicTable.tsx) should be exposed to row/cell components asonCellValueChange. - On commit, call:
Where value is either a real value or null.
Optionally (recommended for UX): apply an optimistic UI update for the affected cell before the async request returns.
Schema & API Requirements
Inline editing must remain schema-driven (no hardcoded per-field logic in the UI). The UI uses TableSchema.fields[] entries.
SDTF Schema Requirements
1) Field identity
field_id: immutable SDTF field id (e.g.pnfld-...) — used in update payloads.db_field_name: database column / logical field name (e.g.size,recruiter_assignee,contacts).
Important: update payloads use fieldId (SDTF field_id), not db_field_name.
2) Relation metadata
For relational fields:
is_relational: truerelation.type:ONEorMANYrelation.table: related table namerelation.display.format.templateandrelation.display_fieldsdetermine rendering
This metadata is used by the UI to choose editors and decide between scalar vs relational update behavior.
3) field_edit_options (recommended)
Per-field inline editing behavior should be configured via field_edit_options.
Base:
Remote fetch options (server-side search):
Contract: UI calls:
<fetch_endpoint>?page=1&limit=20&search=<term>
and will always include params (fixed query params).
MANY relations: incremental editing can be explicitly configured with multi_select_mode: "incremental":
Note: The inline editor should default to incremental ops for async MANY relations.
4) Static options via formatting.options
If the schema provides formatting.options for a field (common for status-like enums), the UI should:
- Use those options directly (no remote fetch)
- Prefer
option.idas the stored value when present
Alternative (legacy) config: additional_config.filter_configs
Some deployments have fetch configs under additional_config.filter_configs. The UI may fall back to these configs for inline editing, but the preferred path is to migrate to per-field field_edit_options.fetch_options.
Update API Contracts
Inline editing uses a single update endpoint per SDTF table (core or dynamic):
PATCH <schema.endpoint_url>/:id
Update payloads
Scalar / single-value update:
Custom fields (hybrid core table):
MANY relation incremental ops:
and:
Audit fields
Clients must not send audit fields; api-gateway should strip them defensively:
created_by,created_at,updated_by,updated_atlast_updated_by,last_updated_atdeleted_by,deleted_at,is_deleted
Option endpoints (fetch_options.fetch_endpoint)
For every field that uses remote options, api-gateway must expose an endpoint that supports:
page(1-based),limit,search- any additional fixed params (e.g.
department=RESEARCHERS)
The UI normalizer is tolerant, but the easiest response shape is:
Visual States
- Active cell: subtle outline/background.
- Editing cell: stronger outline + editor focus.
- Editors must stop propagation to prevent:
- sheet open (pinned column)
- context menu conflicts
Future Enhancements
- Spreadsheet-like keyboard navigation (arrows/tab/enter workflows)
- Searchable selects via
Command - Richer editors for
USER,ATTACHMENT, and linked records - Better offline/latency handling (queued edits, batching)
- Backend-driven validation/error rendering consistency across inline and sheet edits