({
({
rowSelection={tableRowSelection}
expandable={tableExpandable}
{...tablePropsWithShortcuts}
+ rowClassName={rowClassName}
scroll={{
x: scrollConfig.x,
y: scrollConfig.y,
diff --git a/web/packages/agenta-ui/src/InfiniteVirtualTable/hooks/useColumnDomRefs.ts b/web/packages/agenta-ui/src/InfiniteVirtualTable/hooks/useColumnDomRefs.ts
index 45de13d2df..aa02352e45 100644
--- a/web/packages/agenta-ui/src/InfiniteVirtualTable/hooks/useColumnDomRefs.ts
+++ b/web/packages/agenta-ui/src/InfiniteVirtualTable/hooks/useColumnDomRefs.ts
@@ -1,6 +1,7 @@
import {useLayoutEffect, useRef} from "react"
import type {ColumnDefs} from "../columnDef"
+import {ANTD_SELECTOR} from "../tableDom"
interface ColumnDomRefs {
cols: HTMLTableColElement[]
@@ -24,9 +25,7 @@ const useColumnDomRefs = (
}
const headerCells = Array.from(
- container.querySelectorAll(
- ".ant-table-thead th[data-column-key]",
- ),
+ container.querySelectorAll(ANTD_SELECTOR.headerCellWithKey),
).filter((cell) => Number(cell.getAttribute("colspan") ?? "1") === 1)
if (!headerCells.length) {
diff --git a/web/packages/agenta-ui/src/InfiniteVirtualTable/hooks/useScrollConfig.ts b/web/packages/agenta-ui/src/InfiniteVirtualTable/hooks/useScrollConfig.ts
index 2bc84e02ad..5fc7606edc 100644
--- a/web/packages/agenta-ui/src/InfiniteVirtualTable/hooks/useScrollConfig.ts
+++ b/web/packages/agenta-ui/src/InfiniteVirtualTable/hooks/useScrollConfig.ts
@@ -2,6 +2,7 @@ import {useMemo, useRef, type RefObject} from "react"
import type {TableProps} from "antd/es/table"
+import {ANTD_SELECTOR} from "../tableDom"
import {shallowEqual} from "../utils/columnUtils"
interface UseScrollConfigOptions {
@@ -50,7 +51,7 @@ const useScrollConfig = ({
const headerHeight =
(typeof tableHeaderHeight === "number" && Number.isFinite(tableHeaderHeight)
? tableHeaderHeight
- : (containerRef.current?.querySelector(".ant-table-thead") as HTMLElement | null)
+ : (containerRef.current?.querySelector(ANTD_SELECTOR.header) as HTMLElement | null)
?.offsetHeight) ?? null
const computedY = Math.max((containerHeight ?? 0) - (headerHeight ?? 0), 0)
diff --git a/web/packages/agenta-ui/src/InfiniteVirtualTable/hooks/useScrollContainer.ts b/web/packages/agenta-ui/src/InfiniteVirtualTable/hooks/useScrollContainer.ts
index 0a82f638a0..0ae9ebba66 100644
--- a/web/packages/agenta-ui/src/InfiniteVirtualTable/hooks/useScrollContainer.ts
+++ b/web/packages/agenta-ui/src/InfiniteVirtualTable/hooks/useScrollContainer.ts
@@ -1,5 +1,7 @@
import {useEffect, useRef, useState} from "react"
+import {ANTD_SELECTOR} from "../tableDom"
+
interface ScrollContainerResult {
scrollContainer: HTMLDivElement | null
visibilityRoot: HTMLDivElement | null
@@ -33,7 +35,7 @@ const useScrollContainer = (
return
}
- const tableBody = containerElement.querySelector(".ant-table-body") ?? null
+ const tableBody = containerElement.querySelector(ANTD_SELECTOR.body) ?? null
const isScrollable = (element: HTMLDivElement | null) => {
if (!element) return false
@@ -52,7 +54,7 @@ const useScrollContainer = (
}
const headerContainer =
- containerElement.querySelector(".ant-table-container") ??
+ containerElement.querySelector(ANTD_SELECTOR.container) ??
containerElement
if (headerContainer !== lastVisibilityRootRef.current) {
diff --git a/web/packages/agenta-ui/src/InfiniteVirtualTable/hooks/useTableHeaderHeight.ts b/web/packages/agenta-ui/src/InfiniteVirtualTable/hooks/useTableHeaderHeight.ts
index 9e16e47254..e03838e771 100644
--- a/web/packages/agenta-ui/src/InfiniteVirtualTable/hooks/useTableHeaderHeight.ts
+++ b/web/packages/agenta-ui/src/InfiniteVirtualTable/hooks/useTableHeaderHeight.ts
@@ -3,6 +3,7 @@ import {useLayoutEffect, useState, type RefObject} from "react"
import type {TableProps} from "antd/es/table"
import type {ColumnDefs} from "../columnDef"
+import {ANTD_SELECTOR} from "../tableDom"
interface UseTableHeaderHeightOptions {
containerRef: RefObject
@@ -29,7 +30,7 @@ const useTableHeaderHeight = ({
return
}
const headerEl =
- container.querySelector(".ant-table-thead") ??
+ container.querySelector(ANTD_SELECTOR.header) ??
container.querySelector("table thead")
if (!headerEl) {
setTableHeaderHeight(null)
diff --git a/web/packages/agenta-ui/src/InfiniteVirtualTable/hooks/useTableManager.tsx b/web/packages/agenta-ui/src/InfiniteVirtualTable/hooks/useTableManager.tsx
index f7525b95ec..4cba009b7d 100644
--- a/web/packages/agenta-ui/src/InfiniteVirtualTable/hooks/useTableManager.tsx
+++ b/web/packages/agenta-ui/src/InfiniteVirtualTable/hooks/useTableManager.tsx
@@ -17,6 +17,7 @@ import type {
TableDeleteConfig,
TableExportConfig,
} from "../features/InfiniteVirtualTableFeatureShell"
+import {ANTD_SELECTOR} from "../tableDom"
import type {
InfiniteTableRowBase,
InfiniteVirtualTableProps,
@@ -31,7 +32,7 @@ const dummySearchAtom = atom("")
const INTERACTIVE_SELECTOR =
"button, a, input, textarea, select, [role='button'], [role='menuitem'], [role='checkbox'], " +
".ant-btn, .ant-checkbox, .ant-checkbox-input, .ant-checkbox-inner, .ant-checkbox-wrapper, " +
- ".ant-select, .ant-dropdown-trigger, .ant-table-selection-column, .ag-table-actions-cell"
+ ANTD_SELECTOR.interactiveCell
/**
* Returns true when the click originated from an interactive element (button, link,
diff --git a/web/packages/agenta-ui/src/InfiniteVirtualTable/index.ts b/web/packages/agenta-ui/src/InfiniteVirtualTable/index.ts
index 7cdc4a2345..5b245f0148 100644
--- a/web/packages/agenta-ui/src/InfiniteVirtualTable/index.ts
+++ b/web/packages/agenta-ui/src/InfiniteVirtualTable/index.ts
@@ -137,6 +137,7 @@ export type {
ColumnSorterConfig,
} from "./columnDef"
export {toAntdColumns, fromAntdColumns} from "./antdColumns"
+export {AVT, ANTD_SELECTOR, stampTableDom, type AvtClass} from "./tableDom"
export type {VisibilityRegistrationHandler} from "./components/ColumnVisibilityHeader"
// Shared hooks for cell renderers
diff --git a/web/packages/agenta-ui/src/InfiniteVirtualTable/tableDom.ts b/web/packages/agenta-ui/src/InfiniteVirtualTable/tableDom.ts
new file mode 100644
index 0000000000..ff2c2a4a83
--- /dev/null
+++ b/web/packages/agenta-ui/src/InfiniteVirtualTable/tableDom.ts
@@ -0,0 +1,55 @@
+/**
+ * The table's DOM contract, in one file.
+ *
+ * `AVT` names are stamped onto the rendered table by the package and are the only hooks app
+ * code should target. They survive the render-leaf swap; `.ant-table-*` will not.
+ *
+ * `ANTD_SELECTOR` is where each hook lives in antd's DOM today. It is the implementation
+ * detail the swap replaces, so keep every `.ant-table-*` string in this file and nowhere else
+ * in the directory.
+ */
+
+export const AVT = {
+ root: "avt-table",
+ container: "avt-container",
+ body: "avt-body",
+ header: "avt-thead",
+ row: "avt-row",
+ cell: "avt-cell",
+ headerCell: "avt-head-cell",
+} as const
+
+export type AvtClass = (typeof AVT)[keyof typeof AVT]
+
+export const ANTD_SELECTOR = {
+ container: ".ant-table-container",
+ body: ".ant-table-body",
+ bodyInner: ".ant-table-body-inner",
+ header: ".ant-table-thead",
+ headerCellWithKey: ".ant-table-thead th[data-column-key]",
+ headerSelectionCell: ".ant-table-thead th.ant-table-selection-column",
+ selectionCol: "colgroup col.ant-table-selection-col",
+ /** Cells that own their click, so row-click shortcuts skip them. */
+ interactiveCell:
+ ".ant-select, .ant-dropdown-trigger, .ant-table-selection-column, .ag-table-actions-cell",
+} as const
+
+/** Structural nodes rendered once per table, so a mount-time stamp is enough. */
+const STAMPED: [selector: string, className: string][] = [
+ [ANTD_SELECTOR.container, AVT.container],
+ [ANTD_SELECTOR.body, AVT.body],
+ [ANTD_SELECTOR.header, AVT.header],
+]
+
+/**
+ * Adds the stable class hooks to a mounted table.
+ *
+ * Rows and cells are not stamped here — virtualization recycles them, so they get their class
+ * through `rowClassName` and the column adapter instead.
+ */
+export const stampTableDom = (container: HTMLElement | null): void => {
+ if (!container) return
+ for (const [selector, className] of STAMPED) {
+ container.querySelector(selector)?.classList.add(className)
+ }
+}