Appearance
Data Operations
End-to-end flows for cell editing, row insertion, row deletion, and batch cell editing. All data operations are synchronous — they execute SQL directly on the workspace database with no RabbitMQ or processor involvement.
All data operations are synchronous
Unlike column and view operations (which go through the processor for DDL), data operations execute directly against the customer database. The Quarkus REST API runs the SQL and broadcasts the result via SSE.
Cell Edit
Edits a single cell value — direct SQL UPDATE on the workspace database.
Response: 200 OK
Frontend (Spread) Quarkus REST SSE
───────────────── ──────────── ───
User edits cell value
(inline edit, blur/enter)
│
▼
NgRx action: editCell
│
▼
CellEditEffects
│ PATCH /api/data/cell/{viewUuid}
│ Body: { rowId, columnUuid, value }
│ Header: X-Client-Id: {clientId}
▼
ViewDataResource
.updateCell()
│
▼
ViewDataService
.updateCell()
│
├── Resolve column metadata
│ (type, constraints)
│
├── Validate value
│ (type checking,
│ constraint validation)
│
├── SQL UPDATE on
│ workspace DB
│
└── Broadcast SSE event
│
▼
SSE: data.cell.edited
{
viewUuid, rowId,
columnUuid, value,
originClientId
}
│
├── Same tab (originClientId matches)
│ → Event ignored (echo prevention)
│
└── Other tabs / users
→ Update cell in viewData stateNo Optimistic Update
Cell edits do not use optimistic updates. The flow is:
- User types new value and commits (blur or Enter)
- API request is sent
- Cell shows a subtle loading state
- On
200: cell is updated with the confirmed value - On
400: cell reverts to previous value, validation error shown
This prevents flickering from rejected edits (e.g. type mismatches, constraint violations).
Validation
The backend validates cell values before writing:
- Type checking — value must be compatible with the column type (e.g. a string for a VARCHAR column, a number for INTEGER)
- Constraint validation — checks column constraints (
NOT_BLANK,MIN_LENGTH,MAX_LENGTH,PATTERN,EMAIL,URL,MIN,MAX, etc.) - Nullable — null values rejected for non-nullable columns
On validation failure, the response is 400 Bad Request with field-level error details.
Echo Prevention
Every browser tab has a unique clientId generated via crypto.randomUUID() (from ClientIdService). This is sent as X-Client-Id on every request and echoed back as originClientId in the SSE event.
When an SSE event arrives, ClientIdService.isOwnEvent(originClientId) returns true if the event originated from this tab — the event is then ignored to prevent double-applying the edit.
Row Add
Inserts a new row — direct SQL INSERT on the workspace database.
Response: 201 Created
Frontend (Spread) Quarkus REST SSE
───────────────── ──────────── ───
User opens "Add Row" dialog
fills in fields, submits
│
▼
NgRx action: addRow
│
▼
RowCrudEffects
│ POST /api/data/add/{viewUuid}
│ Body: { field: value, ... }
▼
ViewDataResource
.addRow()
│
▼
ViewDataService
.addRow()
│
├── Validate all fields
│
├── SQL INSERT on
│ workspace DB
│
└── Broadcast SSE event
│
▼
SSE: data.row.inserted
{
viewUuid, row,
originClientId
}
│
├── Same tab
│ → Ignored (already added
│ from API response)
│
└── Other tabs / users
→ Insert row into viewData
(or queue if batching)Add Row Dialog
The "Add Row" dialog is dynamically generated from the view's column metadata:
- Each column becomes a form field with the appropriate input type
- Constraints are applied as form validators
- Required fields (non-nullable, no default) are marked
- Relationship fields show a picker component
Adaptive Row Batching (Remote Inserts)
When remote users insert rows at high frequency, the spread app switches to batched insertion to prevent UI jank:
typescript
const BATCH_CONFIG = {
WINDOW_MS: 1000, // Rolling time window
AUTO_INSERT_THRESHOLD: 3, // Max inserts per window before batch mode
SHOW_PILL_THRESHOLD: 1, // Queued rows before showing pill
};- If more than 3 remote row inserts arrive within 1 second, batch mode activates
- New rows are queued into
PendingRowsState.rowsinstead of being inserted into the grid - A "N new rows" pill notification appears above the grid
- User clicks the pill to flush all pending rows at once
- Batch mode deactivates when the insert frequency drops below the threshold
Row Delete
Deletes a single row — direct SQL DELETE on the workspace database.
Response: 200 OK
Frontend (Spread) Quarkus REST SSE
───────────────── ──────────── ───
User deletes row
(context menu or keyboard)
│
▼
NgRx action: deleteRow
│
▼
RowCrudEffects
│ DELETE /api/data/item/
│ {viewUuid}/{rowId}
▼
ViewDataResource
.deleteRow()
│
▼
ViewDataService
.deleteRow()
│
├── SQL DELETE on
│ workspace DB
│
└── Broadcast SSE event
│
▼
SSE: data.row.deleted
{
viewUuid, rowId,
originClientId
}
│
├── Same tab
│ → Ignored
│
└── Other tabs / users
→ Remove row from viewDataBatch Cell Edit
Edits multiple cells in a single column at once — direct SQL UPDATEs on the workspace database.
Response: 200 OK
Frontend (Spread) Quarkus REST SSE
───────────────── ──────────── ───
User applies bulk value
to selected cells
(e.g. "fill column" action)
│
▼
NgRx action: batchCellEdit
│
▼
CellEditEffects
│ PATCH /api/data/
│ batch-cell-edit/{viewUuid}
│ Body: {
│ columnUuid,
│ cells: [
│ { rowId, value },
│ ...
│ ]
│ }
▼
ViewDataResource
.batchCellEdit()
│
▼
ViewDataService
.batchCellEdit()
│
├── Validate each cell value
│
├── Multiple SQL UPDATEs
│ on workspace DB
│
└── Broadcast SSE event
│
▼
SSE: data.column.filled
{
viewUuid, columnUuid,
cells: [...],
originClientId
}
│
└── Other tabs / users
→ Update all affected
cells in viewDataBatch vs Bulk
These are distinct concepts:
| Batch Cell Edit | Bulk Operations | |
|---|---|---|
| Scope | Multiple cells in one column | Multiple complete rows |
| Processing | Synchronous (direct SQL) | Asynchronous (RabbitMQ job) |
| Response | 200 OK | 202 Accepted |
| Progress | Instant | Tracked with progress events |
| Page | This page | Bulk Operations |
Response Code Summary
| Operation | Method | Success Code | Notes |
|---|---|---|---|
| Cell Edit | PATCH | 200 OK | Single cell update |
| Row Add | POST | 201 Created | Returns created row |
| Row Delete | DELETE | 200 OK | Single row |
| Batch Cell Edit | PATCH | 200 OK | Multiple cells, one column |
Cross-References
- Bulk Operations — async bulk delete, update, and export
- Frontend Overview — SSE implementation and adaptive batching details
- System Overview — SSE architecture