@@ -29,6 +29,7 @@ import {
2929 ArrowRight ,
3030 Calendar as CalendarIcon ,
3131 ChevronDown ,
32+ EyeOff ,
3233 Pencil ,
3334 PlayOutline ,
3435 Plus ,
@@ -79,6 +80,7 @@ import {
7980import { useWorkflowStates , useWorkflows } from '@/hooks/queries/workflows'
8081import { useInlineRename } from '@/hooks/use-inline-rename'
8182import { extractCreatedRowId , useTableUndo } from '@/hooks/use-table-undo'
83+ import { useLogDetailsUIStore } from '@/stores/logs/store'
8284import type { DeletedRowSnapshot } from '@/stores/table/types'
8385import type { WorkflowMetadata } from '@/stores/workflows/registry/types'
8486import { useContextMenu , useRowExecution , useTableData } from '../../hooks'
@@ -264,6 +266,8 @@ const COL_WIDTH = 160
264266const COL_WIDTH_MIN = 80
265267const CHECKBOX_COL_WIDTH = 56
266268const ADD_COL_WIDTH = 120
269+ /** Width of the column-config slideout (matches `column-sidebar.tsx`'s `w-[400px]`). */
270+ const COLUMN_SIDEBAR_WIDTH = 400
267271const SKELETON_COL_COUNT = 4
268272const SKELETON_ROW_COUNT = 10
269273const ROW_HEIGHT_ESTIMATE = 35
@@ -2226,6 +2230,18 @@ export function Table({
22262230 const [ configState , setConfigState ] = useState < ColumnConfigState > ( null )
22272231 /** Execution id whose run details are open in the slideout. */
22282232 const [ executionDetailsId , setExecutionDetailsId ] = useState < string | null > ( null )
2233+ /**
2234+ * Right padding added to the table's scroll content while a slideout panel
2235+ * is open, equal to the panel's width. Without it, the rightmost columns are
2236+ * clipped under the panel and there's no way to scroll them into view.
2237+ * The two panels are mutually exclusive (each opener closes the other).
2238+ */
2239+ const logPanelWidth = useLogDetailsUIStore ( ( state ) => state . panelWidth )
2240+ const sidebarReservedPx = configState
2241+ ? COLUMN_SIDEBAR_WIDTH
2242+ : executionDetailsId
2243+ ? logPanelWidth
2244+ : 0
22292245
22302246 const handleConfigureColumn = useCallback ( ( columnName : string ) => {
22312247 setExecutionDetailsId ( null )
@@ -2239,7 +2255,13 @@ export function Table({
22392255 [ deleteWorkflowGroupMutation ]
22402256 )
22412257
2242- const handleDeleteColumn = useCallback ( ( columnName : string ) => {
2258+ /**
2259+ * Computes the names slated for deletion given a click on `columnName` and
2260+ * the current column selection. If the click landed inside a multi-column
2261+ * selection, the entire selection is the target; otherwise it's just the
2262+ * clicked column.
2263+ */
2264+ const resolveDeletionNames = useCallback ( ( columnName : string ) : string [ ] => {
22432265 const cols = columnsRef . current
22442266 if ( isColumnSelectionRef . current && selectionAnchorRef . current ) {
22452267 const sel = computeNormalizedSelection ( selectionAnchorRef . current , selectionFocusRef . current )
@@ -2250,16 +2272,63 @@ export function Table({
22502272 for ( let c = sel . startCol ; c <= sel . endCol ; c ++ ) {
22512273 if ( c < cols . length ) names . push ( cols [ c ] . name )
22522274 }
2253- if ( names . length > 0 ) {
2254- setDeletingColumns ( names )
2255- return
2256- }
2275+ if ( names . length > 0 ) return names
22572276 }
22582277 }
22592278 }
2260- setDeletingColumns ( [ columnName ] )
2279+ return [ columnName ]
22612280 } , [ ] )
22622281
2282+ /**
2283+ * Hide a workflow-output column by removing it from its group's `outputs`
2284+ * via `updateWorkflowGroup`. Server-side this drops the schema column AND
2285+ * wipes the cell data on every row. The user can re-add the output from
2286+ * the sidebar's picker; the existing backfill repopulates from execution
2287+ * logs. Only valid when removing the columns leaves every affected group
2288+ * with at least one surviving output — caller must check first.
2289+ */
2290+ const hideWorkflowOutputColumns = useCallback (
2291+ ( names : string [ ] ) => {
2292+ const schemaCols = schemaColumnsRef . current
2293+ const groups = workflowGroupsRef . current
2294+ const removalsByGroup = new Map < string , Set < string > > ( )
2295+ for ( const name of names ) {
2296+ const def = schemaCols . find ( ( c ) => c . name === name )
2297+ if ( ! def ?. workflowGroupId ) return false
2298+ const set = removalsByGroup . get ( def . workflowGroupId ) ?? new Set < string > ( )
2299+ set . add ( name )
2300+ removalsByGroup . set ( def . workflowGroupId , set )
2301+ }
2302+ for ( const [ groupId , removed ] of removalsByGroup ) {
2303+ const group = groups . find ( ( g ) => g . id === groupId )
2304+ if ( ! group ) return false
2305+ const remaining = group . outputs . filter ( ( o ) => ! removed . has ( o . columnName ) )
2306+ if ( remaining . length === 0 ) return false
2307+ updateWorkflowGroupMutation . mutate ( {
2308+ groupId : group . id ,
2309+ workflowId : group . workflowId ,
2310+ name : group . name ,
2311+ dependencies : group . dependencies ,
2312+ outputs : remaining ,
2313+ } )
2314+ }
2315+ return true
2316+ } ,
2317+ [ updateWorkflowGroupMutation ]
2318+ )
2319+
2320+ const handleDeleteColumn = useCallback (
2321+ ( columnName : string ) => {
2322+ const names = resolveDeletionNames ( columnName )
2323+ // If every target is a workflow output AND removing them all leaves each
2324+ // group with ≥1 output, hide them directly — no destructive-confirm
2325+ // modal, since the workflow can re-produce the value any time.
2326+ if ( hideWorkflowOutputColumns ( names ) ) return
2327+ setDeletingColumns ( names )
2328+ } ,
2329+ [ resolveDeletionNames , hideWorkflowOutputColumns ]
2330+ )
2331+
22632332 const handleDeleteColumnConfirm = useCallback ( ( ) => {
22642333 if ( ! deletingColumns || deletingColumns . length === 0 ) return
22652334 const columnsToDelete = [ ...deletingColumns ]
@@ -2658,7 +2727,13 @@ export function Table({
26582727 onDragOver = { handleScrollDragOver }
26592728 onDrop = { handleScrollDrop }
26602729 >
2661- < div className = 'relative h-fit' style = { { width : `${ tableWidth } px` } } >
2730+ < div
2731+ className = 'relative h-fit'
2732+ style = { {
2733+ width : `${ tableWidth + sidebarReservedPx } px` ,
2734+ paddingRight : sidebarReservedPx ,
2735+ } }
2736+ >
26622737 < table
26632738 className = 'table-fixed border-separate border-spacing-0 text-small'
26642739 style = { { width : `${ tableWidth } px` } }
@@ -3880,6 +3955,7 @@ function ColumnOptionsMenu({
38803955 onOpenChange,
38813956 position,
38823957 column,
3958+ deleteLabel,
38833959 onOpenConfig,
38843960 onInsertLeft,
38853961 onInsertRight,
@@ -3892,6 +3968,11 @@ function ColumnOptionsMenu({
38923968 onOpenChange : ( open : boolean ) => void
38933969 position : { x : number ; y : number }
38943970 column : DisplayColumn
3971+ /** Override for the destructive item's label. Defaults to "Delete column"
3972+ * (or "Delete workflow" when `onDeleteGroup` is set). Use "Hide column"
3973+ * when the destructive action is non-lossy (workflow-output column where
3974+ * removing it leaves the group with siblings). */
3975+ deleteLabel ?: string
38953976 onOpenConfig : ( columnName : string ) => void
38963977 onInsertLeft : ( columnName : string ) => void
38973978 onInsertRight : ( columnName : string ) => void
@@ -3963,8 +4044,8 @@ function ColumnOptionsMenu({
39634044 < DropdownMenuItem
39644045 onSelect = { ( ) => ( onDeleteGroup ? onDeleteGroup ( ) : onDeleteColumn ( column . name ) ) }
39654046 >
3966- < Trash />
3967- { onDeleteGroup ? 'Delete workflow' : 'Delete column' }
4047+ { deleteLabel === 'Hide column' ? < EyeOff /> : < Trash /> }
4048+ { deleteLabel ?? ( onDeleteGroup ? 'Delete workflow' : 'Delete column' ) }
39684049 </ DropdownMenuItem >
39694050 </ DropdownMenuContent >
39704051 </ DropdownMenu >
@@ -4190,6 +4271,14 @@ const ColumnHeaderMenu = React.memo(function ColumnHeaderMenu({
41904271 const configuredWorkflow = ownGroup
41914272 ? workflows ?. find ( ( w ) => w . id === ownGroup . workflowId )
41924273 : undefined
4274+ // Workflow-output column with siblings → "Hide column" (non-destructive,
4275+ // re-addable from sidebar). Last output of a group → "Delete workflow"
4276+ // (removes the entire group). Plain column → undefined (default "Delete column").
4277+ const deleteLabel = ownGroup
4278+ ? ownGroup . outputs . length > 1
4279+ ? 'Hide column'
4280+ : 'Delete workflow'
4281+ : undefined
41934282 const workflowColor = configuredWorkflow ?. color
41944283 const blockIconInfo = sourceInfo ?. blockIconInfo
41954284 const blockName = sourceInfo ?. blockName
@@ -4416,6 +4505,7 @@ const ColumnHeaderMenu = React.memo(function ColumnHeaderMenu({
44164505 onOpenChange = { setMenuOpen }
44174506 position = { menuPosition }
44184507 column = { column }
4508+ deleteLabel = { deleteLabel }
44194509 onOpenConfig = { onOpenConfig }
44204510 onInsertLeft = { onInsertLeft }
44214511 onInsertRight = { onInsertRight }
0 commit comments