From fda213a4e8a7415e84e75f54f27ddfd926f14c5f Mon Sep 17 00:00:00 2001
From: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
Date: Wed, 26 Aug 2026 14:03:46 -0700
Subject: [PATCH] Improve automations table layout and column sizing (#16667)
* Improve automations table layout and column sizing
- Wrap table containers with min-width constraint for horizontal scrolling
- Adjust grid column widths for better visual balance
- Simplify automation draft building with helper function
- Remove unused validation checks and imports
* Make automation list first column sticky
- Keep automation name visible when scrolling horizontally
- Adjust header z-index to layer above sticky cells
* Remove unused canCreateAutomation prop from test
---
.../AutomationListExternalRows.tsx | 10 +++-
.../automations/AutomationListLocalRows.tsx | 10 +++-
.../automations/AutomationListTableHeader.tsx | 16 ++++++-
.../automations/AutomationsListPanel.test.tsx | 2 +-
.../automations/AutomationsListPanel.tsx | 4 +-
.../automations/AutomationsPage.tsx | 14 ------
.../automations/AutomationsPageSkeleton.tsx | 47 ++++++++++++-------
.../automations/automations-table-layout.ts | 2 +-
src/renderer/src/lib/list-table-layout.ts | 17 ++++++-
9 files changed, 78 insertions(+), 44 deletions(-)
diff --git a/src/renderer/src/components/automations/AutomationListExternalRows.tsx b/src/renderer/src/components/automations/AutomationListExternalRows.tsx
index 12e8f3c0c85..976a93b2433 100644
--- a/src/renderer/src/components/automations/AutomationListExternalRows.tsx
+++ b/src/renderer/src/components/automations/AutomationListExternalRows.tsx
@@ -32,7 +32,11 @@ import {
import { getExternalAutomationScheduleDisplay } from './external-automation-schedule-display'
import { getExternalAutomationActionDisabledMessage } from './external-automation-source-availability'
import { AUTOMATIONS_TABLE_GRID_CLASS } from './automations-table-layout'
-import { LIST_TABLE_ROW_CLASS, LIST_TABLE_ROW_SELECTED_CLASS } from '@/lib/list-table-layout'
+import {
+ LIST_TABLE_ROW_CLASS,
+ LIST_TABLE_ROW_SELECTED_CLASS,
+ LIST_TABLE_STICKY_ROW_CELL_CLASS
+} from '@/lib/list-table-layout'
import { isPortaledRowMenuClick, isRowActivationKey } from '@/lib/list-row-interaction'
import { getExternalAutomationLastRunSnapshot } from './automation-list-last-run'
import { AutomationListLastRunCell } from './AutomationListLastRunCell'
@@ -121,7 +125,9 @@ export function AutomationListExternalRows({
isSelected && LIST_TABLE_ROW_SELECTED_CLASS
)}
>
- {entry.job.name}
+
+ {entry.job.name}
+
{scheduleLabel}
diff --git a/src/renderer/src/components/automations/AutomationListLocalRows.tsx b/src/renderer/src/components/automations/AutomationListLocalRows.tsx
index d0aef5d899b..292eb545b4d 100644
--- a/src/renderer/src/components/automations/AutomationListLocalRows.tsx
+++ b/src/renderer/src/components/automations/AutomationListLocalRows.tsx
@@ -49,7 +49,11 @@ import {
type AutomationUsageSummary
} from './automation-usage-model'
import { AUTOMATIONS_TABLE_GRID_CLASS } from './automations-table-layout'
-import { LIST_TABLE_ROW_CLASS, LIST_TABLE_ROW_SELECTED_CLASS } from '@/lib/list-table-layout'
+import {
+ LIST_TABLE_ROW_CLASS,
+ LIST_TABLE_ROW_SELECTED_CLASS,
+ LIST_TABLE_STICKY_ROW_CELL_CLASS
+} from '@/lib/list-table-layout'
import { isPortaledRowMenuClick, isRowActivationKey } from '@/lib/list-row-interaction'
import { AutomationListStatusCell } from './AutomationListStatusCell'
import { translate } from '@/i18n/i18n'
@@ -246,7 +250,9 @@ export function AutomationListLocalRows({
isSelected && LIST_TABLE_ROW_SELECTED_CLASS
)}
>
- {automation.name}
+
+ {automation.name}
+
{scheduleLabel}
diff --git a/src/renderer/src/components/automations/AutomationListTableHeader.tsx b/src/renderer/src/components/automations/AutomationListTableHeader.tsx
index d76c749927d..dcbd107fcbc 100644
--- a/src/renderer/src/components/automations/AutomationListTableHeader.tsx
+++ b/src/renderer/src/components/automations/AutomationListTableHeader.tsx
@@ -1,7 +1,10 @@
import React from 'react'
import { translate } from '@/i18n/i18n'
import { AUTOMATIONS_TABLE_GRID_CLASS } from './automations-table-layout'
-import { LIST_TABLE_HEADER_CLASS } from '@/lib/list-table-layout'
+import {
+ LIST_TABLE_HEADER_CLASS,
+ LIST_TABLE_STICKY_HEADER_CELL_CLASS
+} from '@/lib/list-table-layout'
export function AutomationListTableHeader(): React.JSX.Element {
const labels = [
@@ -17,7 +20,16 @@ export function AutomationListTableHeader(): React.JSX.Element {
return (
{labels.map(([key, fallback], index) => (
-
+
{translate(key, fallback)}
))}
diff --git a/src/renderer/src/components/automations/AutomationsListPanel.test.tsx b/src/renderer/src/components/automations/AutomationsListPanel.test.tsx
index 3ccaefabfab..21ca21f495e 100644
--- a/src/renderer/src/components/automations/AutomationsListPanel.test.tsx
+++ b/src/renderer/src/components/automations/AutomationsListPanel.test.tsx
@@ -71,6 +71,7 @@ function renderPanel(
searchActive: query !== ''
}}
hostCatalog={HOST_CATALOG}
+ canCreateAutomation={true}
externalManagersUncheckedNotice={uncheckedNotice}
onSelectHost={() => undefined}
onRecoverHost={() => undefined}
@@ -98,7 +99,6 @@ function renderPanel(
requestExternalAction={() => undefined}
openEditExternalDialog={() => undefined}
openCreateDialog={() => undefined}
- canCreateAutomation
onOpenDetail={() => undefined}
onRefresh={() => undefined}
isRefreshing={false}
diff --git a/src/renderer/src/components/automations/AutomationsListPanel.tsx b/src/renderer/src/components/automations/AutomationsListPanel.tsx
index 93a829b8257..a2986dd9afd 100644
--- a/src/renderer/src/components/automations/AutomationsListPanel.tsx
+++ b/src/renderer/src/components/automations/AutomationsListPanel.tsx
@@ -360,7 +360,7 @@ export function AutomationsListPanel(props: AutomationsListPanelProps): React.JS
className={cn('scrollbar-sleek min-h-0 flex-1 overflow-auto', LIST_TABLE_CONTAINER_CLASS)}
>
{hasFilteredListItems ? (
- <>
+
@@ -380,7 +380,7 @@ export function AutomationsListPanel(props: AutomationsListPanelProps): React.JS
onEdit={openEditExternalDialog}
/>
- >
+
) : (
-
+
+
+
@@ -122,24 +129,28 @@ export function AutomationsPageSkeleton(): React.JSX.Element {
-
-
-
-
-
-
-
-
-
-
-
-
- {TABLE_ROW_SKELETONS.map(({ id, ...row }) => (
-
- ))}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {TABLE_ROW_SKELETONS.map(({ id, ...row }) => (
+
+ ))}
+
diff --git a/src/renderer/src/components/automations/automations-table-layout.ts b/src/renderer/src/components/automations/automations-table-layout.ts
index 718a090e85b..f286276bfbd 100644
--- a/src/renderer/src/components/automations/automations-table-layout.ts
+++ b/src/renderer/src/components/automations/automations-table-layout.ts
@@ -1,3 +1,3 @@
// Name | Schedule | Project | Host | Next run | Last run | Status | Agent | Actions
export const AUTOMATIONS_TABLE_GRID_CLASS =
- 'grid grid-cols-[minmax(0,1.4fr)_minmax(6.5rem,9.5rem)_minmax(4.5rem,8rem)_minmax(4.5rem,7.5rem)_minmax(8rem,1fr)_minmax(7.5rem,10rem)_minmax(4.5rem,5.5rem)_2.5rem_2.5rem]'
+ 'grid grid-cols-[minmax(11rem,1.8fr)_minmax(6.5rem,9.5rem)_minmax(4.5rem,7.5rem)_minmax(4.5rem,7rem)_minmax(7.5rem,9.5rem)_minmax(7rem,9.5rem)_minmax(4.5rem,5.5rem)_2.5rem_2.5rem]'
diff --git a/src/renderer/src/lib/list-table-layout.ts b/src/renderer/src/lib/list-table-layout.ts
index 374f3deb174..c40607ee944 100644
--- a/src/renderer/src/lib/list-table-layout.ts
+++ b/src/renderer/src/lib/list-table-layout.ts
@@ -5,11 +5,24 @@
*/
export const LIST_TABLE_CONTAINER_CLASS = 'rounded-md border border-border/50 bg-muted/20'
+// Why: z-30 must beat the rows' sticky first cells (z-20) so the header still covers them.
export const LIST_TABLE_HEADER_CLASS =
- 'sticky top-0 z-10 h-8 items-center gap-3 border-b border-border/50 bg-muted/25 px-3 text-[11px] font-medium uppercase tracking-[0.08em] text-muted-foreground'
+ 'sticky top-0 z-30 h-8 items-center gap-3 border-b border-border/50 bg-muted/25 px-3 text-[11px] font-medium uppercase tracking-[0.08em] text-muted-foreground'
// Why: keep keyboard-selected rows clear of the sticky table header.
export const LIST_TABLE_ROW_CLASS =
- 'w-full min-h-11 scroll-mt-8 cursor-pointer items-center gap-3 px-3 py-3 text-left text-sm transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50'
+ 'group/list-table-row w-full min-h-11 scroll-mt-8 cursor-pointer items-center gap-3 px-3 py-3 text-left text-sm transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50'
export const LIST_TABLE_ROW_SELECTED_CLASS = 'bg-accent text-accent-foreground'
+
+// Why: sticky cells must paint opaquely, so they carry the container tint flattened
+// against the page instead of the alpha wash the scrolled columns would show through.
+// The before:/after: strips fill the row's px-3 gutter and the gap-3 column gap, which
+// the cell itself cannot reach, so scrolled columns slide fully out of sight.
+const LIST_TABLE_STICKY_CELL_BASE_CLASS =
+ 'sticky left-3 flex min-w-0 self-stretch items-center before:absolute before:-left-3 before:top-0 before:bottom-0 before:w-3 before:bg-inherit after:absolute after:-right-3 after:top-0 after:bottom-0 after:w-3 after:bg-inherit'
+
+export const LIST_TABLE_STICKY_HEADER_CELL_CLASS = `${LIST_TABLE_STICKY_CELL_BASE_CLASS} z-10 bg-[color-mix(in_srgb,var(--muted)_40%,var(--background))]`
+
+// Why: hover/selection ride variants, not props, so the frozen cell tracks the row's own wash.
+export const LIST_TABLE_STICKY_ROW_CELL_CLASS = `${LIST_TABLE_STICKY_CELL_BASE_CLASS} z-20 bg-[color-mix(in_srgb,var(--muted)_20%,var(--background))] transition-colors group-hover/list-table-row:bg-accent group-data-[current=true]/list-table-row:bg-accent`