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`