Skip to content

Commit a86999e

Browse files
committed
fix(tables): fix bulk ops truncation for tables larger than one page
Bulk operations (column-header delete, select-all copy/cut/delete/run) were silently truncated to the first 1000 rows because handlers only iterated the loaded pages from useInfiniteQuery. Fix: - Extract tableRowsInfiniteOptions factory (infiniteQueryOptions) so the hook and imperative drain share the same typed cache key - Add background drain via useEffect watching hasNextPage/isFetchingNextPage — chains fetchNextPage until getNextPageParam returns undefined - Add ensureAllRowsLoaded to use-table: reads cache via getQueryData + calls fetchNextPage in a while loop until the last page is partial - Await ensureAllRowsLoaded at every kind:'all' bulk-op entry point in table-grid (column delete, copy, cut, action-bar delete/run) - Add chunkBatchUpdates to send updates in MAX_BULK_OPERATION_SIZE=1000 chunks so server validation never rejects oversized batches - Fix undo-redo: make executeAction async and chunk clear-cells, update-cells, and delete-column cell-restore with mutateAsync loops Tests: 41 passing across use-table, tables queries, and use-table-undo
1 parent b74f8da commit a86999e

13 files changed

Lines changed: 1057 additions & 193 deletions

File tree

apps/sim/app/api/table/import-csv/route.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
inferSchemaFromCsv,
1818
parseCsvBuffer,
1919
sanitizeName,
20+
TABLE_LIMITS,
2021
type TableSchema,
2122
} from '@/lib/table'
2223
import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils'
@@ -67,7 +68,10 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
6768
const { headers, rows } = await parseCsvBuffer(buffer, delimiter)
6869

6970
const { columns, headerToColumn } = inferSchemaFromCsv(headers, rows)
70-
const tableName = sanitizeName(file.name.replace(/\.[^.]+$/, ''), 'imported_table')
71+
const tableName = sanitizeName(file.name.replace(/\.[^.]+$/, ''), 'imported_table').slice(
72+
0,
73+
TABLE_LIMITS.MAX_TABLE_NAME_LENGTH
74+
)
7175
const planLimits = await getWorkspaceTableLimits(workspaceId)
7276

7377
const normalizedSchema: TableSchema = {

apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-grid/cells/expanded-cell-popover.tsx

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,11 @@ const EXPANDED_CELL_MIN_WIDTH = 420
2222
const EXPANDED_CELL_HEIGHT = 280
2323

2424
/**
25-
* Supabase-style anchored cell expander. Floats over the clicked cell at the cell's
26-
* top-left, minimum width {@link EXPANDED_CELL_MIN_WIDTH}, fixed height, internally
27-
* scrollable. Triggered by cell double-click so long values are readable/editable
28-
* without widening the column. Inline edit via Enter/F2/typing is unaffected.
25+
* Anchored cell editor. Floats over the double-clicked cell, minimum width
26+
* {@link EXPANDED_CELL_MIN_WIDTH}, fixed height, internally scrollable.
2927
*
30-
* Workflow and boolean cells are read-only in this view — workflow cells are driven
31-
* by the scheduler, booleans use a checkbox cell inline.
28+
* Workflow and boolean cells are read-only here — workflow cells are driven
29+
* by the scheduler, booleans toggle inline.
3230
*/
3331
export function ExpandedCellPopover({
3432
expandedCell,

apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-grid/cells/inline-editors.tsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ function InlineDateEditor({
131131
)
132132
}
133133

134-
/** Inline editor for `string`/`number`/`json` columns — single-line text input. */
134+
/** Inline editor for `string`/`number`/`json` columns — single-line text input. Number columns use `type="number"` so the browser rejects non-numeric input. */
135135
function InlineTextEditor({
136136
value,
137137
column,
@@ -193,16 +193,21 @@ function InlineTextEditor({
193193
}
194194
}
195195

196+
const isNumber = column.type === 'number'
197+
196198
return (
197199
<input
198200
ref={inputRef}
199-
type='text'
200-
value={draft}
201+
type={isNumber ? 'number' : 'text'}
202+
step={isNumber ? 'any' : undefined}
203+
value={draft ?? ''}
201204
onChange={(e) => setDraft(e.target.value)}
202205
onKeyDown={handleKeyDown}
203206
onBlur={() => doSave('blur')}
204207
className={cn(
205-
'w-full min-w-0 select-text border-none bg-transparent p-0 text-[var(--text-primary)] text-small outline-none'
208+
'w-full min-w-0 select-text border-none bg-transparent p-0 text-[var(--text-primary)] text-small outline-none',
209+
isNumber &&
210+
'[appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none'
206211
)}
207212
/>
208213
)

0 commit comments

Comments
 (0)