@@ -63,7 +63,6 @@ import type {
6363import { getColumnId } from '@/lib/table/column-keys'
6464import { columnTypeOf } from '@/lib/table/column-types'
6565import { TABLE_LIMITS } from '@/lib/table/constants'
66- import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/providers/workspace-permissions-provider'
6766import type { BlockedTableAction } from '@/app/workspace/[workspaceId]/tables/[tableId]/lock-copy'
6867import { useTimezone } from '@/hooks/queries/general-settings'
6968import {
@@ -83,6 +82,7 @@ import {
8382import { useAddToChat } from '@/hooks/use-add-to-chat'
8483import { useInlineRename } from '@/hooks/use-inline-rename'
8584import { extractCreatedRowId , useTableUndo } from '@/hooks/use-table-undo'
85+ import type { ResourceGrants } from '@/resources'
8686import type { ChatContext } from '@/stores/panel'
8787import type { DeletedRowSnapshot } from '@/stores/table/types'
8888import { useContextMenu , useTable } from '../../hooks'
@@ -171,6 +171,12 @@ interface TableGridProps {
171171 */
172172 workspaceId : string
173173 tableId : string
174+ /**
175+ * What this viewer may do. `grants.write` replaces every `canEdit` read: it is
176+ * `canEdit` exactly, and deliberately NOT `grants.run`, which is
177+ * `canEdit || canRead` and would hand a read-only member the run controls.
178+ */
179+ grants : ResourceGrants
174180 embedded ?: boolean
175181 /** Remote collaborators' cell selections, rendered as presence overlays. */
176182 remoteSelections : RemoteTableSelection [ ]
@@ -425,6 +431,7 @@ async function chunkBatchUpdates(
425431export function TableGrid ( {
426432 workspaceId,
427433 tableId,
434+ grants,
428435 embedded,
429436 remoteSelections,
430437 emitCellSelection,
@@ -651,27 +658,26 @@ export function TableGrid({
651658 if ( measured > 0 && Math . abs ( measured - rowHeight ) >= 0.5 ) setRowHeight ( measured )
652659 } , [ isLoadingTable , isLoadingRows , rowHeight ] )
653660
654- const userPermissions = useUserPermissionsContext ( )
655- const canEditRef = useRef ( userPermissions . canEdit )
656- canEditRef . current = userPermissions . canEdit
661+ const canEditRef = useRef ( grants . write )
662+ canEditRef . current = grants . write
657663
658- const canEditCell = userPermissions . canEdit && ! locks ?. updateLocked
659- const canDeleteRow = userPermissions . canEdit && ! locks ?. deleteLocked
660- const canMutateSchema = userPermissions . canEdit && ! locks ?. schemaLocked
664+ const canEditCell = grants . write && ! locks ?. updateLocked
665+ const canDeleteRow = grants . write && ! locks ?. deleteLocked
666+ const canMutateSchema = grants . write && ! locks ?. schemaLocked
661667 // Dropping or retyping a column rewrites every row's data, so `assertColumnDestructive`
662668 // requires the delete lock clear too — mirror that here or the affordance
663669 // stays live on an append-only table and only fails on click.
664670 const canDestroyColumn = canMutateSchema && ! locks ?. deleteLocked
665671 // Duplicate inserts a full copied row in one shot, so unlike the blank-row
666672 // paths it needs the insert lock only — it is valid on an append-only table.
667- const canInsertFullRow = userPermissions . canEdit && ! locks ?. insertLocked
673+ const canInsertFullRow = grants . write && ! locks ?. insertLocked
668674 // Manual grid entry is "add an empty row, then type into its cells" — the
669675 // typing is an update. So a *useful* manual add needs BOTH insert and update
670676 // unlocked; on an append-only table (update locked) it would leave a blank
671677 // row the user can't fill. The control stays visible and explains itself via
672678 // `onBlockedAction`. Full-row inserts still flow through CSV import / API /
673679 // blocks / Mothership, which the insert lock alone governs server-side.
674- const canManualAddRow = userPermissions . canEdit && ! locks ?. insertLocked && ! locks ?. updateLocked
680+ const canManualAddRow = grants . write && ! locks ?. insertLocked && ! locks ?. updateLocked
675681 const canEditCellRef = useRef ( canEditCell )
676682 canEditCellRef . current = canEditCell
677683 const canManualAddRowRef = useRef ( canManualAddRow )
@@ -4292,7 +4298,7 @@ export function TableGrid({
42924298 groupName = { workflowGroupById . get ( g . groupId ) ?. name }
42934299 onSelectGroup = { handleGroupSelect }
42944300 onOpenConfig = { ( ) => handleConfigureWorkflowGroup ( g . groupId ) }
4295- onRunColumn = { userPermissions . canEdit ? handleRunColumn : undefined }
4301+ onRunColumn = { grants . write ? handleRunColumn : undefined }
42964302 hasActiveFilter = { Boolean ( effectiveFilter ) }
42974303 selectedRowIds = { selectedRowIds }
42984304 // Every locked action passes its blocked handler rather
@@ -4301,28 +4307,28 @@ export function TableGrid({
43014307 // the whole menu — and each item should explain the lock
43024308 // rather than silently vanish or fail with a 423 toast.
43034309 onInsertLeft = {
4304- ! userPermissions . canEdit
4310+ ! grants . write
43054311 ? undefined
43064312 : canMutateSchema
43074313 ? handleInsertColumnLeft
43084314 : handleBlockedAddColumn
43094315 }
43104316 onInsertRight = {
4311- ! userPermissions . canEdit
4317+ ! grants . write
43124318 ? undefined
43134319 : canMutateSchema
43144320 ? handleInsertColumnRight
43154321 : handleBlockedAddColumn
43164322 }
43174323 onDeleteColumn = {
4318- ! userPermissions . canEdit
4324+ ! grants . write
43194325 ? undefined
43204326 : canDestroyColumn
43214327 ? handleDeleteColumn
43224328 : handleBlockedDeleteColumn
43234329 }
43244330 onDeleteGroup = {
4325- ! userPermissions . canEdit
4331+ ! grants . write
43264332 ? undefined
43274333 : canDestroyColumn
43284334 ? handleDeleteWorkflowGroup
@@ -4333,21 +4339,13 @@ export function TableGrid({
43334339 ? undefined
43344340 : handleViewWorkflow
43354341 }
4336- readOnly = { ! userPermissions . canEdit }
4337- onDragStart = {
4338- userPermissions . canEdit ? handleColumnDragStart : undefined
4339- }
4340- onDragOver = {
4341- userPermissions . canEdit ? handleColumnDragOver : undefined
4342- }
4343- onDragEnd = {
4344- userPermissions . canEdit ? handleColumnDragEnd : undefined
4345- }
4346- onDragLeave = {
4347- userPermissions . canEdit ? handleColumnDragLeave : undefined
4348- }
4342+ readOnly = { ! grants . write }
4343+ onDragStart = { grants . write ? handleColumnDragStart : undefined }
4344+ onDragOver = { grants . write ? handleColumnDragOver : undefined }
4345+ onDragEnd = { grants . write ? handleColumnDragEnd : undefined }
4346+ onDragLeave = { grants . write ? handleColumnDragLeave : undefined }
43494347 isPinned = { firstCol ? pinnedColumnSet . has ( firstCol . key ) : false }
4350- onPinToggle = { userPermissions . canEdit ? handlePinToggle : undefined }
4348+ onPinToggle = { grants . write ? handlePinToggle : undefined }
43514349 stickyLeft = { stickyLeft }
43524350 isLastPinned = { lastCol ?. key === lastPinnedColKey }
43534351 />
@@ -4370,7 +4368,7 @@ export function TableGrid({
43704368 />
43714369 )
43724370 } ) }
4373- { userPermissions . canEdit && (
4371+ { grants . write && (
43744372 < th className = 'border-[var(--border)] border-b bg-[var(--bg)] px-2 py-[5px]' />
43754373 ) }
43764374 </ tr >
@@ -4394,7 +4392,7 @@ export function TableGrid({
43944392 // open-config — all metadata, not schema. The schema lock is
43954393 // enforced per-action instead (insert/delete below), and a
43964394 // rename attempt surfaces the server's 423 as a toast.
4397- readOnly = { ! userPermissions . canEdit }
4395+ readOnly = { ! grants . write }
43984396 isRenaming = { columnRename . editingId === column . key }
43994397 isColumnSelected = {
44004398 isColumnSelection &&
@@ -4434,13 +4432,13 @@ export function TableGrid({
44344432 onOpenConfig = { handleConfigureColumn }
44354433 onViewWorkflow = { handleViewWorkflow }
44364434 isPinned = { colIsPinned }
4437- onPinToggle = { userPermissions . canEdit ? handlePinToggle : undefined }
4435+ onPinToggle = { grants . write ? handlePinToggle : undefined }
44384436 stickyLeft = { colStickyLeft }
44394437 isLastPinned = { column . key === lastPinnedColKey }
44404438 />
44414439 )
44424440 } ) }
4443- { userPermissions . canEdit && (
4441+ { grants . write && (
44444442 < NewColumnDropdown
44454443 trigger = 'inline-header'
44464444 disabled = { addColumnMutation . isPending }
@@ -4581,7 +4579,7 @@ export function TableGrid({
45814579 </ >
45824580 ) }
45834581 </ div >
4584- { ! isLoadingTable && ! isLoadingRows && userPermissions . canEdit && (
4582+ { ! isLoadingTable && ! isLoadingRows && grants . write && (
45854583 < AddRowButton onClick = { handleAddRowClick } />
45864584 ) }
45874585 </ div >
@@ -4603,17 +4601,17 @@ export function TableGrid({
46034601 canEditCell = { ! contextMenuIsWorkflowColumn }
46044602 selectedRowCount = { selectedRowCount }
46054603 onRunWorkflows = {
4606- userPermissions . canEdit && hasWorkflowColumns && contextMenuStats . hasIncompleteOrFailed
4604+ grants . write && hasWorkflowColumns && contextMenuStats . hasIncompleteOrFailed
46074605 ? handleRunWorkflowsOnSelection
46084606 : undefined
46094607 }
46104608 onRefreshWorkflows = {
4611- userPermissions . canEdit && hasWorkflowColumns && contextMenuStats . hasCompleted
4609+ grants . write && hasWorkflowColumns && contextMenuStats . hasCompleted
46124610 ? handleRefreshWorkflowsOnSelection
46134611 : undefined
46144612 }
46154613 onStopWorkflows = {
4616- userPermissions . canEdit && hasWorkflowColumns ? handleStopWorkflowsOnSelection : undefined
4614+ grants . write && hasWorkflowColumns ? handleStopWorkflowsOnSelection : undefined
46174615 }
46184616 runningInSelectionCount = { runningInContextSelection }
46194617 hasWorkflowColumns = { hasWorkflowColumns }
0 commit comments