Skip to content

Commit 2afe917

Browse files
waleedlatif1claude
andauthored
improvement(tables): fix cell editing flash, batch API docs, and UI polish (#3507)
* fix: show text cursor in chunk editor and ensure textarea fills container Add cursor-text to the editor wrapper so the whole area shows a text cursor. Click on empty space focuses the textarea. Changed textarea from h-full/w-full to flex-1/min-h-0 so it properly fills the flex container. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * improvement(tables): fix cell editing flash, add batch API docs, and UI polish Fix stale-data flash when saving inline cell edits by using TanStack Query's isPending+variables pattern instead of manual cache writes. Also adds OpenAPI docs for batch table endpoints, DatePicker support in row modal, duplicate row in context menu, and styling improvements. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: remove dead resolveColumnFromEvent callback Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: unify paste undo into single create-rows action Batch-created rows from paste now push one `create-rows` undo entry instead of N individual `create-row` entries, so a single Ctrl+Z reverses the entire paste operation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: validate dates in inline editor and displayToStorage InlineDateEditor now validates computed values via Date.parse before saving, preventing invalid strings like "hello" from being sent to the server. displayToStorage now rejects out-of-range month/day values (e.g. 13/32) instead of producing invalid YYYY-MM-DD strings. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: accept ISO date format in inline date editor Fall back to raw draft input when displayToStorage returns null, so valid ISO dates like "2024-03-15" pasted or typed directly are accepted instead of silently discarded. Date.parse still validates the final value. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: add ISO date support to displayToStorage and fix picker Escape displayToStorage now recognizes YYYY-MM-DD input directly, so ISO dates typed or pasted work correctly for both saving and picker sync. DatePicker Escape now refocuses the input instead of saving, so the user can press Escape again to cancel or Enter to confirm — matching the expected cancel behavior. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: remove dead paste boundary check The totalR guard in handlePaste could never trigger since totalR included pasteRows.length, making targetRow always < totalR. Remove the unused variable and simplify the selection focus calc. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * update openapi --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 6f694e5 commit 2afe917

File tree

14 files changed

+1600
-995
lines changed

14 files changed

+1600
-995
lines changed

apps/docs/openapi.json

Lines changed: 944 additions & 297 deletions
Large diffs are not rendered by default.

apps/sim/app/api/table/[tableId]/rows/route.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -690,6 +690,10 @@ export async function PATCH(request: NextRequest, { params }: TableRowsRoutePara
690690
if (
691691
errorMessage.includes('Row size exceeds') ||
692692
errorMessage.includes('Schema validation') ||
693+
errorMessage.includes('must be valid') ||
694+
errorMessage.includes('must be string') ||
695+
errorMessage.includes('must be number') ||
696+
errorMessage.includes('must be boolean') ||
693697
errorMessage.includes('must be unique') ||
694698
errorMessage.includes('Unique constraint violation') ||
695699
errorMessage.includes('Cannot set unique column') ||

apps/sim/app/api/table/route.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ const CreateTableSchema = z.object({
6565
),
6666
}),
6767
workspaceId: z.string().min(1, 'Workspace ID is required'),
68+
initialRowCount: z.number().int().min(0).max(100).optional(),
6869
})
6970

7071
const ListTablesSchema = z.object({
@@ -133,6 +134,7 @@ export async function POST(request: NextRequest) {
133134
userId: authResult.userId,
134135
maxRows: planLimits.maxRowsPerTable,
135136
maxTables: planLimits.maxTables,
137+
initialRowCount: params.initialRowCount,
136138
},
137139
requestId
138140
)

apps/sim/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/components/chunk-editor/chunk-editor.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -125,9 +125,14 @@ export function ChunkEditor({
125125

126126
return (
127127
<div className='flex flex-1 flex-col overflow-hidden'>
128-
<div className='flex flex-1 overflow-hidden'>
128+
<div
129+
className='flex min-h-0 flex-1 cursor-text overflow-hidden'
130+
onClick={(e) => {
131+
if (e.target === e.currentTarget) textareaRef.current?.focus()
132+
}}
133+
>
129134
{tokenizerOn ? (
130-
<div className='h-full w-full overflow-y-auto whitespace-pre-wrap break-words p-[24px] font-sans text-[14px] text-[var(--text-body)]'>
135+
<div className='h-full w-full cursor-default overflow-y-auto whitespace-pre-wrap break-words p-[24px] font-sans text-[14px] text-[var(--text-body)]'>
131136
{tokenStrings.map((token, index) => (
132137
<span
133138
key={index}
@@ -153,7 +158,7 @@ export function ChunkEditor({
153158
? 'This chunk is synced from a connector and cannot be edited'
154159
: 'Read-only view'
155160
}
156-
className='h-full w-full resize-none border-0 bg-transparent p-[24px] font-sans text-[14px] text-[var(--text-body)] outline-none placeholder:text-[var(--text-subtle)]'
161+
className='min-h-0 flex-1 resize-none border-0 bg-transparent p-[24px] font-sans text-[14px] text-[var(--text-body)] outline-none placeholder:text-[var(--text-subtle)]'
157162
disabled={!canEdit}
158163
readOnly={!canEdit}
159164
spellCheck={false}

apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/context-menu/context-menu.tsx

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,19 @@ import {
55
PopoverDivider,
66
PopoverItem,
77
} from '@/components/emcn'
8-
import { ArrowDown, ArrowUp, Pencil, Trash } from '@/components/emcn/icons'
8+
import { ArrowDown, ArrowUp, Duplicate, Pencil, Trash } from '@/components/emcn/icons'
99
import type { ContextMenuState } from '../../types'
1010

11+
const ICON = 'h-3.5 w-3.5'
12+
1113
interface ContextMenuProps {
1214
contextMenu: ContextMenuState
1315
onClose: () => void
1416
onEditCell: () => void
1517
onDelete: () => void
1618
onInsertAbove: () => void
1719
onInsertBelow: () => void
20+
onDuplicate: () => void
1821
selectedRowCount?: number
1922
disableEdit?: boolean
2023
disableInsert?: boolean
@@ -28,6 +31,7 @@ export function ContextMenu({
2831
onDelete,
2932
onInsertAbove,
3033
onInsertBelow,
34+
onDuplicate,
3135
selectedRowCount = 1,
3236
disableEdit = false,
3337
disableInsert = false,
@@ -61,7 +65,7 @@ export function ContextMenu({
6165
onClose()
6266
}}
6367
>
64-
<Pencil />
68+
<Pencil className={ICON} />
6569
Edit cell
6670
</PopoverItem>
6771
)}
@@ -72,7 +76,7 @@ export function ContextMenu({
7276
onClose()
7377
}}
7478
>
75-
<ArrowUp />
79+
<ArrowUp className={ICON} />
7680
Insert row above
7781
</PopoverItem>
7882
<PopoverItem
@@ -82,19 +86,29 @@ export function ContextMenu({
8286
onClose()
8387
}}
8488
>
85-
<ArrowDown />
89+
<ArrowDown className={ICON} />
8690
Insert row below
8791
</PopoverItem>
92+
<PopoverItem
93+
disabled={disableInsert || selectedRowCount > 1}
94+
onClick={() => {
95+
onDuplicate()
96+
onClose()
97+
}}
98+
>
99+
<Duplicate className={ICON} />
100+
Duplicate row
101+
</PopoverItem>
88102
<PopoverDivider />
89103
<PopoverItem
90104
disabled={disableDelete}
91105
onClick={() => {
92106
onDelete()
93107
onClose()
94108
}}
95-
className='text-[var(--text-error)] focus:text-[var(--text-error)]'
109+
className='!text-[var(--text-error)] [&_svg]:!text-[var(--text-error)]'
96110
>
97-
<Trash />
111+
<Trash className={ICON} />
98112
{deleteLabel}
99113
</PopoverItem>
100114
</PopoverContent>

apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/row-modal/row-modal.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { useParams } from 'next/navigation'
66
import {
77
Button,
88
Checkbox,
9+
DatePicker,
910
Input,
1011
Label,
1112
Modal,
@@ -292,10 +293,17 @@ function ColumnField({ column, value, onChange }: ColumnFieldProps) {
292293
className='font-mono text-[12px]'
293294
required={column.required}
294295
/>
296+
) : column.type === 'date' ? (
297+
<DatePicker
298+
mode='single'
299+
value={formatValueForInput(value, column.type) || undefined}
300+
onChange={(dateStr) => onChange(dateStr)}
301+
placeholder='Select date'
302+
/>
295303
) : (
296304
<Input
297305
id={column.name}
298-
type={column.type === 'number' ? 'number' : column.type === 'date' ? 'date' : 'text'}
306+
type={column.type === 'number' ? 'number' : 'text'}
299307
value={formatValueForInput(value, column.type)}
300308
onChange={(e) => onChange(e.target.value)}
301309
placeholder={`Enter ${column.name}`}

0 commit comments

Comments
 (0)